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/11/18 16:55:42 UTC

svn commit: r1640379 - in /qpid/dispatch/trunk: python/qpid_dispatch/management/ python/qpid_dispatch_internal/ python/qpid_dispatch_internal/management/ src/ tests/

Author: aconway
Date: Tue Nov 18 15:55:41 2014
New Revision: 1640379

URL: http://svn.apache.org/r1640379
Log:
DISPATCH-74: Allow changes to logging configuration of a running router.

Allow UPDATE management requests to log entities to change log settings on a running router.
Use log:MODULE as management ID for log entity representing MODULE.
The generic qdmanage client can be used to modify log settings.

This commit does not include a more convenient client for this task.

Modified:
    qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json
    qpid/dispatch/trunk/python/qpid_dispatch_internal/dispatch_c.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py
    qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py
    qpid/dispatch/trunk/src/alloc.c
    qpid/dispatch/trunk/src/c_entity.c
    qpid/dispatch/trunk/src/log.c
    qpid/dispatch/trunk/src/router_agent.c
    qpid/dispatch/trunk/src/server.c
    qpid/dispatch/trunk/tests/system_tests_management.py

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=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch/management/qdrouter.json Tue Nov 18 15:55:41 2014
@@ -233,7 +233,7 @@
             "include": [
                 "common"
             ],
-            "operations": ["CREATE", "READ"],
+            "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
             "attributes": {
                 "module": {
                     "type":[

Modified: qpid/dispatch/trunk/python/qpid_dispatch_internal/dispatch_c.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/python/qpid_dispatch_internal/dispatch_c.py?rev=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/dispatch_c.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/dispatch_c.py Tue Nov 18 15:55:41 2014
@@ -70,8 +70,8 @@ class QdDll(ctypes.PyDLL):
 
         self._prototype(self.qd_connection_manager_start, None, [self.qd_dispatch_p])
         self._prototype(self.qd_waypoint_activate_all, None, [self.qd_dispatch_p])
-        self._prototype(self.qd_c_entity_update_begin, c_long, [py_object])
-        self._prototype(self.qd_c_entity_update_end, None, [])
+        self._prototype(self.qd_c_entity_refresh_begin, c_long, [py_object])
+        self._prototype(self.qd_c_entity_refresh_end, None, [])
 
     def _errcheck(self, result, func, args):
         if self.qd_error_code():

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=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/agent.py Tue Nov 18 15:55:41 2014
@@ -48,7 +48,7 @@ Adding/removing/updating entities from C
   4. unlocks the router.
 """
 
-import traceback
+import traceback, sys
 from itertools import ifilter, chain
 from traceback import format_exc
 from threading import Lock
@@ -93,7 +93,9 @@ class Entity(SchemaEntity):
     Base class for agent entities with operations as well as attributes.
     """
 
-    def _update(self): return False # Replaced by _set_pointer
+    def _refresh(self):
+        """Refresh self.attributes from C implementation. No-op if no C impl pointer"""
+        return False # Replaced by _set_pointer
 
     def __init__(self, agent, entity_type, attributes=None, validate=True):
         """
@@ -101,7 +103,7 @@ class Entity(SchemaEntity):
         @param dispatch: Pointer to qd_dispatch C object.
         @param entity_type: L{EntityType}
         @param attributes: Attribute name:value map
-        @param pointer: Pointer to C object that can be used to update attributes.
+        @param pointer: Pointer to C object that can be used to refresh attributes.
         """
         super(Entity, self).__init__(entity_type, attributes, validate=validate)
         # Direct __dict__ access to avoid validation as schema attributes
@@ -110,13 +112,13 @@ class Entity(SchemaEntity):
         self.__dict__['_dispatch'] = agent.dispatch
 
     def _set_pointer(self, pointer):
-        fname = "qd_c_entity_update_" + self.entity_type.short_name.replace('.', '_')
-        updatefn = self._qd.function(
+        fname = "qd_c_entity_refresh_" + self.entity_type.short_name.replace('.', '_')
+        refreshfn = self._qd.function(
             fname, c_long, [py_object, c_void_p])
-        def _do_update():
-            updatefn(self.attributes, pointer)
+        def _do_refresh():
+            refreshfn(self.attributes, pointer)
             return True
-        self.__dict__['_update'] = _do_update
+        self.__dict__['_refresh'] = _do_refresh
 
     def create(self, request):
         """Subclasses can add extra create actions here"""
@@ -135,13 +137,22 @@ class Entity(SchemaEntity):
         newattrs = dict(self.attributes, **request.body)
         self.entity_type.validate(newattrs)
         self.attributes = newattrs
+        self._update()
         return (OK, self.attributes)
 
+    def _update(self):
+        """Subclasses implement update logic here"""
+        pass
+
     def delete(self, request):
         """Handle delete request from client"""
         self._agent.remove(self)
+        self._delete()
         return (NO_CONTENT, {})
 
+    def _delete(self):
+        """Subclasses implement delete logic here"""
+        pass
 
 class ContainerEntity(Entity): pass
 
@@ -153,9 +164,21 @@ class RouterEntity(Entity):
         self._set_pointer(self._dispatch)
 
 class LogEntity(Entity):
+    def __init__(self, agent, entity_type, attributes=None, validate=True):
+        module = attributes.get('module')
+        if module:
+            attributes['identity'] = attributes['name'] = "%s:%s" % (entity_type.short_name, module)
+        super(LogEntity, self).__init__(agent, entity_type, attributes, validate)
+
     def create(self, request):
         self._qd.qd_log_entity(self)
 
+    def _update(self):
+        self._qd.qd_log_entity(self)
+
+    def _delete(self):
+        """Can't actually delete a log source but return it to the default state"""
+        self._qd.qd_log_source_reset(self.attributes['module'])
 
 class ListenerEntity(Entity):
     def create(self, request):
@@ -204,7 +227,7 @@ class CEntity(Entity):
 
         super(CEntity, self).__init__(agent, entity_type, validate=False)
         self._set_pointer(pointer)
-        self._update()
+        self._refresh()
         identity = self.attributes.get('identity')
         if identity is None: identity = str(self.id_count.next())
         self.attributes['identity'] = prefix(entity_type.short_name, identity)
@@ -233,7 +256,7 @@ class AllocatorEntity(CEntity):
 
 class EntityCache(object):
     """
-    Searchable cache of entities, can be updated from C attributes.
+    Searchable cache of entities, can be refreshd from C attributes.
     """
     def __init__(self, agent):
         self.entities = []
@@ -284,8 +307,8 @@ class EntityCache(object):
             del self.pointers[pointer]
             self._remove(entity)
 
-    def update_from_c(self):
-        """Update entities from the C dispatch runtime"""
+    def refresh_from_c(self):
+        """Refresh entities from the C dispatch runtime"""
         events = []
         REMOVE, ADD, REMOVE_ADD = 0, 1, 2
 
@@ -312,7 +335,7 @@ class EntityCache(object):
         # FIXME aconway 2014-10-23: locking is ugly, push it down into C code.
         self.qd.qd_dispatch_router_lock(self.agent.dispatch)
         try:
-            self.qd.qd_c_entity_update_begin(events)
+            self.qd.qd_c_entity_refresh_begin(events)
             # Collapse sequences of add/remove into a single remove/add/remove_add per pointer.
             actions = {}
             for action, type, pointer in events:
@@ -327,9 +350,9 @@ class EntityCache(object):
                     entity = klass(self.agent, entity_type, pointer)
                     self.add(entity, pointer)
 
-            for e in self.entities: e._update()
+            for e in self.entities: e._refresh()
         finally:
-            self.qd.qd_c_entity_update_end()
+            self.qd.qd_c_entity_refresh_end()
             self.qd.qd_dispatch_router_unlock(self.agent.dispatch)
 
 class Agent(object):
@@ -394,7 +417,7 @@ class Agent(object):
         """Called when a management request is received."""
         # Coarse locking, handle one request at a time.
         with self.request_lock:
-            self.entities.update_from_c()
+            self.entities.refresh_from_c()
             self.log(LOG_DEBUG, "Agent request %s on link %s"%(request, link_id))
             def error(e, trace):
                 """Raise an error"""
@@ -525,7 +548,7 @@ class Agent(object):
             raise InternalServerErrorStatus(
                 "Duplicate (%s) entities with %s=%r" % (len(found), k, v))
         else:
-            raise NotFoundStatus("No entity with %s'" % attrvals())
+            raise NotFoundStatus("No entity with %s" % attrvals())
 
         for k, v in ids.iteritems():
             if entity[k] != v: raise BadRequestStatus("Conflicting %s" % attrvals())

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=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py (original)
+++ qpid/dispatch/trunk/python/qpid_dispatch_internal/management/config.py Tue Nov 18 15:55:41 2014
@@ -144,23 +144,26 @@ def configure_dispatch(dispatch, filenam
     dispatch = qd.qd_dispatch_p(dispatch)
     config = Config(filename)
 
-    # NOTE: Can't import agent till till dispatch C extension module is initialized.
-    from .agent import Agent
-    agent = Agent(dispatch, config.entities)
-    qd.qd_dispatch_set_agent(dispatch, agent)
-
     # Configure any DEFAULT log entities first so we can report errors in non-
     # default log configurations to the correct place.
     for l in config.by_type('log'):
         if l['module'].upper() == 'DEFAULT': qd.qd_log_entity(l)
     for l in config.by_type('log'):
         if l['module'].upper() != 'DEFAULT': qd.qd_log_entity(l)
+
+    # NOTE: Can't import agent till till dispatch C extension module is initialized.
+    from .agent import Agent
+    agent = Agent(dispatch, config.entities)
+    qd.qd_dispatch_set_agent(dispatch, agent)
+
+    # Configure and prepare container and router before activating agent
     qd.qd_dispatch_configure_container(dispatch, config.by_type('container').next())
     qd.qd_dispatch_configure_router(dispatch, config.by_type('router').next())
     qd.qd_dispatch_prepare(dispatch)
 
     agent.activate("$management")
-    qd.qd_router_setup_late(dispatch)
+
+    qd.qd_router_setup_late(dispatch) # Actions requiring management agent.
 
     # Note must configure addresses, waypoints, listeners and connectors after qd_dispatch_prepare
     for a in config.by_type('fixedAddress'): qd.qd_dispatch_configure_address(dispatch, a)

Modified: qpid/dispatch/trunk/src/alloc.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/alloc.c?rev=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/alloc.c (original)
+++ qpid/dispatch/trunk/src/alloc.c Tue Nov 18 15:55:41 2014
@@ -347,7 +347,7 @@ void qd_alloc_finalize(void)
 }
 
 
-qd_error_t qd_c_entity_update_allocator(qd_entity_t* entity, void *impl) {
+qd_error_t qd_c_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) &&

Modified: qpid/dispatch/trunk/src/c_entity.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/c_entity.c?rev=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/c_entity.c (original)
+++ qpid/dispatch/trunk/src/c_entity.c Tue Nov 18 15:55:41 2014
@@ -73,8 +73,8 @@ void qd_c_entity_remove(const char *type
 // Get events in the add/remove cache into a python list of (action, type, pointer)
 // Locks the entity cache so entities can be updated safely (prevent entities from being deleted.)
 // Do not processs any entities if return error code != 0
-// Must call qd_c_entity_update_end when done, regardless of error code.
-qd_error_t qd_c_entity_update_begin(PyObject *list) {
+// Must call qd_c_entity_refresh_end when done, regardless of error code.
+qd_error_t qd_c_entity_refresh_begin(PyObject *list) {
     if (!event_lock) return QD_ERROR_NONE;    /* Unit tests don't call qd_c_entity_initialize */
     qd_error_clear();
     sys_mutex_lock(event_lock);
@@ -92,7 +92,7 @@ qd_error_t qd_c_entity_update_begin(PyOb
     return qd_error_code();
 }
 
-void qd_c_entity_update_end() {
+void qd_c_entity_refresh_end() {
     sys_mutex_unlock(event_lock);
 }
 

Modified: qpid/dispatch/trunk/src/log.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/log.c?rev=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/log.c (original)
+++ qpid/dispatch/trunk/src/log.c Tue Nov 18 15:55:41 2014
@@ -239,6 +239,14 @@ static void write_log(qd_log_source_t *l
     }
 }
 
+/// Reset the log source to the default state
+static void qd_log_source_defaults(qd_log_source_t *log_source) {
+    log_source->mask = -1;
+    log_source->timestamp = -1;
+    log_source->source = -1;
+    log_source->sink = 0;
+}
+
 /// Caller must hold the log_source_lock
 static qd_log_source_t *qd_log_source_lh(const char *module)
 {
@@ -249,10 +257,7 @@ static qd_log_source_t *qd_log_source_lh
 	memset(log_source, 0, sizeof(qd_log_source_t));
 	DEQ_ITEM_INIT(log_source);
 	log_source->module = module;
-	log_source->mask = -1;
-	log_source->timestamp = -1;
-	log_source->source = -1;
-	log_source->sink = 0;
+        qd_log_source_defaults(log_source);
         DEQ_INSERT_TAIL(source_list, log_source);
     }
     return log_source;
@@ -266,6 +271,15 @@ qd_log_source_t *qd_log_source(const cha
     return src;
 }
 
+qd_log_source_t *qd_log_source_reset(const char *module)
+{
+    sys_mutex_lock(log_source_lock);
+    qd_log_source_t* src = qd_log_source_lh(module);
+    qd_log_source_defaults(src);
+    sys_mutex_unlock(log_source_lock);
+    return src;
+}
+
 static void qd_log_source_free_lh(qd_log_source_t* src) {
     DEQ_REMOVE_HEAD(source_list);
     log_sink_free_lh(src->sink);

Modified: qpid/dispatch/trunk/src/router_agent.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/router_agent.c?rev=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/router_agent.c (original)
+++ qpid/dispatch/trunk/src/router_agent.c Tue Nov 18 15:55:41 2014
@@ -36,7 +36,7 @@ static const char *qd_router_mode_names[
 };
 ENUM_DEFINE(qd_router_mode, qd_router_mode_names);
 
-qd_error_t qd_c_entity_update_router(qd_entity_t* entity, void *impl) {
+qd_error_t qd_c_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_set_stringf(entity, "name", "%s:%s", QD_ROUTER_TYPE, router->router_id) == 0 &&
@@ -57,7 +57,7 @@ static const char *address_text(qd_addre
     return addr ? (const char*) qd_hash_key_by_handle(addr->hash_handle) : 0;
 }
 
-qd_error_t qd_c_entity_update_router_address(qd_entity_t* entity, void *impl) {
+qd_error_t qd_c_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) &&
@@ -76,7 +76,7 @@ qd_error_t qd_c_entity_update_router_add
 
 #define CHECK(err) if (err != 0) return qd_error_code()
 
-qd_error_t qd_c_entity_update_router_node(qd_entity_t* entity, void *impl) {
+qd_error_t qd_c_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")) {
@@ -251,7 +251,7 @@ static const char *qd_router_addr_text(q
     return addr ? (const char*)qd_hash_key_by_handle(addr->hash_handle) : NULL;
 }
 
-qd_error_t qd_c_entity_update_router_link(qd_entity_t* entity, void *impl)
+qd_error_t qd_c_entity_refresh_router_link(qd_entity_t* entity, void *impl)
 {
     qd_router_link_t *link = (qd_router_link_t*) impl;
     /* FIXME aconway 2014-10-17: old management used link->bit_mask as name/identity,

Modified: qpid/dispatch/trunk/src/server.c
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/src/server.c?rev=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/src/server.c (original)
+++ qpid/dispatch/trunk/src/server.c Tue Nov 18 15:55:41 2014
@@ -78,7 +78,7 @@ static qd_error_t connection_entity_upda
         return qd_entity_set_string(entity, "host", pn_connector_name(conn->pn_cxtr));
 }
 
-qd_error_t qd_c_entity_update_connection(qd_entity_t* entity, void *impl)
+qd_error_t qd_c_entity_refresh_connection(qd_entity_t* entity, void *impl)
 {
     qd_connection_t *conn = (qd_connection_t*)impl;
     const qd_server_config_t *config =

Modified: qpid/dispatch/trunk/tests/system_tests_management.py
URL: http://svn.apache.org/viewvc/qpid/dispatch/trunk/tests/system_tests_management.py?rev=1640379&r1=1640378&r2=1640379&view=diff
==============================================================================
--- qpid/dispatch/trunk/tests/system_tests_management.py (original)
+++ qpid/dispatch/trunk/tests/system_tests_management.py Tue Nov 18 15:55:41 2014
@@ -94,7 +94,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)
-        for name in ['router:' + self.router.name, 'log:0']:
+        for name in ['router:' + self.router.name, 'log:DEFAULT']:
             self.assertTrue([r for r in response.get_dicts() if r['name'] == name],
                             msg="Can't find result with name '%s'" % name)
 
@@ -123,16 +123,44 @@ class ManagementTest(system_test.TestCas
         router = node3.query(type=ROUTER).get_entities()
         self.assertEqual(self.__class__.router.name, router[0]['routerId'])
 
-    def test_create_log(self):
-        """Create a log entity"""
+    def test_log(self):
+        """Create, update and query log entities"""
+
+        self.assertRaises(NotFoundStatus, self.node.read, identity='log:AGENT') # Not configured
+
+        default = self.node.read(identity='log:DEFAULT')
+        self.assertEqual(default.attributes,
+                         {u'identity': u'log:DEFAULT',
+                          u'level': u'trace',
+                          u'module': u'DEFAULT',
+                          u'name': u'log:DEFAULT',
+                          u'output': u'ManagementTest.log',
+                          u'source': True,
+                          u'timestamp': True,
+                          u'type': u'org.apache.qpid.dispatch.log'})
+
+
+        def check_log(log, bad_type='nosuch'):
+            # Cause an error and verify it shows up in the log file.
+            self.assertRaises(ManagementError, self.node.create, type=bad_type, name=bad_type)
+            f = self.cleanup(open(log))
+            logstr = f.read()
+            self.assertTrue(re.search(r'ValidationError.*%s' % bad_type, logstr),
+                            msg="Can't find expected ValidationError.*%s in '%r'" % (bad_type, logstr))
+
+        # Create a log entity, verify logging is as expected
         log = os.path.abspath("test_create_log.log")
-        self.assert_create_ok('log', 'log.1', dict(module='AGENT', level="error", output=log))
-        # Cause an error and verify it shows up in the log file.
-        self.assertRaises(ManagementError, self.node.create, type='nosuch', name='nosuch')
-        f = self.cleanup(open(log))
-        logstr = f.read()
-        self.assertTrue(re.search(r'ValidationError.*nosuch', logstr),
-                        msg="Can't find expected ValidationError.*nosuch in '%r'" % logstr)
+        agent_log = self.assert_create_ok('log', 'log.1', dict(module='AGENT', level="error", output=log))
+        check_log(log)
+
+        # Update the log entity to output to a different file
+        log = os.path.abspath("test_create_log2.log")
+        self.node.update(dict(module='AGENT', level="error", output=log), identity='log:AGENT')
+        check_log(log)
+
+        # Delete the log entity - return to default state.
+        self.node.delete(identity='log:AGENT')
+        self.assertRaises(AssertionError, check_log, log, 'nosuch2')
 
     def test_create_fixed_address(self):
         self.assert_create_ok(FIXED_ADDRESS, 'fixed1', dict(prefix='fixed1'))



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