diff options
Diffstat (limited to 'qpid/extras/dispatch')
64 files changed, 5453 insertions, 2439 deletions
diff --git a/qpid/extras/dispatch/CMakeLists.txt b/qpid/extras/dispatch/CMakeLists.txt index ae0e126be7..a55de067f9 100644 --- a/qpid/extras/dispatch/CMakeLists.txt +++ b/qpid/extras/dispatch/CMakeLists.txt @@ -51,6 +51,7 @@ set(INCLUDE_INSTALL_DIR include CACHE PATH "Include file directory") set(LIB_INSTALL_DIR "lib${LIB_SUFFIX}" CACHE PATH "Library object file directory") set(SYSCONF_INSTALL_DIR etc CACHE PATH "System read only configuration directory") set(SHARE_INSTALL_DIR share CACHE PATH "Shared read only data directory") +set(DOC_INSTALL_DIR ${SHARE_INSTALL_DIR}/doc CACHE PATH "Shared read-only data directory") set(MAN_INSTALL_DIR share/man CACHE PATH "Manpage directory") # determine the location for installing the python packages @@ -61,13 +62,6 @@ if (PYTHONLIBS_FOUND) OUTPUT_STRIP_TRAILING_WHITESPACE) endif (PYTHONLIBS_FOUND) -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/src - ${proton_include} - ${PYTHON_INCLUDE_PATH} - ) - ## ## Find dependencies ## @@ -76,6 +70,13 @@ find_library(pthread_lib pthread) find_library(rt_lib rt) find_path(proton_include proton/driver.h) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${proton_include} + ${PYTHON_INCLUDE_PATH} + ) + set(CMAKE_C_FLAGS "-pthread -Wall -Werror -std=gnu99") set(CATCH_UNDEFINED "-Wl,--no-undefined") @@ -85,6 +86,8 @@ set(CATCH_UNDEFINED "-Wl,--no-undefined") set(server_SOURCES src/agent.c src/alloc.c + src/amqp.c + src/bitmask.c src/buffer.c src/compose.c src/config.c @@ -98,12 +101,18 @@ set(server_SOURCES src/parse.c src/posix/threading.c src/python_embedded.c + src/router_agent.c src/router_node.c + src/router_pynode.c src/server.c src/timer.c src/work_queue.c ) +set_property(SOURCE src/python_embedded.c src/router_pynode.c + PROPERTY COMPILE_FLAGS -Wno-strict-aliasing + ) + add_library(qpid-dispatch SHARED ${server_SOURCES}) target_link_libraries(qpid-dispatch ${proton_lib} ${pthread_lib} ${rt_lib} ${PYTHON_LIBRARIES}) set_target_properties(qpid-dispatch PROPERTIES @@ -116,7 +125,7 @@ install(TARGETS qpid-dispatch file(GLOB headers "include/qpid/dispatch/*.h") install(FILES ${headers} DESTINATION ${INCLUDE_INSTALL_DIR}/qpid/dispatch) install(FILES include/qpid/dispatch.h DESTINATION ${INCLUDE_INSTALL_DIR}/qpid) -install(FILES etc/qpid-dispatch.conf DESTINATION ${SYSCONF_INSTALL_DIR}) +install(FILES etc/qpid-dispatch.conf DESTINATION ${SYSCONF_INSTALL_DIR}/qpid) ## ## Python modules installation @@ -149,6 +158,13 @@ set(PYTHON_CONFIG_SOURCES python/qpid/dispatch/__init__.py ) +set(DOC_FILES + ChangeLog + LICENSE + README.md + TODO +) + install(FILES ${PYTHON_STUBS_SOURCES} DESTINATION ${PYTHON_SITELIB_PACKAGES}/qpid/dispatch/stubs) @@ -163,6 +179,9 @@ install(FILES python/qpid/__init__.py install(FILES python/qpid/dispatch/__init__.py DESTINATION ${PYTHON_SITELIB_PACKAGES}/qpid/dispatch) + +install(FILES ${DOC_FILES} + DESTINATION ${DOC_INSTALL_DIR}/qpid-dispatch) ## ## Build Tests ## diff --git a/qpid/extras/dispatch/ChangeLog b/qpid/extras/dispatch/ChangeLog index a171f0eb11..2a5b4bc4b7 100644 --- a/qpid/extras/dispatch/ChangeLog +++ b/qpid/extras/dispatch/ChangeLog @@ -1,2 +1,26 @@ Version 0.1: * Initial development work. + * QPID-5186: Install documentation files. + * QPID-5185: Install qpid-dispatch.conf to /etc/qpid + * QPID-4612: Dispatch - Change server and container pattern to be consistent with other objects + * QPID-4613: Dispatch Message API Improvement + * QPID-4614: CTEST for Dispatch + * QPID-4913: Dispatch - Add a configuration file reader to configure the service + * QPID-4968: Dispatch - Generalized framework for embedded Python modules + * QPID-4974: Dispatch - Improve the API for parsing and composing AMQP-typed fields + * QPID-5066: Dispatch - move Python code into the qpid.dispatch package + * QPID-5068: Dispatch - Internal feature to easily add and update Delivery Annotations + * QPID-5096: Dispatch - Install the configuration file + * QPID-5097: Dispatch - create a source tarball + * QPID-5181: Dispatch - Assign temporary source addresses for dynamic listener links + * QPID-5185: Move the qpid-dispatch.conf file to /etc/qpid + * QPID-5186: Installing Dispatch should also install the LICENSE, TODO and related files + * QPID-5189: Add a config.sh file for Qpid Dispatch to set an environment for running the router + + * QPID-4788: Dispatch - Re-schedule of an "immediate" timer causes crash + * QPID-4816: dispatch-router crashes when incomplete (but valid) url specified by client. + * QPID-4997: Dispatch - Thread safety issues in the usage of Proton + * QPID-5064: Dispatch - make-install doesn't install the Python artifacts + * QPID-5173: [dispatch] cmake ignores overrides to CMAKE_INCLUDE_PATH and CMAKE_LIBRARY_PATH + * QPID-5201: Dispatch - Fix build errors in Release mode + * QPID-5218: [dispatch] Crash when outgoing window > 0 and multiple subscribed Messenger clients diff --git a/qpid/extras/dispatch/README.md b/qpid/extras/dispatch/README.md index 22e1a68570..1a4438fdf9 100644 --- a/qpid/extras/dispatch/README.md +++ b/qpid/extras/dispatch/README.md @@ -13,8 +13,18 @@ $ mkdir build $ cd build $ cmake .. $ make -$ make test +$ make test # see below Note: Your PYTHONPATH _must_ include <dispatch>/python in its list of paths in order to test and run Dispatch. +Running The Tests +================= + +Prior to running the unit tests, you should source the file config.sh which is +found in the root directory. + +$ . config.sh + +The file sets up the environment so that the tests can find the Python +libraries, etc. diff --git a/qpid/extras/dispatch/TODO b/qpid/extras/dispatch/TODO index ab0c1a951e..5bb334a6ca 100644 --- a/qpid/extras/dispatch/TODO +++ b/qpid/extras/dispatch/TODO @@ -5,3 +5,50 @@ enhancements to be fixed by going to the Apache Qpid JIRA instance: http://issues.apache.org/jira/browse/QPID ============================================================================== + +- Router Mode: + o Stand-Alone-Router - Does not participate in routing protocol, does not permit inter-router + links, acts as a normal interior-router otherwise. + o Interior-Router - Participates in the routing protocol + o Edge-Concentrator - Does not participate in routing protocol, requires uplink connection(s) + This mode should be used when Dispatch is integrated into an endpoint + application or when it is acting as a connection concentrator. + Proxy and access-protocol functions will be available in this mode. + +- Connection Annotation: + o Type: Inter-router, uplink, endpoint, etc. This formal annotation can be accessed internally + by the connection handlers to guide Dispatch's handling of new connections. + o Weight-{in,out}: Weight/Cost metrics for inter-router links + +- Statistics for Instrumentation: + o Link + . delivery count {unsettled, pre-settled} + . deliveries {accepted, rejected, released, modified} + . octets of delivery {accepted, rejected, released, modified} + . flow frame count + . disposition frame count {forward, backward} + o Address + . deliveries {ingress, egress, transit} + . octets of delivery {ingress, egress, transit} + +- Infrastructure + o Router_Link - Buffer and Iterator for a copy of the link's target address (for use + as an address for messages with no 'to' field). + o Router Event Queue - Event queue to feed alerts to the Python router code. + Neighbor-link-loss is a valuable event because it accelerates the + detection of topology change. + o All PyRouter stimulus through a work queue. + o Router Code Updates + . Report address mappings to routers + . Generate RA immediately after updating routing tables + . Generate unsolicited updates for mobile addresses? + o Expose idle-timeout/keepalive on connectors and listeners + +- Major Roadmap Features + o Security Policy Enforcement + o Proxy (Translation Node) Capability + o Address Provisioning with variable semantics + o Link Routing + o Management, Instrumentation, and Accounting + o Link Cost + o Area Routing diff --git a/qpid/extras/dispatch/config.sh b/qpid/extras/dispatch/config.sh new file mode 100644 index 0000000000..3c96464ea9 --- /dev/null +++ b/qpid/extras/dispatch/config.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +# 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. +# + +cd $(dirname ${BASH_SOURCE[0]}) > /dev/null +export QPID_DISPATCH_HOME=$(pwd) +cd - > /dev/null + +export PYTHONPATH=$QPID_DISPATCH_HOME/python:$QPID_DISPATCH_HOME/tools/src/py:$PYTHONPATH +export PATH=$QPID_DISPATCH_HOME/tools/src/py:$PATH + diff --git a/qpid/extras/dispatch/etc/qpid-dispatch.conf b/qpid/extras/dispatch/etc/qpid-dispatch.conf index ce7f5ed4c1..83fa98af60 100644 --- a/qpid/extras/dispatch/etc/qpid-dispatch.conf +++ b/qpid/extras/dispatch/etc/qpid-dispatch.conf @@ -102,6 +102,7 @@ listener { listener { label: Router Interconnect Access + role: inter-router addr: 0.0.0.0 port: 5671 sasl-mechanisms: EXTERNAL @@ -112,6 +113,7 @@ listener { connector { label: Router Uplink + role: inter-router addr: backbone.enterprise.com port: amqps sasl-mechanisms: EXTERNAL @@ -125,49 +127,27 @@ connector { ## router { ## - ## area and router-id - Each router is assigned an area name and a - ## router-id that is unique within the area. - ## - area: all - router-id: Router.A - - ## - ## hello-interval - The interval, in seconds, between HELLO messages - ## sent between connected routers. This interval can be relatively - ## fast because HELLO messages do not propagate beyond one hop on the - ## network. A good value is 1 second. - ## - hello-interval: 1 - + ## Router Mode: ## - ## hello-max-age - The maximum time, in seconds, that can elapse - ## without receiving a HELLO message from a neighbor before that - ## neighbor is declared absent. A good value is 3 seconds. + ## standalone - Standalone router. In standalone mode, the router operates as + ## a single component. It does not participate in the routing protocol + ## and therefore will not coorperate with other routers. + ## interior - Interior router. The router operates in cooreration with other + ## interior routers in an interconnected network. + ## edge - Edge router. The router operates with an uplink into an interior + ## router network. Edge routers are typically used as connection concentrators + ## or as security firewalls for access into the interior network. ## - hello-max-age: 3 + mode: standalone ## - ## ra-interval - The interval, in seconds, between RA (Router - ## Advertisement) messages sent by this router. This interval should - ## be considerably longer than the hello interval because RA messages - ## propagate across the area. A good value is 30 seconds. + ## For Interior router mode only. ## - ra-interval: 30 - - ## - ## remote-ls-max-age - The maximum age, in seconds, of a remote - ## link-state record. Any such records that are not updated and time - ## out shall be removed from the set and the topology shall be - ## recomputed. A good value is 60 seconds. - ## - remote-ls-max-age: 60 - - ## - ## mobile-addr-max-age - The maximum age, in seconds, of a remote - ## mobile address record. Any record that exceeds this age shall be - ## removed from the routing table. A good value is 60 seconds. + ## area and router-id - Each router is assigned an area name and a + ## router-id that is unique within the area. ## - mobile-addr-max-age: 60 + area: all + router-id: Router.A } diff --git a/qpid/extras/dispatch/include/qpid/dispatch.h b/qpid/extras/dispatch/include/qpid/dispatch.h index 72b3456099..6b3d6a963f 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch.h +++ b/qpid/extras/dispatch/include/qpid/dispatch.h @@ -20,6 +20,7 @@ */ #include <qpid/dispatch/alloc.h> +#include <qpid/dispatch/bitmask.h> #include <qpid/dispatch/buffer.h> #include <qpid/dispatch/ctools.h> #include <qpid/dispatch/hash.h> diff --git a/qpid/extras/dispatch/include/qpid/dispatch/amqp.h b/qpid/extras/dispatch/include/qpid/dispatch/amqp.h index 9babeb930d..c27eec6589 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/amqp.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/amqp.h @@ -76,5 +76,23 @@ #define DX_AMQP_ARRAY8 0xe0 #define DX_AMQP_ARRAY32 0xf0 +/** + * Delivery Annotation Headers + */ +const char * const DX_DA_INGRESS; // Ingress Router +const char * const DX_DA_TRACE; // Trace +const char * const DX_DA_TO; // To-Override + +/** + * Link Terminus Capabilities + */ +const char * const DX_CAPABILITY_ROUTER; + +/** + * Miscellaneous Strings + */ +const char * const DX_INTERNODE_LINK_NAME_1; +const char * const DX_INTERNODE_LINK_NAME_2; + #endif diff --git a/qpid/extras/dispatch/include/qpid/dispatch/bitmask.h b/qpid/extras/dispatch/include/qpid/dispatch/bitmask.h new file mode 100644 index 0000000000..fe436bc19b --- /dev/null +++ b/qpid/extras/dispatch/include/qpid/dispatch/bitmask.h @@ -0,0 +1,37 @@ +#ifndef __dispatch_bitmask_h__ +#define __dispatch_bitmask_h__ 1 +/* + * 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. + */ + +typedef struct dx_bitmask_t dx_bitmask_t; + +int dx_bitmask_width(); +dx_bitmask_t *dx_bitmask(int initial); +void dx_bitmask_free(dx_bitmask_t *b); +void dx_bitmask_set_all(dx_bitmask_t *b); +void dx_bitmask_clear_all(dx_bitmask_t *b); +void dx_bitmask_set_bit(dx_bitmask_t *b, int bitnum); +void dx_bitmask_clear_bit(dx_bitmask_t *b, int bitnum); +int dx_bitmask_value(dx_bitmask_t *b, int bitnum); +int dx_bitmask_first_set(dx_bitmask_t *b, int *bitnum); + + + +#endif + diff --git a/qpid/extras/dispatch/include/qpid/dispatch/buffer.h b/qpid/extras/dispatch/include/qpid/dispatch/buffer.h index 1c372b265d..fadded4d74 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/buffer.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/buffer.h @@ -36,12 +36,12 @@ void dx_buffer_set_size(size_t size); /** */ -dx_buffer_t *dx_allocate_buffer(void); +dx_buffer_t *dx_buffer(void); /** * @param buf A pointer to an allocated buffer */ -void dx_free_buffer(dx_buffer_t *buf); +void dx_buffer_free(dx_buffer_t *buf); /** * @param buf A pointer to an allocated buffer diff --git a/qpid/extras/dispatch/include/qpid/dispatch/compose.h b/qpid/extras/dispatch/include/qpid/dispatch/compose.h index 7d668f0ac0..d68f6be746 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/compose.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/compose.h @@ -167,7 +167,8 @@ void dx_compose_insert_string(dx_composed_field_t *field, const char *value); * Insert a utf8-encoded string into the field from an iterator * * @param field A field created by dx_compose. - * @param value A pointer to a null-terminated string. + * @param iter An iterator for a string value. The caller is responsible for freeing + * this iterator after the call is complete. */ void dx_compose_insert_string_iterator(dx_composed_field_t *field, dx_field_iterator_t *iter); diff --git a/qpid/extras/dispatch/include/qpid/dispatch/container.h b/qpid/extras/dispatch/include/qpid/dispatch/container.h index a306895b50..03fdd0c4cc 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/container.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/container.h @@ -152,8 +152,21 @@ dx_dist_mode_t dx_container_node_get_dist_modes(const dx_node_t *node); dx_lifetime_policy_t dx_container_node_get_life_policy(const dx_node_t *node); dx_link_t *dx_link(dx_node_t *node, dx_connection_t *conn, dx_direction_t dir, const char *name); + +/** + * Context associated with the link for storing link-specific state. + */ void dx_link_set_context(dx_link_t *link, void *link_context); void *dx_link_get_context(dx_link_t *link); + +/** + * Link context associated with the link's connection for storing state shared across + * all links in a connection. + */ +void dx_link_set_conn_context(dx_link_t *link, void *link_context); +void *dx_link_get_conn_context(dx_link_t *link); + +dx_connection_t *dx_link_connection(dx_link_t *link); pn_link_t *dx_link_pn(dx_link_t *link); pn_terminus_t *dx_link_source(dx_link_t *link); pn_terminus_t *dx_link_target(dx_link_t *link); diff --git a/qpid/extras/dispatch/include/qpid/dispatch/hash.h b/qpid/extras/dispatch/include/qpid/dispatch/hash.h index bfad142517..4f079f6993 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/hash.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/hash.h @@ -23,16 +23,23 @@ #include <qpid/dispatch/iterator.h> #include <qpid/dispatch/error.h> -typedef struct hash_t hash_t; +typedef struct dx_hash_t dx_hash_t; +typedef struct dx_hash_handle_t dx_hash_handle_t; -hash_t *hash(int bucket_exponent, int batch_size, int value_is_const); -void hash_free(hash_t *h); +dx_hash_t *dx_hash(int bucket_exponent, int batch_size, int value_is_const); +void dx_hash_free(dx_hash_t *h); + +size_t dx_hash_size(dx_hash_t *h); +dx_error_t dx_hash_insert(dx_hash_t *h, dx_field_iterator_t *key, void *val, dx_hash_handle_t **handle); +dx_error_t dx_hash_insert_const(dx_hash_t *h, dx_field_iterator_t *key, const void *val, dx_hash_handle_t **handle); +dx_error_t dx_hash_retrieve(dx_hash_t *h, dx_field_iterator_t *key, void **val); +dx_error_t dx_hash_retrieve_const(dx_hash_t *h, dx_field_iterator_t *key, const void **val); +dx_error_t dx_hash_remove(dx_hash_t *h, dx_field_iterator_t *key); + +void dx_hash_handle_free(dx_hash_handle_t *handle); +const unsigned char *dx_hash_key_by_handle(const dx_hash_handle_t *handle); +dx_error_t dx_hash_remove_by_handle(dx_hash_t *h, dx_hash_handle_t *handle); +dx_error_t dx_hash_remove_by_handle2(dx_hash_t *h, dx_hash_handle_t *handle, unsigned char **key); -size_t hash_size(hash_t *h); -dx_error_t hash_insert(hash_t *h, dx_field_iterator_t *key, void *val); -dx_error_t hash_insert_const(hash_t *h, dx_field_iterator_t *key, const void *val); -dx_error_t hash_retrieve(hash_t *h, dx_field_iterator_t *key, void **val); -dx_error_t hash_retrieve_const(hash_t *h, dx_field_iterator_t *key, const void **val); -dx_error_t hash_remove(hash_t *h, dx_field_iterator_t *key); #endif diff --git a/qpid/extras/dispatch/include/qpid/dispatch/message.h b/qpid/extras/dispatch/include/qpid/dispatch/message.h index 34a7933f9f..8702e509b3 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/message.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/message.h @@ -96,7 +96,7 @@ typedef enum { * @return A pointer to a dx_message_t that is the sole reference to a newly allocated * message. */ -dx_message_t *dx_allocate_message(void); +dx_message_t *dx_message(void); /** * Free a message reference. If this is the last reference to the message, free the @@ -104,7 +104,7 @@ dx_message_t *dx_allocate_message(void); * * @param msg A pointer to a dx_message_t that is no longer needed. */ -void dx_free_message(dx_message_t *msg); +void dx_message_free(dx_message_t *msg); /** * Make a new reference to an existing message. diff --git a/qpid/extras/dispatch/include/qpid/dispatch/python_embedded.h b/qpid/extras/dispatch/include/qpid/dispatch/python_embedded.h index 644885bbd7..aad0453c24 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/python_embedded.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/python_embedded.h @@ -71,4 +71,11 @@ void dx_py_to_composed(PyObject *value, dx_composed_field_t *field); */ PyObject *dx_field_to_py(dx_parsed_field_t *field); +/** + * These are temporary and will eventually be replaced by having an internal python + * work queue that feeds a dedicated embedded-python thread. + */ +void dx_python_lock(); +void dx_python_unlock(); + #endif diff --git a/qpid/extras/dispatch/include/qpid/dispatch/router.h b/qpid/extras/dispatch/include/qpid/dispatch/router.h index c900d93615..1dadc8d119 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/router.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/router.h @@ -27,7 +27,7 @@ typedef struct dx_address_t dx_address_t; -typedef void (*dx_router_message_cb)(void *context, dx_message_t *msg); +typedef void (*dx_router_message_cb)(void *context, dx_message_t *msg, int link_id); const char *dx_router_id(const dx_dispatch_t *dx); diff --git a/qpid/extras/dispatch/include/qpid/dispatch/server.h b/qpid/extras/dispatch/include/qpid/dispatch/server.h index 7b2d4a2432..e50f9ac75b 100644 --- a/qpid/extras/dispatch/include/qpid/dispatch/server.h +++ b/qpid/extras/dispatch/include/qpid/dispatch/server.h @@ -186,7 +186,7 @@ typedef struct dx_connector_t dx_connector_t; typedef struct dx_connection_t dx_connection_t; /** - * Event type for the connection callback. + * \brief Event type for the connection callback. */ typedef enum { /// The connection just opened via a listener (inbound). @@ -204,79 +204,6 @@ typedef enum { /** - * \brief Connection Event Handler - * - * Callback invoked when processing is needed on a proton connection. This - * callback shall be invoked on one of the server's worker threads. The - * server guarantees that no two threads shall be allowed to process a single - * connection concurrently. The implementation of this handler may assume - * that it has exclusive access to the connection and its subservient - * components (sessions, links, deliveries, etc.). - * - * @param handler_context The handler context supplied in dx_server_set_conn_handler. - * @param conn_context The handler context supplied in dx_server_{connect,listen}. - * @param event The event/reason for the invocation of the handler. - * @param conn The connection that requires processing by the handler. - * @return A value greater than zero if the handler did any proton processing for - * the connection. If no work was done, zero is returned. - */ -typedef int (*dx_conn_handler_cb_t)(void *handler_context, void* conn_context, dx_conn_event_t event, dx_connection_t *conn); - - -/** - * \brief Set the connection event handler callback. - * - * Set the connection handler callback for the server. This callback is - * mandatory and must be set prior to the invocation of dx_server_run. - * - * @param dx The dispatch handle returned by dx_dispatch. - * @param conn_hander The handler for processing connection-related events. - */ -void dx_server_set_conn_handler(dx_dispatch_t *dx, dx_conn_handler_cb_t conn_handler, void *handler_context); - - -/** - * \brief Set the user context for a connection. - * - * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN - * @param context User context to be stored with the connection. - */ -void dx_connection_set_context(dx_connection_t *conn, void *context); - - -/** - * \brief Get the user context from a connection. - * - * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN - * @return The user context stored with the connection. - */ -void *dx_connection_get_context(dx_connection_t *conn); - - -/** - * \brief Activate a connection for output. - * - * This function is used to request that the server activate the indicated - * connection. It is assumed that the connection is one that the caller does - * not have permission to access (i.e. it may be owned by another thread - * currently). An activated connection will, when writable, appear in the - * internal work list and be invoked for processing by a worker thread. - * - * @param conn The connection over which the application wishes to send data - */ -void dx_server_activate(dx_connection_t *conn); - - -/** - * \brief Get the wrapped proton-engine connection object. - * - * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN - * @return The proton connection object. - */ -pn_connection_t *dx_connection_pn(dx_connection_t *conn); - - -/** * \brief Configuration block for a connector or a listener. */ typedef struct dx_server_config_t { @@ -367,10 +294,116 @@ typedef struct dx_server_config_t { * meaningful for outgoing (connector) connections only. */ int allow_redirect; + + /** + * The specified role of the connection. This can be used to control the behavior and + * capabilities of the connections. + */ + const char *role; } dx_server_config_t; /** + * \brief Connection Event Handler + * + * Callback invoked when processing is needed on a proton connection. This + * callback shall be invoked on one of the server's worker threads. The + * server guarantees that no two threads shall be allowed to process a single + * connection concurrently. The implementation of this handler may assume + * that it has exclusive access to the connection and its subservient + * components (sessions, links, deliveries, etc.). + * + * @param handler_context The handler context supplied in dx_server_set_conn_handler. + * @param conn_context The handler context supplied in dx_server_{connect,listen}. + * @param event The event/reason for the invocation of the handler. + * @param conn The connection that requires processing by the handler. + * @return A value greater than zero if the handler did any proton processing for + * the connection. If no work was done, zero is returned. + */ +typedef int (*dx_conn_handler_cb_t)(void *handler_context, void* conn_context, dx_conn_event_t event, dx_connection_t *conn); + + +/** + * \brief Set the connection event handler callback. + * + * Set the connection handler callback for the server. This callback is + * mandatory and must be set prior to the invocation of dx_server_run. + * + * @param dx The dispatch handle returned by dx_dispatch. + * @param conn_hander The handler for processing connection-related events. + */ +void dx_server_set_conn_handler(dx_dispatch_t *dx, dx_conn_handler_cb_t conn_handler, void *handler_context); + + +/** + * \brief Set the user context for a connection. + * + * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN + * @param context User context to be stored with the connection. + */ +void dx_connection_set_context(dx_connection_t *conn, void *context); + + +/** + * \brief Get the user context from a connection. + * + * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN + * @return The user context stored with the connection. + */ +void *dx_connection_get_context(dx_connection_t *conn); + + +/** + * \brief Set the link context for a connection. + * + * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN + * @param context Link context to be stored with the connection. + */ +void dx_connection_set_link_context(dx_connection_t *conn, void *context); + + +/** + * \brief Get the link context from a connection. + * + * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN + * @return The link context stored with the connection. + */ +void *dx_connection_get_link_context(dx_connection_t *conn); + + +/** + * \brief Activate a connection for output. + * + * This function is used to request that the server activate the indicated + * connection. It is assumed that the connection is one that the caller does + * not have permission to access (i.e. it may be owned by another thread + * currently). An activated connection will, when writable, appear in the + * internal work list and be invoked for processing by a worker thread. + * + * @param conn The connection over which the application wishes to send data + */ +void dx_server_activate(dx_connection_t *conn); + + +/** + * \brief Get the wrapped proton-engine connection object. + * + * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN + * @return The proton connection object. + */ +pn_connection_t *dx_connection_pn(dx_connection_t *conn); + + +/** + * \brief Get the configuration that was used in the setup of this connection. + * + * @param conn Connection object supplied in DX_CONN_EVENT_{LISTENER,CONNETOR}_OPEN + * @return A pointer to the server configuration used in the establishment of this connection. + */ +const dx_server_config_t *dx_connection_config(const dx_connection_t *conn); + + +/** * \brief Create a listener for incoming connections. * * @param dx The dispatch handle returned by dx_dispatch. diff --git a/qpid/extras/dispatch/python/qpid/dispatch/config/schema.py b/qpid/extras/dispatch/python/qpid/dispatch/config/schema.py index 545139f0df..7b00000c99 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/config/schema.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/config/schema.py @@ -57,6 +57,7 @@ config_schema = { 'addr' : (str, 0, "M"), 'port' : (str, 1, "M"), 'label' : (str, None, "", None), + 'role' : (str, None, "", 'normal'), 'sasl-mechanisms' : (str, None, "M"), 'ssl-profile' : (str, None, "E", None), 'require-peer-auth' : (bool, None, "", True), @@ -66,11 +67,13 @@ config_schema = { 'addr' : (str, 0, "M"), 'port' : (str, 1, "M"), 'label' : (str, None, "", None), + 'role' : (str, None, "", 'normal'), 'sasl-mechanisms' : (str, None, "M"), 'ssl-profile' : (str, None, "E", None), 'allow-redirect' : (bool, None, "", True) }), 'router' : (True, { + 'mode' : (str, None, "", 'standalone'), 'router-id' : (str, None, "M"), 'area' : (str, None, "", None), 'hello-interval' : (int, None, "", 1), diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/adapter.py b/qpid/extras/dispatch/python/qpid/dispatch/router/adapter.py deleted file mode 100644 index 7f7f6c9e8e..0000000000 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/adapter.py +++ /dev/null @@ -1,99 +0,0 @@ -# -# 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. -# - -try: - from dispatch import * -except ImportError: - from ..stubs import * - -ENTRY_OLD = 1 -ENTRY_CURRENT = 2 -ENTRY_NEW = 3 - -class AdapterEngine(object): - """ - This module is responsible for managing the Adapter's key bindings (list of address-subject:next-hop). - Key binding lists are kept in disjoint key-classes that can come from different parts of the router - (i.e. topological keys for inter-router communication and mobile keys for end users). - - For each key-class, a mirror copy of what the adapter has is kept internally. This allows changes to the - routing tables to be efficiently communicated to the adapter in the form of table deltas. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.key_classes = {} # map [key_class] => (addr-key, next-hop) - - - def tick(self, now): - """ - There is no periodic processing needed for this module. - """ - pass - - - def remote_routes_changed(self, key_class, new_table): - old_table = [] - if key_class in self.key_classes: - old_table = self.key_classes[key_class] - - # flag all of the old entries - old_flags = {} - for a,b in old_table: - old_flags[(a,b)] = ENTRY_OLD - - # flag the new entries - new_flags = {} - for a,b in new_table: - new_flags[(a,b)] = ENTRY_NEW - - # calculate the differences from old to new - for a,b in new_table: - if old_table.count((a,b)) > 0: - old_flags[(a,b)] = ENTRY_CURRENT - new_flags[(a,b)] = ENTRY_CURRENT - - # make to_add and to_delete lists - to_add = [] - to_delete = [] - for (a,b),f in old_flags.items(): - if f == ENTRY_OLD: - to_delete.append((a,b)) - for (a,b),f in new_flags.items(): - if f == ENTRY_NEW: - to_add.append((a,b)) - - # set the routing table to the new contents - self.key_classes[key_class] = new_table - - # update the adapter's routing tables - # Note: Do deletions before adds to avoid overlapping routes that may cause - # messages to be duplicated. It's better to have gaps in the routing - # tables momentarily because unroutable messages are stored for retry. - for a,b in to_delete: - self.container.router_adapter.remote_unbind(a, b) - for a,b in to_add: - self.container.router_adapter.remote_bind(a, b) - - self.container.log(LOG_INFO, "New Routing Table (class=%s):" % key_class) - for a,b in new_table: - self.container.log(LOG_INFO, " %s => %s" % (a, b)) - - diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/binding.py b/qpid/extras/dispatch/python/qpid/dispatch/router/binding.py deleted file mode 100644 index a37b585e3e..0000000000 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/binding.py +++ /dev/null @@ -1,133 +0,0 @@ -# -# 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. -# - -try: - from dispatch import * -except ImportError: - from ..stubs import * - - -class BindingEngine(object): - """ - This module is responsible for responding to two different events: - 1) The learning of new remote mobile addresses - 2) The change of topology (i.e. different next-hops for remote routers) - When these occur, this module converts the mobile routing table (address => router) - to a next-hop routing table (address => next-hop), compresses the keys in case there - are wild-card overlaps, and notifies outbound of changes in the "mobile-key" address class. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.current_keys = {} - - - def tick(self, now): - pass - - - def mobile_keys_changed(self, keys): - self.current_keys = keys - next_hop_keys = self._convert_ids_to_next_hops(keys) - routing_table = self._compress_keys(next_hop_keys) - self.container.remote_routes_changed('mobile-key', routing_table) - - - def next_hops_changed(self): - next_hop_keys = self._convert_ids_to_next_hops(self.current_keys) - routing_table = self._compress_keys(next_hop_keys) - self.container.remote_routes_changed('mobile-key', routing_table) - - - def _convert_ids_to_next_hops(self, keys): - next_hops = self.container.get_next_hops() - new_keys = {} - for _id, value in keys.items(): - if _id in next_hops: - next_hop = next_hops[_id] - if next_hop not in new_keys: - new_keys[next_hop] = [] - new_keys[next_hop].extend(value) - return new_keys - - - def _compress_keys(self, keys): - trees = {} - for _id, key_list in keys.items(): - trees[_id] = TopicElementList() - for key in key_list: - trees[_id].add_key(key) - routing_table = [] - for _id, tree in trees.items(): - tree_keys = tree.get_list() - for tk in tree_keys: - routing_table.append((tk, _id)) - return routing_table - - -class TopicElementList(object): - """ - """ - def __init__(self): - self.elements = {} # map text => (terminal, sub-list) - - def __repr__(self): - return "%r" % self.elements - - def add_key(self, key): - self.add_tokens(key.split('.')) - - def add_tokens(self, tokens): - first = tokens.pop(0) - terminal = len(tokens) == 0 - - if terminal and first == '#': - ## Optimization #1A (A.B.C.D followed by A.B.#) - self.elements = {'#':(True, TopicElementList())} - return - - if '#' in self.elements: - _t,_el = self.elements['#'] - if _t: - ## Optimization #1B (A.B.# followed by A.B.C.D) - return - - if first not in self.elements: - self.elements[first] = (terminal, TopicElementList()) - else: - _t,_el = self.elements[first] - if terminal and not _t: - self.elements[first] = (terminal, _el) - - if not terminal: - _t,_el = self.elements[first] - _el.add_tokens(tokens) - - def get_list(self): - keys = [] - for token, (_t,_el) in self.elements.items(): - if _t: keys.append(token) - _el.build_list(token, keys) - return keys - - def build_list(self, prefix, keys): - for token, (_t,_el) in self.elements.items(): - if _t: keys.append("%s.%s" % (prefix, token)) - _el.build_list("%s.%s" % (prefix, token), keys) diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/configuration.py b/qpid/extras/dispatch/python/qpid/dispatch/router/configuration.py index f87d2ee7d2..e0fd060b82 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/configuration.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/configuration.py @@ -18,30 +18,30 @@ # class Configuration(object): - """ - This module manages and holds the configuration and tuning parameters for a router. - """ - def __init__(self, overrides={}): - ## - ## Load default values - ## - self.values = { 'hello_interval' : 1.0, - 'hello_max_age' : 3.0, - 'ra_interval' : 30.0, - 'remote_ls_max_age' : 60.0, - 'mobile_addr_max_age' : 60.0 } + """ + This module manages and holds the configuration and tuning parameters for a router. + """ + def __init__(self, overrides={}): + ## + ## Load default values + ## + self.values = { 'hello_interval' : 1.0, + 'hello_max_age' : 3.0, + 'ra_interval' : 30.0, + 'remote_ls_max_age' : 60.0, + 'mobile_addr_max_age' : 60.0 } - ## - ## Apply supplied overrides - ## - for k, v in overrides.items(): - self.values[k] = v + ## + ## Apply supplied overrides + ## + for k, v in overrides.items(): + self.values[k] = v - def __getattr__(self, key): - if key in self.values: - return self.values[key] - raise KeyError + def __getattr__(self, key): + if key in self.values: + return self.values[key] + raise KeyError - def __repr__(self): - return "%r" % self.values + def __repr__(self): + return "%r" % self.values diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/data.py b/qpid/extras/dispatch/python/qpid/dispatch/router/data.py index 89e347a29e..812369768f 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/data.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/data.py @@ -19,257 +19,257 @@ try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * def getMandatory(data, key, cls=None): - """ - Get the value mapped to the requested key. If it's not present, raise an exception. - """ - if key in data: - value = data[key] - if cls and value.__class__ != cls: - raise Exception("Protocol field has wrong data type: '%s' type=%r expected=%r" % (key, value.__class__, cls)) - return value - raise Exception("Mandatory protocol field missing: '%s'" % key) + """ + Get the value mapped to the requested key. If it's not present, raise an exception. + """ + if key in data: + value = data[key] + if cls and value.__class__ != cls: + raise Exception("Protocol field has wrong data type: '%s' type=%r expected=%r" % (key, value.__class__, cls)) + return value + raise Exception("Mandatory protocol field missing: '%s'" % key) def getOptional(data, key, default=None, cls=None): - """ - Get the value mapped to the requested key. If it's not present, return the default value. - """ - if key in data: - value = data[key] - if cls and value.__class__ != cls: - raise Exception("Protocol field has wrong data type: '%s' type=%r expected=%r" % (key, value.__class__, cls)) - return value - return default + """ + Get the value mapped to the requested key. If it's not present, return the default value. + """ + if key in data: + value = data[key] + if cls and value.__class__ != cls: + raise Exception("Protocol field has wrong data type: '%s' type=%r expected=%r" % (key, value.__class__, cls)) + return value + return default class LinkState(object): - """ - The link-state of a single router. The link state consists of a list of neighbor routers reachable from - the reporting router. The link-state-sequence number is incremented each time the link state changes. - """ - def __init__(self, body, _id=None, _area=None, _ls_seq=None, _peers=None): - self.last_seen = 0 - if body: - self.id = getMandatory(body, 'id', str) - self.area = getMandatory(body, 'area', str) - self.ls_seq = getMandatory(body, 'ls_seq', long) - self.peers = getMandatory(body, 'peers', list) - else: - self.id = _id - self.area = _area - self.ls_seq = long(_ls_seq) - self.peers = _peers - - def __repr__(self): - return "LS(id=%s area=%s ls_seq=%d peers=%r)" % (self.id, self.area, self.ls_seq, self.peers) - - def to_dict(self): - return {'id' : self.id, - 'area' : self.area, - 'ls_seq' : self.ls_seq, - 'peers' : self.peers} - - def add_peer(self, _id): - if self.peers.count(_id) == 0: - self.peers.append(_id) - return True - return False - - def del_peer(self, _id): - if self.peers.count(_id) > 0: - self.peers.remove(_id) - return True - return False - - def bump_sequence(self): - self.ls_seq += 1 + """ + The link-state of a single router. The link state consists of a list of neighbor routers reachable from + the reporting router. The link-state-sequence number is incremented each time the link state changes. + """ + def __init__(self, body, _id=None, _area=None, _ls_seq=None, _peers=None): + self.last_seen = 0 + if body: + self.id = getMandatory(body, 'id', str) + self.area = getMandatory(body, 'area', str) + self.ls_seq = getMandatory(body, 'ls_seq', long) + self.peers = getMandatory(body, 'peers', list) + else: + self.id = _id + self.area = _area + self.ls_seq = long(_ls_seq) + self.peers = _peers + + def __repr__(self): + return "LS(id=%s area=%s ls_seq=%d peers=%r)" % (self.id, self.area, self.ls_seq, self.peers) + + def to_dict(self): + return {'id' : self.id, + 'area' : self.area, + 'ls_seq' : self.ls_seq, + 'peers' : self.peers} + + def add_peer(self, _id): + if self.peers.count(_id) == 0: + self.peers.append(_id) + return True + return False + + def del_peer(self, _id): + if self.peers.count(_id) > 0: + self.peers.remove(_id) + return True + return False + + def bump_sequence(self): + self.ls_seq += 1 class MessageHELLO(object): - """ - HELLO Message - scope: neighbors only - HELLO messages travel at most one hop - This message is used by directly connected routers to determine with whom they have - bidirectional connectivity. - """ - def __init__(self, body, _id=None, _area=None, _seen_peers=None): - if body: - self.id = getMandatory(body, 'id', str) - self.area = getMandatory(body, 'area', str) - self.seen_peers = getMandatory(body, 'seen', list) - else: - self.id = _id - self.area = _area - self.seen_peers = _seen_peers - - def __repr__(self): - return "HELLO(id=%s area=%s seen=%r)" % (self.id, self.area, self.seen_peers) - - def get_opcode(self): - return 'HELLO' - - def to_dict(self): - return {'id' : self.id, - 'area' : self.area, - 'seen' : self.seen_peers} - - def is_seen(self, _id): - return self.seen_peers.count(_id) > 0 + """ + HELLO Message + scope: neighbors only - HELLO messages travel at most one hop + This message is used by directly connected routers to determine with whom they have + bidirectional connectivity. + """ + def __init__(self, body, _id=None, _area=None, _seen_peers=None): + if body: + self.id = getMandatory(body, 'id', str) + self.area = getMandatory(body, 'area', str) + self.seen_peers = getMandatory(body, 'seen', list) + else: + self.id = _id + self.area = _area + self.seen_peers = _seen_peers + + def __repr__(self): + return "HELLO(id=%s area=%s seen=%r)" % (self.id, self.area, self.seen_peers) + + def get_opcode(self): + return 'HELLO' + + def to_dict(self): + return {'id' : self.id, + 'area' : self.area, + 'seen' : self.seen_peers} + + def is_seen(self, _id): + return self.seen_peers.count(_id) > 0 class MessageRA(object): - """ - Router Advertisement (RA) Message - scope: all routers in the area and all designated routers - This message is sent periodically to indicate the originating router's sequence numbers - for link-state and mobile-address-state. - """ - def __init__(self, body, _id=None, _area=None, _ls_seq=None, _mobile_seq=None): - if body: - self.id = getMandatory(body, 'id', str) - self.area = getMandatory(body, 'area', str) - self.ls_seq = getMandatory(body, 'ls_seq', long) - self.mobile_seq = getMandatory(body, 'mobile_seq', long) - else: - self.id = _id - self.area = _area - self.ls_seq = long(_ls_seq) - self.mobile_seq = long(_mobile_seq) - - def get_opcode(self): - return 'RA' - - def __repr__(self): - return "RA(id=%s area=%s ls_seq=%d mobile_seq=%d)" % \ - (self.id, self.area, self.ls_seq, self.mobile_seq) - - def to_dict(self): - return {'id' : self.id, - 'area' : self.area, - 'ls_seq' : self.ls_seq, - 'mobile_seq' : self.mobile_seq} + """ + Router Advertisement (RA) Message + scope: all routers in the area and all designated routers + This message is sent periodically to indicate the originating router's sequence numbers + for link-state and mobile-address-state. + """ + def __init__(self, body, _id=None, _area=None, _ls_seq=None, _mobile_seq=None): + if body: + self.id = getMandatory(body, 'id', str) + self.area = getMandatory(body, 'area', str) + self.ls_seq = getMandatory(body, 'ls_seq', long) + self.mobile_seq = getMandatory(body, 'mobile_seq', long) + else: + self.id = _id + self.area = _area + self.ls_seq = long(_ls_seq) + self.mobile_seq = long(_mobile_seq) + + def get_opcode(self): + return 'RA' + + def __repr__(self): + return "RA(id=%s area=%s ls_seq=%d mobile_seq=%d)" % \ + (self.id, self.area, self.ls_seq, self.mobile_seq) + + def to_dict(self): + return {'id' : self.id, + 'area' : self.area, + 'ls_seq' : self.ls_seq, + 'mobile_seq' : self.mobile_seq} class MessageLSU(object): - """ - """ - def __init__(self, body, _id=None, _area=None, _ls_seq=None, _ls=None): - if body: - self.id = getMandatory(body, 'id', str) - self.area = getMandatory(body, 'area', str) - self.ls_seq = getMandatory(body, 'ls_seq', long) - self.ls = LinkState(getMandatory(body, 'ls', dict)) - else: - self.id = _id - self.area = _area - self.ls_seq = long(_ls_seq) - self.ls = _ls - - def get_opcode(self): - return 'LSU' - - def __repr__(self): - return "LSU(id=%s area=%s ls_seq=%d ls=%r)" % \ - (self.id, self.area, self.ls_seq, self.ls) - - def to_dict(self): - return {'id' : self.id, - 'area' : self.area, - 'ls_seq' : self.ls_seq, - 'ls' : self.ls.to_dict()} + """ + """ + def __init__(self, body, _id=None, _area=None, _ls_seq=None, _ls=None): + if body: + self.id = getMandatory(body, 'id', str) + self.area = getMandatory(body, 'area', str) + self.ls_seq = getMandatory(body, 'ls_seq', long) + self.ls = LinkState(getMandatory(body, 'ls', dict)) + else: + self.id = _id + self.area = _area + self.ls_seq = long(_ls_seq) + self.ls = _ls + + def get_opcode(self): + return 'LSU' + + def __repr__(self): + return "LSU(id=%s area=%s ls_seq=%d ls=%r)" % \ + (self.id, self.area, self.ls_seq, self.ls) + + def to_dict(self): + return {'id' : self.id, + 'area' : self.area, + 'ls_seq' : self.ls_seq, + 'ls' : self.ls.to_dict()} class MessageLSR(object): - """ - """ - def __init__(self, body, _id=None, _area=None): - if body: - self.id = getMandatory(body, 'id', str) - self.area = getMandatory(body, 'area', str) - else: - self.id = _id - self.area = _area + """ + """ + def __init__(self, body, _id=None, _area=None): + if body: + self.id = getMandatory(body, 'id', str) + self.area = getMandatory(body, 'area', str) + else: + self.id = _id + self.area = _area - def get_opcode(self): - return 'LSR' + def get_opcode(self): + return 'LSR' - def __repr__(self): - return "LSR(id=%s area=%s)" % (self.id, self.area) + def __repr__(self): + return "LSR(id=%s area=%s)" % (self.id, self.area) - def to_dict(self): - return {'id' : self.id, - 'area' : self.area} + def to_dict(self): + return {'id' : self.id, + 'area' : self.area} class MessageMAU(object): - """ - """ - def __init__(self, body, _id=None, _area=None, _seq=None, _add_list=None, _del_list=None, _exist_list=None): - if body: - self.id = getMandatory(body, 'id', str) - self.area = getMandatory(body, 'area', str) - self.mobile_seq = getMandatory(body, 'mobile_seq', long) - self.add_list = getOptional(body, 'add', None, list) - self.del_list = getOptional(body, 'del', None, list) - self.exist_list = getOptional(body, 'exist', None, list) - else: - self.id = _id - self.area = _area - self.mobile_seq = long(_seq) - self.add_list = _add_list - self.del_list = _del_list - self.exist_list = _exist_list - - def get_opcode(self): - return 'MAU' - - def __repr__(self): - _add = '' - _del = '' - _exist = '' - if self.add_list: _add = ' add=%r' % self.add_list - if self.del_list: _del = ' del=%r' % self.del_list - if self.exist_list: _exist = ' exist=%r' % self.exist_list - return "MAU(id=%s area=%s mobile_seq=%d%s%s%s)" % \ - (self.id, self.area, self.mobile_seq, _add, _del, _exist) - - def to_dict(self): - body = { 'id' : self.id, - 'area' : self.area, - 'mobile_seq' : self.mobile_seq } - if self.add_list: body['add'] = self.add_list - if self.del_list: body['del'] = self.del_list - if self.exist_list: body['exist'] = self.exist_list - return body + """ + """ + def __init__(self, body, _id=None, _area=None, _seq=None, _add_list=None, _del_list=None, _exist_list=None): + if body: + self.id = getMandatory(body, 'id', str) + self.area = getMandatory(body, 'area', str) + self.mobile_seq = getMandatory(body, 'mobile_seq', long) + self.add_list = getOptional(body, 'add', None, list) + self.del_list = getOptional(body, 'del', None, list) + self.exist_list = getOptional(body, 'exist', None, list) + else: + self.id = _id + self.area = _area + self.mobile_seq = long(_seq) + self.add_list = _add_list + self.del_list = _del_list + self.exist_list = _exist_list + + def get_opcode(self): + return 'MAU' + + def __repr__(self): + _add = '' + _del = '' + _exist = '' + if self.add_list: _add = ' add=%r' % self.add_list + if self.del_list: _del = ' del=%r' % self.del_list + if self.exist_list: _exist = ' exist=%r' % self.exist_list + return "MAU(id=%s area=%s mobile_seq=%d%s%s%s)" % \ + (self.id, self.area, self.mobile_seq, _add, _del, _exist) + + def to_dict(self): + body = { 'id' : self.id, + 'area' : self.area, + 'mobile_seq' : self.mobile_seq } + if self.add_list: body['add'] = self.add_list + if self.del_list: body['del'] = self.del_list + if self.exist_list: body['exist'] = self.exist_list + return body class MessageMAR(object): - """ - """ - def __init__(self, body, _id=None, _area=None, _have_seq=None): - if body: - self.id = getMandatory(body, 'id', str) - self.area = getMandatory(body, 'area', str) - self.have_seq = getMandatory(body, 'have_seq', long) - else: - self.id = _id - self.area = _area - self.have_seq = long(_have_seq) - - def get_opcode(self): - return 'MAR' - - def __repr__(self): - return "MAR(id=%s area=%s have_seq=%d)" % (self.id, self.area, self.have_seq) - - def to_dict(self): - return {'id' : self.id, - 'area' : self.area, - 'have_seq' : self.have_seq} + """ + """ + def __init__(self, body, _id=None, _area=None, _have_seq=None): + if body: + self.id = getMandatory(body, 'id', str) + self.area = getMandatory(body, 'area', str) + self.have_seq = getMandatory(body, 'have_seq', long) + else: + self.id = _id + self.area = _area + self.have_seq = long(_have_seq) + + def get_opcode(self): + return 'MAR' + + def __repr__(self): + return "MAR(id=%s area=%s have_seq=%d)" % (self.id, self.area, self.have_seq) + + def to_dict(self): + return {'id' : self.id, + 'area' : self.area, + 'have_seq' : self.have_seq} diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/link.py b/qpid/extras/dispatch/python/qpid/dispatch/router/link.py index fb23177e2f..11307cd079 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/link.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/link.py @@ -21,123 +21,124 @@ from data import MessageRA, MessageLSU, MessageLSR from time import time try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * class LinkStateEngine(object): - """ - This module is responsible for running the Link State protocol and maintaining the set - of link states that are gathered from the domain. It notifies outbound when changes to - the link-state-collection are detected. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.ra_interval = self.container.config.ra_interval - self.remote_ls_max_age = self.container.config.remote_ls_max_age - self.last_ra_time = 0 - self.collection = {} - self.collection_changed = False - self.mobile_seq = 0 - self.needed_lsrs = {} - - - def tick(self, now): - self._expire_ls(now) - self._send_lsrs() - - if now - self.last_ra_time >= self.ra_interval: - self.last_ra_time = now - self._send_ra() - - if self.collection_changed: - self.collection_changed = False - self.container.log(LOG_INFO, "New Link-State Collection:") - for a,b in self.collection.items(): - self.container.log(LOG_INFO, " %s => %r" % (a, b.peers)) - self.container.ls_collection_changed(self.collection) - - - def handle_ra(self, msg, now): - if msg.id == self.id: - return - if msg.id in self.collection: - ls = self.collection[msg.id] - ls.last_seen = now - if ls.ls_seq < msg.ls_seq: - self.needed_lsrs[(msg.area, msg.id)] = None - else: - self.needed_lsrs[(msg.area, msg.id)] = None - - - def handle_lsu(self, msg, now): - if msg.id == self.id: - return - if msg.id in self.collection: - ls = self.collection[msg.id] - if ls.ls_seq < msg.ls_seq: - ls = msg.ls - self.collection[msg.id] = ls + """ + This module is responsible for running the Link State protocol and maintaining the set + of link states that are gathered from the domain. It notifies outbound when changes to + the link-state-collection are detected. + """ + def __init__(self, container): + self.container = container + self.id = self.container.id + self.area = self.container.area + self.ra_interval = self.container.config.ra_interval + self.remote_ls_max_age = self.container.config.remote_ls_max_age + self.last_ra_time = 0 + self.collection = {} + self.collection_changed = False + self.mobile_seq = 0 + self.needed_lsrs = {} + + + def tick(self, now): + self._expire_ls(now) + self._send_lsrs() + + if now - self.last_ra_time >= self.ra_interval: + self.last_ra_time = now + self._send_ra() + + if self.collection_changed: + self.collection_changed = False + self.container.log(LOG_INFO, "New Link-State Collection:") + for a,b in self.collection.items(): + self.container.log(LOG_INFO, " %s => %r" % (a, b.peers)) + self.container.ls_collection_changed(self.collection) + + + def handle_ra(self, msg, now): + if msg.id == self.id: + return + if msg.id in self.collection: + ls = self.collection[msg.id] + ls.last_seen = now + if ls.ls_seq < msg.ls_seq: + self.needed_lsrs[(msg.area, msg.id)] = None + else: + self.needed_lsrs[(msg.area, msg.id)] = None + + + def handle_lsu(self, msg, now): + if msg.id == self.id: + return + if msg.id in self.collection: + ls = self.collection[msg.id] + if ls.ls_seq < msg.ls_seq: + ls = msg.ls + self.collection[msg.id] = ls + self.collection_changed = True + ls.last_seen = now + else: + ls = msg.ls + self.collection[msg.id] = ls + self.collection_changed = True + ls.last_seen = now + self.container.new_node(msg.id) + self.container.log(LOG_INFO, "Learned link-state from new router: %s" % msg.id) + # Schedule LSRs for any routers referenced in this LS that we don't know about + for _id in msg.ls.peers: + if _id not in self.collection: + self.container.new_node(_id) + self.needed_lsrs[(msg.area, _id)] = None + + + def handle_lsr(self, msg, now): + if msg.id == self.id: + return + if self.id not in self.collection: + return + my_ls = self.collection[self.id] + self.container.send('amqp:/_topo/%s/%s/qdxrouter' % (msg.area, msg.id), MessageLSU(None, self.id, self.area, my_ls.ls_seq, my_ls)) + + + def new_local_link_state(self, link_state): + self.collection[self.id] = link_state self.collection_changed = True - ls.last_seen = now - else: - ls = msg.ls - self.collection[msg.id] = ls - self.collection_changed = True - ls.last_seen = now - self.container.new_node(msg.id) - self.container.log(LOG_INFO, "Learned link-state from new router: %s" % msg.id) - # Schedule LSRs for any routers referenced in this LS that we don't know about - for _id in msg.ls.peers: - if _id not in self.collection: - self.needed_lsrs[(msg.area, _id)] = None - - - def handle_lsr(self, msg, now): - if msg.id == self.id: - return - if self.id not in self.collection: - return - my_ls = self.collection[self.id] - self.container.send('_topo/%s/%s' % (msg.area, msg.id), MessageLSU(None, self.id, self.area, my_ls.ls_seq, my_ls)) - - - def new_local_link_state(self, link_state): - self.collection[self.id] = link_state - self.collection_changed = True - self._send_ra() - - - def set_mobile_sequence(self, seq): - self.mobile_seq = seq - - - def get_collection(self): - return self.collection - - - def _expire_ls(self, now): - to_delete = [] - for key, ls in self.collection.items(): - if key != self.id and now - ls.last_seen > self.remote_ls_max_age: - to_delete.append(key) - for key in to_delete: - ls = self.collection.pop(key) - self.collection_changed = True - self.container.lost_node(key) - self.container.log(LOG_INFO, "Expired link-state from router: %s" % key) - - - def _send_lsrs(self): - for (_area, _id) in self.needed_lsrs.keys(): - self.container.send('_topo/%s/%s' % (_area, _id), MessageLSR(None, self.id, self.area)) - self.needed_lsrs = {} - - - def _send_ra(self): - ls_seq = 0 - if self.id in self.collection: - ls_seq = self.collection[self.id].ls_seq - self.container.send('_topo/%s/all' % self.area, MessageRA(None, self.id, self.area, ls_seq, self.mobile_seq)) + self._send_ra() + + + def set_mobile_sequence(self, seq): + self.mobile_seq = seq + + + def get_collection(self): + return self.collection + + + def _expire_ls(self, now): + to_delete = [] + for key, ls in self.collection.items(): + if key != self.id and now - ls.last_seen > self.remote_ls_max_age: + to_delete.append(key) + for key in to_delete: + ls = self.collection.pop(key) + self.collection_changed = True + self.container.lost_node(key) + self.container.log(LOG_INFO, "Expired link-state from router: %s" % key) + + + def _send_lsrs(self): + for (_area, _id) in self.needed_lsrs.keys(): + self.container.send('amqp:/_topo/%s/%s/qdxrouter' % (_area, _id), MessageLSR(None, self.id, self.area)) + self.needed_lsrs = {} + + + def _send_ra(self): + ls_seq = 0 + if self.id in self.collection: + ls_seq = self.collection[self.id].ls_seq + self.container.send('amqp:/_topo/%s/all/qdxrouter' % self.area, MessageRA(None, self.id, self.area, ls_seq, self.mobile_seq)) diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/mobile.py b/qpid/extras/dispatch/python/qpid/dispatch/router/mobile.py index ce80f38780..117cf98c22 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/mobile.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/mobile.py @@ -20,169 +20,170 @@ from data import MessageRA, MessageMAR, MessageMAU try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * class MobileAddressEngine(object): - """ - This module is responsible for maintaining an up-to-date list of mobile addresses in the domain. - It runs the Mobile-Address protocol and generates an un-optimized routing table for mobile addresses. - Note that this routing table maps from the mobile address to the remote router where that address - is directly bound. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.mobile_addr_max_age = self.container.config.mobile_addr_max_age - self.mobile_seq = 0 - self.local_keys = [] - self.added_keys = [] - self.deleted_keys = [] - self.remote_lists = {} # map router_id => (sequence, list of keys) - self.remote_last_seen = {} # map router_id => time of last seen advertizement/update - self.remote_changed = False - self.needed_mars = {} - - - def tick(self, now): - self._expire_remotes(now) - self._send_mars() - - ## - ## If local keys have changed, collect the changes and send a MAU with the diffs - ## Note: it is important that the differential-MAU be sent before a RA is sent - ## - if len(self.added_keys) > 0 or len(self.deleted_keys) > 0: - self.mobile_seq += 1 - self.container.send('_topo.%s.all' % self.area, - MessageMAU(None, self.id, self.area, self.mobile_seq, self.added_keys, self.deleted_keys)) - self.local_keys.extend(self.added_keys) - for key in self.deleted_keys: - self.local_keys.remove(key) - self.added_keys = [] - self.deleted_keys = [] - self.container.mobile_sequence_changed(self.mobile_seq) - - ## - ## If remotes have changed, start the process of updating local bindings - ## - if self.remote_changed: - self.remote_changed = False - self._update_remote_keys() - - - def add_local_address(self, key): """ + This module is responsible for maintaining an up-to-date list of mobile addresses in the domain. + It runs the Mobile-Address protocol and generates an un-optimized routing table for mobile addresses. + Note that this routing table maps from the mobile address to the remote router where that address + is directly bound. """ - if self.local_keys.count(key) == 0: - if self.added_keys.count(key) == 0: - self.added_keys.append(key) - else: - if self.deleted_keys.count(key) > 0: - self.deleted_keys.remove(key) - - - def del_local_address(self, key): - """ - """ - if self.local_keys.count(key) > 0: - if self.deleted_keys.count(key) == 0: - self.deleted_keys.append(key) - else: - if self.added_keys.count(key) > 0: - self.added_keys.remove(key) - - - def handle_ra(self, msg, now): - if msg.id == self.id: - return - - if msg.mobile_seq == 0: - return - - if msg.id in self.remote_lists: - _seq, _list = self.remote_lists[msg.id] - self.remote_last_seen[msg.id] = now - if _seq < msg.mobile_seq: - self.needed_mars[(msg.id, msg.area, _seq)] = None - else: - self.needed_mars[(msg.id, msg.area, 0)] = None - - - def handle_mau(self, msg, now): - ## - ## If the MAU is differential, we can only use it if its sequence is exactly one greater - ## than our stored sequence. If not, we will ignore the content and schedule a MAR. - ## - ## If the MAU is absolute, we can use it in all cases. - ## - if msg.id == self.id: - return - - if msg.exist_list: - ## - ## Absolute MAU - ## - if msg.id in self.remote_lists: - _seq, _list = self.remote_lists[msg.id] - if _seq >= msg.mobile_seq: # ignore duplicates - return - self.remote_lists[msg.id] = (msg.mobile_seq, msg.exist_list) - self.remote_last_seen[msg.id] = now - self.remote_changed = True - else: - ## - ## Differential MAU - ## - if msg.id in self.remote_lists: - _seq, _list = self.remote_lists[msg.id] - if _seq == msg.mobile_seq: # ignore duplicates - return - self.remote_last_seen[msg.id] = now - if _seq + 1 == msg.mobile_seq: - ## - ## This is one greater than our stored value, incorporate the deltas - ## - if msg.add_list and msg.add_list.__class__ == list: - _list.extend(msg.add_list) - if msg.del_list and msg.del_list.__class__ == list: - for key in msg.del_list: - _list.remove(key) - self.remote_lists[msg.id] = (msg.mobile_seq, _list) - self.remote_changed = True + def __init__(self, container, node_tracker): + self.container = container + self.node_tracker = node_tracker + self.id = self.container.id + self.area = self.container.area + self.mobile_addr_max_age = self.container.config.mobile_addr_max_age + self.mobile_seq = 0 + self.local_addrs = [] + self.added_addrs = [] + self.deleted_addrs = [] + self.remote_lists = {} # map router_id => (sequence, list of addrs) + self.remote_last_seen = {} # map router_id => time of last seen advertizement/update + self.needed_mars = {} + + + def tick(self, now): + self._expire_remotes(now) + self._send_mars() + + ## + ## If local addrs have changed, collect the changes and send a MAU with the diffs + ## Note: it is important that the differential-MAU be sent before a RA is sent + ## + if len(self.added_addrs) > 0 or len(self.deleted_addrs) > 0: + self.mobile_seq += 1 + self.container.send('amqp:/_topo/%s/all/qdxrouter' % self.area, + MessageMAU(None, self.id, self.area, self.mobile_seq, self.added_addrs, self.deleted_addrs)) + self.local_addrs.extend(self.added_addrs) + for addr in self.deleted_addrs: + self.local_addrs.remove(addr) + self.added_addrs = [] + self.deleted_addrs = [] + self.container.mobile_sequence_changed(self.mobile_seq) + + + def add_local_address(self, addr): + """ + """ + if self.local_addrs.count(addr) == 0: + if self.added_addrs.count(addr) == 0: + self.added_addrs.append(addr) else: - self.needed_mars[(msg.id, msg.area, _seq)] = None - else: - self.needed_mars[(msg.id, msg.area, 0)] = None - + if self.deleted_addrs.count(addr) > 0: + self.deleted_addrs.remove(addr) - def handle_mar(self, msg, now): - if msg.id == self.id: - return - if msg.have_seq < self.mobile_seq: - self.container.send('_topo.%s.%s' % (msg.area, msg.id), - MessageMAU(None, self.id, self.area, self.mobile_seq, None, None, self.local_keys)) - - def _update_remote_keys(self): - keys = {} - for _id,(seq,key_list) in self.remote_lists.items(): - keys[_id] = key_list - self.container.mobile_keys_changed(keys) + def del_local_address(self, addr): + """ + """ + if self.local_addrs.count(addr) > 0: + if self.deleted_addrs.count(addr) == 0: + self.deleted_addrs.append(addr) + else: + if self.added_addrs.count(addr) > 0: + self.added_addrs.remove(addr) - def _expire_remotes(self, now): - for _id, t in self.remote_last_seen.items(): - if now - t > self.mobile_addr_max_age: - self.remote_lists.pop(_id) - self.remote_last_seen.pop(_id) - self.remote_changed = True + def handle_ra(self, msg, now): + if msg.id == self.id: + return + if msg.mobile_seq == 0: + return - def _send_mars(self): - for _id, _area, _seq in self.needed_mars.keys(): - self.container.send('_topo.%s.%s' % (_area, _id), MessageMAR(None, self.id, self.area, _seq)) - self.needed_mars = {} + if msg.id in self.remote_lists: + _seq, _list = self.remote_lists[msg.id] + self.remote_last_seen[msg.id] = now + if _seq < msg.mobile_seq: + self.needed_mars[(msg.id, msg.area, _seq)] = None + else: + self.needed_mars[(msg.id, msg.area, 0)] = None + + + def handle_mau(self, msg, now): + ## + ## If the MAU is differential, we can only use it if its sequence is exactly one greater + ## than our stored sequence. If not, we will ignore the content and schedule a MAR. + ## + ## If the MAU is absolute, we can use it in all cases. + ## + if msg.id == self.id: + return + + if msg.exist_list: + ## + ## Absolute MAU + ## + if msg.id in self.remote_lists: + _seq, _list = self.remote_lists[msg.id] + if _seq >= msg.mobile_seq: # ignore duplicates + return + self.remote_lists[msg.id] = (msg.mobile_seq, msg.exist_list) + self.remote_last_seen[msg.id] = now + (add_list, del_list) = self.node_tracker.overwrite_addresses(msg.id, msg.exist_list) + self._activate_remotes(msg.id, add_list, del_list) + else: + ## + ## Differential MAU + ## + if msg.id in self.remote_lists: + _seq, _list = self.remote_lists[msg.id] + if _seq == msg.mobile_seq: # ignore duplicates + return + self.remote_last_seen[msg.id] = now + if _seq + 1 == msg.mobile_seq: + ## + ## This is one greater than our stored value, incorporate the deltas + ## + if msg.add_list and msg.add_list.__class__ == list: + _list.extend(msg.add_list) + if msg.del_list and msg.del_list.__class__ == list: + for addr in msg.del_list: + _list.remove(addr) + self.remote_lists[msg.id] = (msg.mobile_seq, _list) + if msg.add_list: + self.node_tracker.add_addresses(msg.id, msg.add_list) + if msg.del_list: + self.node_tracker.del_addresses(msg.id, msg.del_list) + self._activate_remotes(msg.id, msg.add_list, msg.del_list) + else: + self.needed_mars[(msg.id, msg.area, _seq)] = None + else: + self.needed_mars[(msg.id, msg.area, 0)] = None + + + def handle_mar(self, msg, now): + if msg.id == self.id: + return + if msg.have_seq < self.mobile_seq: + self.container.send('amqp:/_topo/%s/%s/qdxrouter' % (msg.area, msg.id), + MessageMAU(None, self.id, self.area, self.mobile_seq, None, None, self.local_addrs)) + + + def _expire_remotes(self, now): + for _id, t in self.remote_last_seen.items(): + if now - t > self.mobile_addr_max_age: + self.remote_lists.pop(_id) + self.remote_last_seen.pop(_id) + self.remote_changed = True + + + def _send_mars(self): + for _id, _area, _seq in self.needed_mars.keys(): + self.container.send('amqp:/_topo/%s/%s/qdxrouter' % (_area, _id), MessageMAR(None, self.id, self.area, _seq)) + self.needed_mars = {} + + + def _activate_remotes(self, _id, added, deleted): + bit = self.node_tracker.maskbit_for_node(_id) + if added: + for a in added: + self.container.router_adapter.map_destination(a, bit) + if deleted: + for d in deleted: + self.container.router_adapter.unmap_destination(d, bit) diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/neighbor.py b/qpid/extras/dispatch/python/qpid/dispatch/router/neighbor.py index ea8bacd660..37b7888efe 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/neighbor.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/neighbor.py @@ -21,63 +21,63 @@ from data import LinkState, MessageHELLO from time import time try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * class NeighborEngine(object): - """ - This module is responsible for maintaining this router's link-state. It runs the HELLO protocol - with the router's neighbors and notifies outbound when the list of neighbors-in-good-standing (the - link-state) changes. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.last_hello_time = 0.0 - self.hello_interval = container.config.hello_interval - self.hello_max_age = container.config.hello_max_age - self.hellos = {} - self.link_state_changed = False - self.link_state = LinkState(None, self.id, self.area, 0, []) + """ + This module is responsible for maintaining this router's link-state. It runs the HELLO protocol + with the router's neighbors and notifies outbound when the list of neighbors-in-good-standing (the + link-state) changes. + """ + def __init__(self, container): + self.container = container + self.id = self.container.id + self.area = self.container.area + self.last_hello_time = 0.0 + self.hello_interval = container.config.hello_interval + self.hello_max_age = container.config.hello_max_age + self.hellos = {} + self.link_state_changed = False + self.link_state = LinkState(None, self.id, self.area, 0, []) - def tick(self, now): - self._expire_hellos(now) + def tick(self, now): + self._expire_hellos(now) - if now - self.last_hello_time >= self.hello_interval: - self.last_hello_time = now - self.container.send('_local/qdxrouter', MessageHELLO(None, self.id, self.area, self.hellos.keys())) + if now - self.last_hello_time >= self.hello_interval: + self.last_hello_time = now + self.container.send('amqp:/_local/qdxhello', MessageHELLO(None, self.id, self.area, self.hellos.keys())) - if self.link_state_changed: - self.link_state_changed = False - self.link_state.bump_sequence() - self.container.local_link_state_changed(self.link_state) + if self.link_state_changed: + self.link_state_changed = False + self.link_state.bump_sequence() + self.container.local_link_state_changed(self.link_state) - def handle_hello(self, msg, now): - if msg.id == self.id: - return - self.hellos[msg.id] = now - if msg.is_seen(self.id): - if self.link_state.add_peer(msg.id): - self.link_state_changed = True - self.container.new_neighbor(msg.id) - self.container.log(LOG_INFO, "New neighbor established: %s" % msg.id) - ## - ## TODO - Use this function to detect area boundaries - ## + def handle_hello(self, msg, now, link_id): + if msg.id == self.id: + return + self.hellos[msg.id] = now + if msg.is_seen(self.id): + if self.link_state.add_peer(msg.id): + self.link_state_changed = True + self.container.new_neighbor(msg.id, link_id) + self.container.log(LOG_INFO, "New neighbor established: %s on link: %d" % (msg.id, link_id)) + ## + ## TODO - Use this function to detect area boundaries + ## - def _expire_hellos(self, now): - to_delete = [] - for key, last_seen in self.hellos.items(): - if now - last_seen > self.hello_max_age: - to_delete.append(key) - for key in to_delete: - self.hellos.pop(key) - if self.link_state.del_peer(key): - self.link_state_changed = True - self.container.lost_neighbor(key) - self.container.log(LOG_INFO, "Neighbor lost: %s" % key) + def _expire_hellos(self, now): + to_delete = [] + for key, last_seen in self.hellos.items(): + if now - last_seen > self.hello_max_age: + to_delete.append(key) + for key in to_delete: + self.hellos.pop(key) + if self.link_state.del_peer(key): + self.link_state_changed = True + self.container.lost_neighbor(key) + self.container.log(LOG_INFO, "Neighbor lost: %s" % key) diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/node.py b/qpid/extras/dispatch/python/qpid/dispatch/router/node.py index c90f7f4232..ef697428da 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/node.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/node.py @@ -18,86 +18,157 @@ # try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * class NodeTracker(object): - """ - This module is responsible for tracking the set of router nodes that are known to this - router. It tracks whether they are neighbor or remote and whether they are reachable. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.nodes = {} # id => RemoteNode - - - def tick(self, now): - pass - - - def new_neighbor(self, node_id): - if node_id not in self.nodes: - self.nodes[node_id] = RemoteNode(node_id) - self.nodes[node_id].set_neighbor() - self._notify(self.nodes[node_id]) - - - def lost_neighbor(self, node_id): - node = self.nodes[node_id] - node.clear_neighbor() - self._notify(node) - if node.to_delete(): - self.nodes.pop(node_id) - - - def new_node(self, node_id): - if node_id not in self.nodes: - self.nodes[node_id] = RemoteNode(node_id) - self.nodes[node_id].set_remote() - self._notify(self.nodes[node_id]) - - - def lost_node(self, node_id): - node = self.nodes[node_id] - node.clear_remote() - self._notify(node) - if node.to_delete(): - self.nodes.pop(node_id) - - - def _notify(self, node): - if node.to_delete(): - self.container.adapter.node_updated("R%s" % node.id, 0, 0) - else: - is_neighbor = 0 - if node.neighbor: - is_neighbor = 1 - self.container.adapter.node_updated("R%s" % node.id, 1, is_neighbor) + """ + This module is responsible for tracking the set of router nodes that are known to this + router. It tracks whether they are neighbor or remote and whether they are reachable. + + This module is also responsible for assigning a unique mask bit value to each router. + The mask bit is used in the main router to represent sets of valid destinations for addresses. + """ + def __init__(self, container, max_routers): + self.container = container + self.max_routers = max_routers + self.nodes = {} # id => RemoteNode + self.maskbits = [] + self.next_maskbit = 1 # Reserve bit '0' to represent this router + for i in range(max_routers): + self.maskbits.append(None) + self.maskbits[0] = True + + + def tick(self, now): + pass + + + def new_neighbor(self, node_id, link_maskbit): + """ + A node, designated by node_id, has been discovered as a neighbor over a link with + a maskbit of link_maskbit. + """ + if node_id in self.nodes: + node = self.nodes[node_id] + if node.neighbor: + return + self.container.del_remote_router(node.maskbit) + node.neighbor = True + else: + node = RemoteNode(node_id, self._allocate_maskbit(), True) + self.nodes[node_id] = node + self.container.add_neighbor_router(self._address(node_id), node.maskbit, link_maskbit) + + + def lost_neighbor(self, node_id): + """ + We have lost contact with a neighboring node node_id. + """ + node = self.nodes[node_id] + node.neighbor = False + self.container.del_neighbor_router(node.maskbit) + if node.remote: + self.container.add_remote_router(self._address(node.id), node.maskbit) + else: + self._free_maskbit(node.maskbit) + self.nodes.pop(node_id) + + + def new_node(self, node_id): + """ + A node, designated by node_id, has been discovered through the an advertisement from a + remote peer. + """ + if node_id not in self.nodes: + node = RemoteNode(node_id, self._allocate_maskbit(), False) + self.nodes[node_id] = node + self.container.add_remote_router(self._address(node.id), node.maskbit) + else: + node = self.nodes[node_id] + node.remote = True + + + def lost_node(self, node_id): + """ + A remote node, node_id, has not been heard from for too long and is being deemed lost. + """ + node = self.nodes[node_id] + if node.remote: + node.remote = False + if not node.neighbor: + self.container.del_remote_router(node.maskbit) + self._free_maskbit(node.maskbit) + self.nodes.pop(node_id) + + + def maskbit_for_node(self, node_id): + """ + """ + node = self.nodes[node_id] + if node: + return node.maskbit + return None + + + def add_addresses(self, node_id, addrs): + node = self.nodes[node_id] + for a in addrs: + node.addrs[a] = 1 + + + def del_addresses(self, node_id, addrs): + node = self.nodes[node_id] + for a in addrs: + node.addrs.pop(a) + + + def overwrite_addresses(self, node_id, addrs): + node = self.nodes[node_id] + added = [] + deleted = [] + for a in addrs: + if a not in node.addrs.keys(): + added.append(a) + for a in node.addrs.keys(): + if a not in addrs: + deleted.append(a) + for a in addrs: + node.addrs[a] = 1 + return (added, deleted) + + + def _allocate_maskbit(self): + if self.next_maskbit == None: + raise Exception("Exceeded Maximum Router Count") + result = self.next_maskbit + self.next_maskbit = None + self.maskbits[result] = True + for n in range(result + 1, self.max_routers): + if self.maskbits[n] == None: + self.next_maskbit = n + break + return result + + + def _free_maskbit(self, i): + self.maskbits[i] = None + if self.next_maskbit == None or i < self.next_maskbit: + self.next_maskbit = i + + + def _address(self, node_id): + return "amqp:/_topo/%s/%s" % (self.container.area, node_id) class RemoteNode(object): - def __init__(self, node_id): - self.id = node_id - self.neighbor = None - self.remote = None - - def set_neighbor(self): - self.neighbor = True - - def set_remote(self): - self.remote = True - - def clear_neighbor(self): - self.neighbor = None - - def clear_remote(self): - self.remote = None - - def to_delete(self): - return self.neighbor or self.remote + def __init__(self, node_id, maskbit, neighbor): + self.id = node_id + self.maskbit = maskbit + self.neighbor = neighbor + self.remote = not neighbor + self.addrs = {} # Address => Count at Node (1 only for the present) diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/path.py b/qpid/extras/dispatch/python/qpid/dispatch/router/path.py index c051dbe7fc..da04474e75 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/path.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/path.py @@ -18,185 +18,218 @@ # try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * class PathEngine(object): - """ - This module is responsible for computing the next-hop for every router/area in the domain - based on the collection of link states that have been gathered. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.recalculate = False - self.collection = None - - - def tick(self, now_unused): - if self.recalculate: - self.recalculate = False - self._calculate_routes() - - - def ls_collection_changed(self, collection): - self.recalculate = True - self.collection = collection - - - def _calculate_tree_from_root(self, root): - ## - ## Make a copy of the current collection of link-states that contains - ## an empty link-state for nodes that are known-peers but are not in the - ## collection currently. This is needed to establish routes to those nodes - ## so we can trade link-state information with them. - ## - link_states = {} - for _id, ls in self.collection.items(): - link_states[_id] = ls.peers - for p in ls.peers: - if p not in link_states: - link_states[p] = [] - - ## - ## Setup Dijkstra's Algorithm - ## - cost = {} - prev = {} - for _id in link_states: - cost[_id] = None # infinite - prev[_id] = None # undefined - cost[root] = 0 # no cost to the root node - unresolved = NodeSet(cost) - - ## - ## Process unresolved nodes until lowest cost paths to all reachable nodes have been found. - ## - while not unresolved.empty(): - u = unresolved.lowest_cost() - if cost[u] == None: - # There are no more reachable nodes in unresolved - break - for v in link_states[u]: - if unresolved.contains(v): - alt = cost[u] + 1 # TODO - Use link cost instead of 1 - if cost[v] == None or alt < cost[v]: - cost[v] = alt - prev[v] = u - unresolved.set_cost(v, alt) - - ## - ## Remove unreachable nodes from the map. Note that this will also remove the - ## root node (has no previous node) from the map. - ## - for u, val in prev.items(): - if not val: - prev.pop(u) - - ## - ## Return previous-node map. This is a map of all reachable, remote nodes to - ## their predecessor node. - ## - return prev - - - def _calculate_routes(self): - ## - ## Generate the shortest-path tree with the local node as root - ## - prev = self._calculate_tree_from_root(self.id) - nodes = prev.keys() - - ## - ## Distill the path tree into a map of next hops for each node - ## - next_hops = {} - while len(nodes) > 0: - u = nodes[0] # pick any destination - path = [u] - nodes.remove(u) - v = prev[u] - while v != self.id: # build a list of nodes in the path back to the root - if v in nodes: - path.append(v) - nodes.remove(v) - u = v - v = prev[u] - for w in path: # mark each node in the path as reachable via the next hop - next_hops[w] = u - - ## - ## TODO - Calculate the tree from each origin, determine the set of origins-per-dest - ## for which the path from origin to dest passes through us. This is the set - ## of valid origins for forwarding to the destination. - ## - - self.container.next_hops_changed(next_hops) + """ + This module is responsible for computing the next-hop for every router/area in the domain + based on the collection of link states that have been gathered. + """ + def __init__(self, container): + self.container = container + self.id = self.container.id + self.area = self.container.area + self.recalculate = False + self.collection = None -class NodeSet(object): - """ - This data structure is an ordered list of node IDs, sorted in increasing order by their cost. - Equal cost nodes are secondarily sorted by their ID in order to provide deterministic and - repeatable ordering. - """ - def __init__(self, cost_map): - self.nodes = [] - for _id, cost in cost_map.items(): - ## - ## Assume that nodes are either unreachable (cost = None) or local (cost = 0) - ## during this initialization. - ## - if cost == 0: - self.nodes.insert(0, (_id, cost)) - else: + def tick(self, now_unused): + if self.recalculate: + self.recalculate = False + self._calculate_routes() + + + def ls_collection_changed(self, collection): + self.recalculate = True + self.collection = collection + + + def _calculate_tree_from_root(self, root): ## - ## There is no need to sort unreachable nodes by ID + ## Make a copy of the current collection of link-states that contains + ## a fake link-state for nodes that are known-peers but are not in the + ## collection currently. This is needed to establish routes to those nodes + ## so we can trade link-state information with them. ## - self.nodes.append((_id, cost)) + link_states = {} + for _id, ls in self.collection.items(): + link_states[_id] = ls.peers + for p in ls.peers: + if p not in link_states: + link_states[p] = [_id] + ## + ## Setup Dijkstra's Algorithm + ## + cost = {} + prev = {} + for _id in link_states: + cost[_id] = None # infinite + prev[_id] = None # undefined + cost[root] = 0 # no cost to the root node + unresolved = NodeSet(cost) - def __repr__(self): - return self.nodes.__repr__() + ## + ## Process unresolved nodes until lowest cost paths to all reachable nodes have been found. + ## + while not unresolved.empty(): + u = unresolved.lowest_cost() + if cost[u] == None: + # There are no more reachable nodes in unresolved + break + for v in link_states[u]: + if unresolved.contains(v): + alt = cost[u] + 1 # TODO - Use link cost instead of 1 + if cost[v] == None or alt < cost[v]: + cost[v] = alt + prev[v] = u + unresolved.set_cost(v, alt) + + ## + ## Remove unreachable nodes from the map. Note that this will also remove the + ## root node (has no previous node) from the map. + ## + for u, val in prev.items(): + if not val: + prev.pop(u) + ## + ## Return previous-node map. This is a map of all reachable, remote nodes to + ## their predecessor node. + ## + return prev - def empty(self): - return len(self.nodes) == 0 + def _calculate_valid_origins(self, nodeset): + ## + ## Calculate the tree from each origin, determine the set of origins-per-dest + ## for which the path from origin to dest passes through us. This is the set + ## of valid origins for forwarding to the destination. + ## + valid_origin = {} # Map of destination => List of Valid Origins + for node in nodeset: + if node != self.id: + valid_origin[node] = [] + + for root in valid_origin.keys(): + prev = self._calculate_tree_from_root(root) + nodes = prev.keys() + while len(nodes) > 0: + u = nodes[0] + path = [u] + nodes.remove(u) + v = prev[u] + while v != root: + if v in nodes: + if v != self.id: + path.append(v) + nodes.remove(v) + if v == self.id: + valid_origin[root].extend(path) + u = v + v = prev[u] + return valid_origin + + + def _calculate_routes(self): + ## + ## Generate the shortest-path tree with the local node as root + ## + prev = self._calculate_tree_from_root(self.id) + nodes = prev.keys() - def contains(self, _id): - for a, b in self.nodes: - if a == _id: - return True - return False + ## + ## Distill the path tree into a map of next hops for each node + ## + next_hops = {} + while len(nodes) > 0: + u = nodes[0] # pick any destination + path = [u] + nodes.remove(u) + v = prev[u] + while v != self.id: # build a list of nodes in the path back to the root + if v in nodes: + path.append(v) + nodes.remove(v) + u = v + v = prev[u] + for w in path: # mark each node in the path as reachable via the next hop + next_hops[w] = u + + self.container.next_hops_changed(next_hops) + ## + ## Calculate the valid origins for remote routers + ## + valid_origin = self._calculate_valid_origins(prev.keys()) + self.container.valid_origins_changed(valid_origin) - def lowest_cost(self): - """ - Remove and return the lowest cost node ID. - """ - _id, cost = self.nodes.pop(0) - return _id - def set_cost(self, _id, new_cost): +class NodeSet(object): """ - Set the cost for an ID in the NodeSet and re-insert the ID so that the list - remains sorted in increasing cost order. + This data structure is an ordered list of node IDs, sorted in increasing order by their cost. + Equal cost nodes are secondarily sorted by their ID in order to provide deterministic and + repeatable ordering. """ - index = 0 - for i, c in self.nodes: - if i == _id: - break - index += 1 - self.nodes.pop(index) - - index = 0 - for i, c in self.nodes: - if c == None or new_cost < c or (new_cost == c and _id < i): - break - index += 1 - - self.nodes.insert(index, (_id, new_cost)) + def __init__(self, cost_map): + self.nodes = [] + for _id, cost in cost_map.items(): + ## + ## Assume that nodes are either unreachable (cost = None) or local (cost = 0) + ## during this initialization. + ## + if cost == 0: + self.nodes.insert(0, (_id, cost)) + else: + ## + ## There is no need to sort unreachable nodes by ID + ## + self.nodes.append((_id, cost)) + + + def __repr__(self): + return self.nodes.__repr__() + + + def empty(self): + return len(self.nodes) == 0 + + + def contains(self, _id): + for a, b in self.nodes: + if a == _id: + return True + return False + + + def lowest_cost(self): + """ + Remove and return the lowest cost node ID. + """ + _id, cost = self.nodes.pop(0) + return _id + + + def set_cost(self, _id, new_cost): + """ + Set the cost for an ID in the NodeSet and re-insert the ID so that the list + remains sorted in increasing cost order. + """ + index = 0 + for i, c in self.nodes: + if i == _id: + break + index += 1 + self.nodes.pop(index) + + index = 0 + for i, c in self.nodes: + if c == None or new_cost < c or (new_cost == c and _id < i): + break + index += 1 + + self.nodes.insert(index, (_id, new_cost)) + diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/router_engine.py b/qpid/extras/dispatch/python/qpid/dispatch/router/router_engine.py index 18b48379c5..f8b2dd9c94 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/router_engine.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/router_engine.py @@ -27,254 +27,267 @@ from link import LinkStateEngine from path import PathEngine from mobile import MobileAddressEngine from routing import RoutingTableEngine -from binding import BindingEngine -from adapter import AdapterEngine from node import NodeTracker +import sys +import traceback + ## ## Import the Dispatch adapters from the environment. If they are not found ## (i.e. we are in a test bench, etc.), load the stub versions. ## try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * class RouterEngine: - """ - """ - - def __init__(self, router_adapter, router_id=None, area='area', config_override={}): - """ - Initialize an instance of a router for a domain. - """ - ## - ## Record important information about this router instance - ## - self.domain = "domain" - self.router_adapter = router_adapter - self.log_adapter = LogAdapter("dispatch.router") - self.io_adapter = IoAdapter(self, "qdxrouter") - - if router_id: - self.id = router_id - else: - self.id = str(uuid4()) - self.area = area - self.log(LOG_INFO, "Router Engine Instantiated: area=%s id=%s" % (self.area, self.id)) - - ## - ## Setup configuration - ## - self.config = Configuration(config_override) - self.log(LOG_INFO, "Config: %r" % self.config) - - ## - ## Launch the sub-module engines - ## - self.neighbor_engine = NeighborEngine(self) - self.link_state_engine = LinkStateEngine(self) - self.path_engine = PathEngine(self) - self.mobile_address_engine = MobileAddressEngine(self) - self.routing_table_engine = RoutingTableEngine(self) - self.binding_engine = BindingEngine(self) - self.adapter_engine = AdapterEngine(self) - self.node_tracker = NodeTracker(self) - - - - ##======================================================================================== - ## Adapter Entry Points - invoked from the adapter - ##======================================================================================== - def getId(self): - """ - Return the router's ID - """ - return self.id - - - def addLocalAddress(self, key): - """ - """ - try: - if key.find('_topo') == 0 or key.find('_local') == 0: - return - self.mobile_address_engine.add_local_address(key) - except Exception, e: - self.log(LOG_ERROR, "Exception in new-address processing: exception=%r" % e) - - def delLocalAddress(self, key): - """ - """ - try: - if key.find('_topo') == 0 or key.find('_local') == 0: - return - self.mobile_address_engine.del_local_address(key) - except Exception, e: - self.log(LOG_ERROR, "Exception in del-address processing: exception=%r" % e) - - - def handleTimerTick(self): - """ - """ - try: - now = time() - self.neighbor_engine.tick(now) - self.link_state_engine.tick(now) - self.path_engine.tick(now) - self.mobile_address_engine.tick(now) - self.routing_table_engine.tick(now) - self.binding_engine.tick(now) - self.adapter_engine.tick(now) - self.node_tracker.tick(now) - except Exception, e: - self.log(LOG_ERROR, "Exception in timer processing: exception=%r" % e) - - - def handleControlMessage(self, opcode, body): - """ - """ - try: - now = time() - if opcode == 'HELLO': - msg = MessageHELLO(body) - self.log(LOG_TRACE, "RCVD: %r" % msg) - self.neighbor_engine.handle_hello(msg, now) - - elif opcode == 'RA': - msg = MessageRA(body) - self.log(LOG_TRACE, "RCVD: %r" % msg) - self.link_state_engine.handle_ra(msg, now) - self.mobile_address_engine.handle_ra(msg, now) - - elif opcode == 'LSU': - msg = MessageLSU(body) - self.log(LOG_TRACE, "RCVD: %r" % msg) - self.link_state_engine.handle_lsu(msg, now) - - elif opcode == 'LSR': - msg = MessageLSR(body) - self.log(LOG_TRACE, "RCVD: %r" % msg) - self.link_state_engine.handle_lsr(msg, now) - - elif opcode == 'MAU': - msg = MessageMAU(body) - self.log(LOG_TRACE, "RCVD: %r" % msg) - self.mobile_address_engine.handle_mau(msg, now) - - elif opcode == 'MAR': - msg = MessageMAR(body) - self.log(LOG_TRACE, "RCVD: %r" % msg) - self.mobile_address_engine.handle_mar(msg, now) - - except Exception, e: - self.log(LOG_ERROR, "Exception in message processing: opcode=%s body=%r exception=%r" % (opcode, body, e)) - - - def receive(self, message_properties, body): - """ - This is the IoAdapter message-receive handler - """ - try: - self.handleControlMessage(message_properties['opcode'], body) - except Exception, e: - self.log(LOG_ERROR, "Exception in raw message processing: properties=%r body=%r exception=%r" % - (message_properties, body, e)) - - def getRouterData(self, kind): """ """ - if kind == 'help': - return { 'help' : "Get list of supported values for kind", - 'link-state' : "This router's link state", - 'link-state-set' : "The set of link states from known routers", - 'next-hops' : "Next hops to each known router", - 'topo-table' : "Topological routing table", - 'mobile-table' : "Mobile key routing table" - } - if kind == 'link-state' : return self.neighbor_engine.link_state.to_dict() - if kind == 'next-hops' : return self.routing_table_engine.next_hops - if kind == 'topo-table' : return {'table': self.adapter_engine.key_classes['topological']} - if kind == 'mobile-table' : return {'table': self.adapter_engine.key_classes['mobile-key']} - if kind == 'link-state-set' : - copy = {} - for _id,_ls in self.link_state_engine.collection.items(): - copy[_id] = _ls.to_dict() - return copy - - return {'notice':'Use kind="help" to get a list of possibilities'} - - - ##======================================================================================== - ## Adapter Calls - outbound calls to Dispatch - ##======================================================================================== - def log(self, level, text): - """ - Emit a log message to the host's event log - """ - self.log_adapter.log(level, text) - - - def send(self, dest, msg): - """ - Send a control message to another router. - """ - app_props = {'opcode' : msg.get_opcode() } - self.io_adapter.send(dest, app_props, msg.to_dict()) - self.log(LOG_TRACE, "SENT: %r dest=%s" % (msg, dest)) - - - def node_updated(self, addr, reachable, neighbor): - """ - """ - self.router_adapter(addr, reachable, neighbor) - - - ##======================================================================================== - ## Interconnect between the Sub-Modules - ##======================================================================================== - def local_link_state_changed(self, link_state): - self.log(LOG_DEBUG, "Event: local_link_state_changed: %r" % link_state) - self.link_state_engine.new_local_link_state(link_state) - - def ls_collection_changed(self, collection): - self.log(LOG_DEBUG, "Event: ls_collection_changed: %r" % collection) - self.path_engine.ls_collection_changed(collection) - - def next_hops_changed(self, next_hop_table): - self.log(LOG_DEBUG, "Event: next_hops_changed: %r" % next_hop_table) - self.routing_table_engine.next_hops_changed(next_hop_table) - self.binding_engine.next_hops_changed() - - def mobile_sequence_changed(self, mobile_seq): - self.log(LOG_DEBUG, "Event: mobile_sequence_changed: %d" % mobile_seq) - self.link_state_engine.set_mobile_sequence(mobile_seq) - - def mobile_keys_changed(self, keys): - self.log(LOG_DEBUG, "Event: mobile_keys_changed: %r" % keys) - self.binding_engine.mobile_keys_changed(keys) - - def get_next_hops(self): - return self.routing_table_engine.get_next_hops() - - def remote_routes_changed(self, key_class, routes): - self.log(LOG_DEBUG, "Event: remote_routes_changed: class=%s routes=%r" % (key_class, routes)) - self.adapter_engine.remote_routes_changed(key_class, routes) - - def new_neighbor(self, rid): - self.log(LOG_DEBUG, "Event: new_neighbor: id=%s" % rid) - self.node_tracker.new_neighbor(rid) - - def lost_neighbor(self, rid): - self.log(LOG_DEBUG, "Event: lost_neighbor: id=%s" % rid) - self.node_tracker.lost_neighbor(rid) - - def new_node(self, rid): - self.log(LOG_DEBUG, "Event: new_node: id=%s" % rid) - self.node_tracker.new_node(rid) - def lost_node(self, rid): - self.log(LOG_DEBUG, "Event: lost_node: id=%s" % rid) - self.node_tracker.lost_node(rid) + def __init__(self, router_adapter, router_id, area, max_routers, config_override={}): + """ + Initialize an instance of a router for a domain. + """ + ## + ## Record important information about this router instance + ## + self.domain = "domain" + self.router_adapter = router_adapter + self.log_adapter = LogAdapter("dispatch.router") + self.io_adapter = IoAdapter(self, ("qdxrouter", "qdxhello")) + self.max_routers = max_routers + self.id = router_id + self.area = area + self.log(LOG_INFO, "Router Engine Instantiated: area=%s id=%s max_routers=%d" % + (self.area, self.id, self.max_routers)) + + ## + ## Setup configuration + ## + self.config = Configuration(config_override) + self.log(LOG_INFO, "Config: %r" % self.config) + + ## + ## Launch the sub-module engines + ## + self.node_tracker = NodeTracker(self, self.max_routers) + self.neighbor_engine = NeighborEngine(self) + self.link_state_engine = LinkStateEngine(self) + self.path_engine = PathEngine(self) + self.mobile_address_engine = MobileAddressEngine(self, self.node_tracker) + self.routing_table_engine = RoutingTableEngine(self, self.node_tracker) + + + + ##======================================================================================== + ## Adapter Entry Points - invoked from the adapter + ##======================================================================================== + def getId(self): + """ + Return the router's ID + """ + return self.id + + + def addressAdded(self, addr): + """ + """ + try: + if addr.find('Mtemp.') == 0: + return + if addr.find('M') == 0: + self.mobile_address_engine.add_local_address(addr[1:]) + except Exception, e: + self.log(LOG_ERROR, "Exception in new-address processing: exception=%r" % e) + + + def addressRemoved(self, addr): + """ + """ + try: + if addr.find('Mtemp.') == 0: + return + if addr.find('M') == 0: + self.mobile_address_engine.del_local_address(addr[1:]) + except Exception, e: + self.log(LOG_ERROR, "Exception in del-address processing: exception=%r" % e) + + + def handleTimerTick(self): + """ + """ + try: + now = time() + self.neighbor_engine.tick(now) + self.link_state_engine.tick(now) + self.path_engine.tick(now) + self.mobile_address_engine.tick(now) + self.routing_table_engine.tick(now) + self.node_tracker.tick(now) + except Exception, e: + self.log(LOG_ERROR, "Exception in timer processing: exception=%r" % e) + + + def handleControlMessage(self, opcode, body, link_id): + """ + """ + try: + now = time() + if opcode == 'HELLO': + msg = MessageHELLO(body) + self.log(LOG_TRACE, "RCVD: %r" % msg) + self.neighbor_engine.handle_hello(msg, now, link_id) + + elif opcode == 'RA': + msg = MessageRA(body) + self.log(LOG_DEBUG, "RCVD: %r" % msg) + self.link_state_engine.handle_ra(msg, now) + self.mobile_address_engine.handle_ra(msg, now) + + elif opcode == 'LSU': + msg = MessageLSU(body) + self.log(LOG_DEBUG, "RCVD: %r" % msg) + self.link_state_engine.handle_lsu(msg, now) + + elif opcode == 'LSR': + msg = MessageLSR(body) + self.log(LOG_DEBUG, "RCVD: %r" % msg) + self.link_state_engine.handle_lsr(msg, now) + + elif opcode == 'MAU': + msg = MessageMAU(body) + self.log(LOG_DEBUG, "RCVD: %r" % msg) + self.mobile_address_engine.handle_mau(msg, now) + + elif opcode == 'MAR': + msg = MessageMAR(body) + self.log(LOG_DEBUG, "RCVD: %r" % msg) + self.mobile_address_engine.handle_mar(msg, now) + + except Exception, e: + self.log(LOG_ERROR, "Exception in message processing: opcode=%s body=%r exception=%r" % (opcode, body, e)) + exc_type, exc_value, exc_traceback = sys.exc_info() + traceback.print_tb(exc_traceback) + + + def receive(self, message_properties, body, link_id): + """ + This is the IoAdapter message-receive handler + """ + try: + #self.log(LOG_DEBUG, "Raw Receive: mp=%r body=%r link_id=%r" % (message_properties, body, link_id)) + self.handleControlMessage(message_properties['opcode'], body, link_id) + except Exception, e: + self.log(LOG_ERROR, "Exception in raw message processing: properties=%r body=%r exception=%r" % + (message_properties, body, e)) + + + def getRouterData(self, kind): + """ + """ + if kind == 'help': + return { 'help' : "Get list of supported values for kind", + 'link-state' : "This router's link state", + 'link-state-set' : "The set of link states from known routers", + 'next-hops' : "Next hops to each known router" + } + if kind == 'link-state' : return self.neighbor_engine.link_state.to_dict() + if kind == 'next-hops' : return self.routing_table_engine.next_hops + if kind == 'link-state-set' : + copy = {} + for _id,_ls in self.link_state_engine.collection.items(): + copy[_id] = _ls.to_dict() + return copy + + return {'notice':'Use kind="help" to get a list of possibilities'} + + + ##======================================================================================== + ## Adapter Calls - outbound calls to Dispatch + ##======================================================================================== + def log(self, level, text): + """ + Emit a log message to the host's event log + """ + self.log_adapter.log(level, text) + + + def send(self, dest, msg): + """ + Send a control message to another router. + """ + app_props = {'opcode' : msg.get_opcode() } + self.io_adapter.send(dest, app_props, msg.to_dict()) + if "qdxhello" in dest: + self.log(LOG_TRACE, "SENT: %r dest=%s" % (msg, dest)) + else: + self.log(LOG_DEBUG, "SENT: %r dest=%s" % (msg, dest)) + + + def node_updated(self, addr, reachable, neighbor): + """ + """ + self.router_adapter(addr, reachable, neighbor) + + + ##======================================================================================== + ## Interconnect between the Sub-Modules + ##======================================================================================== + def local_link_state_changed(self, link_state): + self.log(LOG_DEBUG, "Event: local_link_state_changed: %r" % link_state) + self.link_state_engine.new_local_link_state(link_state) + + def ls_collection_changed(self, collection): + self.log(LOG_DEBUG, "Event: ls_collection_changed: %r" % collection) + self.path_engine.ls_collection_changed(collection) + + def next_hops_changed(self, next_hop_table): + self.log(LOG_DEBUG, "Event: next_hops_changed: %r" % next_hop_table) + self.routing_table_engine.next_hops_changed(next_hop_table) + + def valid_origins_changed(self, valid_origins): + self.log(LOG_DEBUG, "Event: valid_origins_changed: %r" % valid_origins) + self.routing_table_engine.valid_origins_changed(valid_origins) + + def mobile_sequence_changed(self, mobile_seq): + self.log(LOG_DEBUG, "Event: mobile_sequence_changed: %d" % mobile_seq) + self.link_state_engine.set_mobile_sequence(mobile_seq) + + def get_next_hops(self): + return self.routing_table_engine.get_next_hops() + + def new_neighbor(self, rid, link_id): + self.log(LOG_DEBUG, "Event: new_neighbor: id=%s link_id=%d" % (rid, link_id)) + self.node_tracker.new_neighbor(rid, link_id) + + def lost_neighbor(self, rid): + self.log(LOG_DEBUG, "Event: lost_neighbor: id=%s" % rid) + self.node_tracker.lost_neighbor(rid) + + def new_node(self, rid): + self.log(LOG_DEBUG, "Event: new_node: id=%s" % rid) + self.node_tracker.new_node(rid) + + def lost_node(self, rid): + self.log(LOG_DEBUG, "Event: lost_node: id=%s" % rid) + self.node_tracker.lost_node(rid) + + def add_neighbor_router(self, address, router_bit, link_bit): + self.log(LOG_DEBUG, "Event: add_neighbor_router: address=%s, router_bit=%d, link_bit=%d" % \ + (address, router_bit, link_bit)) + self.router_adapter.add_neighbor_router(address, router_bit, link_bit) + + def del_neighbor_router(self, router_bit): + self.log(LOG_DEBUG, "Event: del_neighbor_router: router_bit=%d" % router_bit) + self.router_adapter.del_neighbor_router(router_bit) + + def add_remote_router(self, address, router_bit): + self.log(LOG_DEBUG, "Event: add_remote_router: address=%s, router_bit=%d" % (address, router_bit)) + self.router_adapter.add_remote_router(address, router_bit) + + def del_remote_router(self, router_bit): + self.log(LOG_DEBUG, "Event: del_remote_router: router_bit=%d" % router_bit) + self.router_adapter.del_remote_router(router_bit) diff --git a/qpid/extras/dispatch/python/qpid/dispatch/router/routing.py b/qpid/extras/dispatch/python/qpid/dispatch/router/routing.py index 8030582177..a4b3e5484a 100644 --- a/qpid/extras/dispatch/python/qpid/dispatch/router/routing.py +++ b/qpid/extras/dispatch/python/qpid/dispatch/router/routing.py @@ -18,39 +18,45 @@ # try: - from dispatch import * + from dispatch import * except ImportError: - from ..stubs import * + from ..stubs import * class RoutingTableEngine(object): - """ - This module is responsible for converting the set of next hops to remote routers to a routing - table in the "topological" address class. - """ - def __init__(self, container): - self.container = container - self.id = self.container.id - self.area = self.container.area - self.next_hops = {} - - - def tick(self, now): - pass - - - def next_hops_changed(self, next_hops): - # Convert next_hops into routing table - self.next_hops = next_hops - new_table = [] - for _id, next_hop in next_hops.items(): - new_table.append(('_topo.%s.%s.#' % (self.area, _id), next_hop)) - pair = ('_topo.%s.all' % (self.area), next_hop) - if new_table.count(pair) == 0: - new_table.append(pair) - - self.container.remote_routes_changed('topological', new_table) - - - def get_next_hops(self): - return self.next_hops + """ + This module is responsible for converting the set of next hops to remote routers to a routing + table in the "topological" address class. + """ + def __init__(self, container, node_tracker): + self.container = container + self.node_tracker = node_tracker + self.id = self.container.id + self.area = self.container.area + self.next_hops = {} + + + def tick(self, now): + pass + + + def next_hops_changed(self, next_hops): + # Convert next_hops into routing table + self.next_hops = next_hops + for _id, next_hop in next_hops.items(): + mb_id = self.node_tracker.maskbit_for_node(_id) + mb_nh = self.node_tracker.maskbit_for_node(next_hop) + self.container.router_adapter.set_next_hop(mb_id, mb_nh) + + + def valid_origins_changed(self, valid_origins): + for _id, vo in valid_origins.items(): + mb_id = self.node_tracker.maskbit_for_node(_id) + mb_vo = [] + for o in vo: + mb_vo.append(self.node_tracker.maskbit_for_node(o)) + self.container.router_adapter.set_valid_origins(mb_id, mb_vo) + + + def get_next_hops(self): + return self.next_hops diff --git a/qpid/extras/dispatch/router/CMakeLists.txt b/qpid/extras/dispatch/router/CMakeLists.txt index ee6d1934cf..388e0d7119 100644 --- a/qpid/extras/dispatch/router/CMakeLists.txt +++ b/qpid/extras/dispatch/router/CMakeLists.txt @@ -18,7 +18,7 @@ ## -set(DEFAULT_CONFIG_PATH "/etc/qpid-dispatch.conf" CACHE string "Default Config File Path") +set(DEFAULT_CONFIG_PATH "/etc/qpid/qpid-dispatch.conf" CACHE string "Default Config File Path") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/qpid/extras/dispatch/router/src/main.c b/qpid/extras/dispatch/router/src/main.c index 62e3a2beb6..e0d6849d1b 100644 --- a/qpid/extras/dispatch/router/src/main.c +++ b/qpid/extras/dispatch/router/src/main.c @@ -117,7 +117,7 @@ int main(int argc, char **argv) } } - dx_log_set_mask(0xFFFFFFFF); + dx_log_set_mask(0xFFFFFFFE); dispatch = dx_dispatch(config_path); diff --git a/qpid/extras/dispatch/src/agent.c b/qpid/extras/dispatch/src/agent.c index 557410910c..a7475888b2 100644 --- a/qpid/extras/dispatch/src/agent.c +++ b/qpid/extras/dispatch/src/agent.c @@ -37,7 +37,7 @@ struct dx_agent_t { dx_dispatch_t *dx; - hash_t *class_hash; + dx_hash_t *class_hash; dx_message_list_t in_fifo; dx_message_list_t out_fifo; sys_mutex_t *lock; @@ -71,7 +71,7 @@ static void dx_agent_process_get(dx_agent_t *agent, dx_parsed_field_t *map, dx_f dx_field_iterator_t *cls_string = dx_parse_raw(cls); const dx_agent_class_t *cls_record; - hash_retrieve_const(agent->class_hash, cls_string, (const void**) &cls_record); + dx_hash_retrieve_const(agent->class_hash, cls_string, (const void**) &cls_record); if (cls_record == 0) return; @@ -138,11 +138,11 @@ static void dx_agent_process_get(dx_agent_t *agent, dx_parsed_field_t *map, dx_f // // Create a message and send it. // - dx_message_t *msg = dx_allocate_message(); + dx_message_t *msg = dx_message(); dx_message_compose_2(msg, field); dx_router_send(agent->dx, reply_to, msg); - dx_free_message(msg); + dx_message_free(msg); dx_compose_free(field); } @@ -234,13 +234,13 @@ static void dx_agent_deferred_handler(void *context) if (msg) { dx_agent_process_request(agent, msg); - dx_free_message(msg); + dx_message_free(msg); } } while (msg); } -static void dx_agent_rx_handler(void *context, dx_message_t *msg) +static void dx_agent_rx_handler(void *context, dx_message_t *msg, int unused_link_id) { dx_agent_t *agent = (dx_agent_t*) context; dx_message_t *copy = dx_message_copy(msg); @@ -257,12 +257,12 @@ dx_agent_t *dx_agent(dx_dispatch_t *dx) { dx_agent_t *agent = NEW(dx_agent_t); agent->dx = dx; - agent->class_hash = hash(6, 10, 1); + agent->class_hash = dx_hash(6, 10, 1); DEQ_INIT(agent->in_fifo); DEQ_INIT(agent->out_fifo); agent->lock = sys_mutex(); agent->timer = dx_timer(dx, dx_agent_deferred_handler, agent); - agent->address = dx_router_register_address(dx, "agent", dx_agent_rx_handler, agent); + agent->address = dx_router_register_address(dx, "$management", dx_agent_rx_handler, agent); return agent; } @@ -273,7 +273,7 @@ void dx_agent_free(dx_agent_t *agent) dx_router_unregister_address(agent->address); sys_mutex_free(agent->lock); dx_timer_free(agent->timer); - hash_free(agent->class_hash); + dx_hash_free(agent->class_hash); free(agent); } @@ -295,7 +295,7 @@ dx_agent_class_t *dx_agent_register_class(dx_dispatch_t *dx, cls->query_handler = query_handler; dx_field_iterator_t *iter = dx_field_iterator_string(fqname, ITER_VIEW_ALL); - int result = hash_insert_const(agent->class_hash, iter, cls); + int result = dx_hash_insert_const(agent->class_hash, iter, cls, 0); dx_field_iterator_free(iter); if (result < 0) assert(false); diff --git a/qpid/extras/dispatch/src/amqp.c b/qpid/extras/dispatch/src/amqp.c new file mode 100644 index 0000000000..6a8545b757 --- /dev/null +++ b/qpid/extras/dispatch/src/amqp.c @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#include <qpid/dispatch/amqp.h> + +const char * const DX_DA_INGRESS = "qdx.ingress"; +const char * const DX_DA_TRACE = "qdx.trace"; +const char * const DX_DA_TO = "qdx.to"; + +const char * const DX_CAPABILITY_ROUTER = "qdx.router"; + +const char * const DX_INTERNODE_LINK_NAME_1 = "qdx.internode.1"; +const char * const DX_INTERNODE_LINK_NAME_2 = "qdx.internode.2"; + diff --git a/qpid/extras/dispatch/src/bitmask.c b/qpid/extras/dispatch/src/bitmask.c new file mode 100644 index 0000000000..88ba69dde1 --- /dev/null +++ b/qpid/extras/dispatch/src/bitmask.c @@ -0,0 +1,124 @@ +/* + * 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. + */ + +#include <qpid/dispatch/bitmask.h> +#include <qpid/dispatch/alloc.h> +#include <assert.h> + +#define DX_BITMASK_LONGS 16 +#define DX_BITMASK_BITS (DX_BITMASK_LONGS * 64) + +struct dx_bitmask_t { + uint64_t array[DX_BITMASK_LONGS]; + int first_set; +}; + +ALLOC_DECLARE(dx_bitmask_t); +ALLOC_DEFINE(dx_bitmask_t); + +#define MASK_INDEX(num) (num / 64) +#define MASK_ONEHOT(num) (((uint64_t) 1) << (num % 64)) +#define FIRST_NONE -1 +#define FIRST_UNKNOWN -2 + + +int dx_bitmask_width() +{ + return DX_BITMASK_BITS; +} + + +dx_bitmask_t *dx_bitmask(int initial) +{ + dx_bitmask_t *b = new_dx_bitmask_t(); + if (initial) + dx_bitmask_set_all(b); + else + dx_bitmask_clear_all(b); + return b; +} + + +void dx_bitmask_free(dx_bitmask_t *b) +{ + free_dx_bitmask_t(b); +} + + +void dx_bitmask_set_all(dx_bitmask_t *b) +{ + for (int i = 0; i < DX_BITMASK_LONGS; i++) + b->array[i] = 0xFFFFFFFFFFFFFFFF; + b->first_set = 0; +} + + +void dx_bitmask_clear_all(dx_bitmask_t *b) +{ + for (int i = 0; i < DX_BITMASK_LONGS; i++) + b->array[i] = 0; + b->first_set = FIRST_NONE; +} + + +void dx_bitmask_set_bit(dx_bitmask_t *b, int bitnum) +{ + assert(bitnum < DX_BITMASK_BITS); + b->array[MASK_INDEX(bitnum)] |= MASK_ONEHOT(bitnum); + if (b->first_set > bitnum || b->first_set < 0) + b->first_set = bitnum; +} + + +void dx_bitmask_clear_bit(dx_bitmask_t *b, int bitnum) +{ + assert(bitnum < DX_BITMASK_BITS); + b->array[MASK_INDEX(bitnum)] &= ~(MASK_ONEHOT(bitnum)); + if (b->first_set == bitnum) + b->first_set = FIRST_UNKNOWN; +} + + +int dx_bitmask_value(dx_bitmask_t *b, int bitnum) +{ + return (b->array[MASK_INDEX(bitnum)] & MASK_ONEHOT(bitnum)) ? 1 : 0; +} + + +int dx_bitmask_first_set(dx_bitmask_t *b, int *bitnum) +{ + if (b->first_set == FIRST_UNKNOWN) { + b->first_set = FIRST_NONE; + for (int i = 0; i < DX_BITMASK_LONGS; i++) + if (b->array[i]) { + for (int j = 0; j < 64; j++) + if ((((uint64_t) 1) << j) & b->array[i]) { + b->first_set = i * 64 + j; + break; + } + break; + } + } + + if (b->first_set == FIRST_NONE) + return 0; + *bitnum = b->first_set; + return 1; +} + diff --git a/qpid/extras/dispatch/src/buffer.c b/qpid/extras/dispatch/src/buffer.c index a5e609b996..d0bbd13cb3 100644 --- a/qpid/extras/dispatch/src/buffer.c +++ b/qpid/extras/dispatch/src/buffer.c @@ -34,7 +34,7 @@ void dx_buffer_set_size(size_t size) } -dx_buffer_t *dx_allocate_buffer(void) +dx_buffer_t *dx_buffer(void) { size_locked = 1; dx_buffer_t *buf = new_dx_buffer_t(); @@ -45,7 +45,7 @@ dx_buffer_t *dx_allocate_buffer(void) } -void dx_free_buffer(dx_buffer_t *buf) +void dx_buffer_free(dx_buffer_t *buf) { free_dx_buffer_t(buf); } diff --git a/qpid/extras/dispatch/src/compose.c b/qpid/extras/dispatch/src/compose.c index 6eb2ab1c73..66b2336e06 100644 --- a/qpid/extras/dispatch/src/compose.c +++ b/qpid/extras/dispatch/src/compose.c @@ -43,7 +43,7 @@ static void dx_insert(dx_composed_field_t *field, const uint8_t *seq, size_t len while (len > 0) { if (buf == 0 || dx_buffer_capacity(buf) == 0) { - buf = dx_allocate_buffer(); + buf = dx_buffer(); if (buf == 0) return; DEQ_INSERT_TAIL(field->buffers, buf); @@ -115,8 +115,8 @@ static void dx_overwrite_32(dx_field_location_t *field, uint32_t value) size_t cursor = field->offset; dx_overwrite(&buf, &cursor, (uint8_t) ((value & 0xFF000000) >> 24)); - dx_overwrite(&buf, &cursor, (uint8_t) ((value & 0x00FF0000) >> 24)); - dx_overwrite(&buf, &cursor, (uint8_t) ((value & 0x0000FF00) >> 24)); + dx_overwrite(&buf, &cursor, (uint8_t) ((value & 0x00FF0000) >> 16)); + dx_overwrite(&buf, &cursor, (uint8_t) ((value & 0x0000FF00) >> 8)); dx_overwrite(&buf, &cursor, (uint8_t) (value & 0x000000FF)); } @@ -212,7 +212,7 @@ void dx_compose_free(dx_composed_field_t *field) dx_buffer_t *buf = DEQ_HEAD(field->buffers); while (buf) { DEQ_REMOVE_HEAD(field->buffers); - dx_free_buffer(buf); + dx_buffer_free(buf); buf = DEQ_HEAD(field->buffers); } diff --git a/qpid/extras/dispatch/src/container.c b/qpid/extras/dispatch/src/container.c index cc46f3ce77..3ae24d81b0 100644 --- a/qpid/extras/dispatch/src/container.c +++ b/qpid/extras/dispatch/src/container.c @@ -88,8 +88,8 @@ typedef struct container_class_t { struct dx_container_t { dx_dispatch_t *dx; dx_server_t *server; - hash_t *node_type_map; - hash_t *node_map; + dx_hash_t *node_type_map; + dx_hash_t *node_map; sys_mutex_t *lock; dx_node_t *default_node; dxc_node_type_list_t node_type_list; @@ -108,7 +108,7 @@ static void setup_outgoing_link(dx_container_t *container, pn_link_t *pn_link) if (source) { iter = dx_field_iterator_string(source, ITER_VIEW_NODE_ID); - hash_retrieve(container->node_map, iter, (void*) &node); + dx_hash_retrieve(container->node_map, iter, (void*) &node); dx_field_iterator_free(iter); } sys_mutex_unlock(container->lock); @@ -149,7 +149,7 @@ static void setup_incoming_link(dx_container_t *container, pn_link_t *pn_link) if (target) { iter = dx_field_iterator_string(target, ITER_VIEW_NODE_ID); - hash_retrieve(container->node_map, iter, (void*) &node); + dx_hash_retrieve(container->node_map, iter, (void*) &node); dx_field_iterator_free(iter); } sys_mutex_unlock(container->lock); @@ -429,8 +429,8 @@ static void container_query_handler(void* context, const char *id, void *correla container_class_t *cls = (container_class_t*) context; if (cls->class_id == DX_CONTAINER_CLASS_CONTAINER) { - dx_agent_value_uint(correlator, "node_type_count", hash_size(cls->container->node_type_map)); - dx_agent_value_uint(correlator, "node_count", hash_size(cls->container->node_map)); + dx_agent_value_uint(correlator, "node_type_count", dx_hash_size(cls->container->node_type_map)); + dx_agent_value_uint(correlator, "node_count", dx_hash_size(cls->container->node_map)); if (cls->container->default_node) dx_agent_value_string(correlator, "default_node_type", cls->container->default_node->ntype->type_name); else @@ -463,8 +463,8 @@ dx_container_t *dx_container(dx_dispatch_t *dx) container->dx = dx; container->server = dx->server; - container->node_type_map = hash(6, 4, 1); // 64 buckets, item batches of 4 - container->node_map = hash(10, 32, 0); // 1K buckets, item batches of 32 + container->node_type_map = dx_hash(6, 4, 1); // 64 buckets, item batches of 4 + container->node_map = dx_hash(10, 32, 0); // 1K buckets, item batches of 32 container->lock = sys_mutex(); container->default_node = 0; DEQ_INIT(container->node_type_list); @@ -507,7 +507,7 @@ int dx_container_register_node_type(dx_dispatch_t *dx, const dx_node_type_t *nt) nt_item->ntype = nt; sys_mutex_lock(container->lock); - result = hash_insert_const(container->node_type_map, iter, nt); + result = dx_hash_insert_const(container->node_type_map, iter, nt, 0); DEQ_INSERT_TAIL(container->node_type_list, nt_item); sys_mutex_unlock(container->lock); @@ -565,7 +565,7 @@ dx_node_t *dx_container_create_node(dx_dispatch_t *dx, if (name) { dx_field_iterator_t *iter = dx_field_iterator_string(name, ITER_VIEW_ALL); sys_mutex_lock(container->lock); - result = hash_insert(container->node_map, iter, node); + result = dx_hash_insert(container->node_map, iter, node, 0); sys_mutex_unlock(container->lock); dx_field_iterator_free(iter); if (result < 0) { @@ -591,7 +591,7 @@ void dx_container_destroy_node(dx_node_t *node) if (node->name) { dx_field_iterator_t *iter = dx_field_iterator_string(node->name, ITER_VIEW_ALL); sys_mutex_lock(container->lock); - hash_remove(container->node_map, iter); + dx_hash_remove(container->node_map, iter); sys_mutex_unlock(container->lock); dx_field_iterator_free(iter); free(node->name); @@ -651,12 +651,63 @@ void *dx_link_get_context(dx_link_t *link) } +void dx_link_set_conn_context(dx_link_t *link, void *context) +{ + pn_session_t *pn_sess = pn_link_session(link->pn_link); + if (!pn_sess) + return; + pn_connection_t *pn_conn = pn_session_connection(pn_sess); + if (!pn_conn) + return; + dx_connection_t *conn = (dx_connection_t*) pn_connection_get_context(pn_conn); + if (!conn) + return; + dx_connection_set_link_context(conn, context); +} + + +void *dx_link_get_conn_context(dx_link_t *link) +{ + pn_session_t *pn_sess = pn_link_session(link->pn_link); + if (!pn_sess) + return 0; + pn_connection_t *pn_conn = pn_session_connection(pn_sess); + if (!pn_conn) + return 0; + dx_connection_t *conn = (dx_connection_t*) pn_connection_get_context(pn_conn); + if (!conn) + return 0; + return dx_connection_get_link_context(conn); +} + + pn_link_t *dx_link_pn(dx_link_t *link) { return link->pn_link; } +dx_connection_t *dx_link_connection(dx_link_t *link) +{ + if (!link || !link->pn_link) + return 0; + + pn_session_t *sess = pn_link_session(link->pn_link); + if (!sess) + return 0; + + pn_connection_t *conn = pn_session_connection(sess); + if (!conn) + return 0; + + dx_connection_t *ctx = pn_connection_get_context(conn); + if (!ctx) + return 0; + + return ctx; +} + + pn_terminus_t *dx_link_source(dx_link_t *link) { return pn_link_source(link->pn_link); diff --git a/qpid/extras/dispatch/src/dispatch.c b/qpid/extras/dispatch/src/dispatch.c index a1a659fd74..20ba84afe0 100644 --- a/qpid/extras/dispatch/src/dispatch.c +++ b/qpid/extras/dispatch/src/dispatch.c @@ -24,6 +24,7 @@ #include "dispatch_private.h" #include "alloc_private.h" #include "log_private.h" +#include "router_private.h" /** * Private Function Prototypes @@ -34,8 +35,8 @@ void dx_server_free(dx_server_t *server); dx_container_t *dx_container(dx_dispatch_t *dx); void dx_container_setup_agent(dx_dispatch_t *dx); void dx_container_free(dx_container_t *container); -dx_router_t *dx_router(dx_dispatch_t *dx, const char *area, const char *id); -void dx_router_setup_agent(dx_dispatch_t *dx); +dx_router_t *dx_router(dx_dispatch_t *dx, dx_router_mode_t mode, const char *area, const char *id); +void dx_router_setup_late(dx_dispatch_t *dx); void dx_router_free(dx_router_t *router); dx_agent_t *dx_agent(dx_dispatch_t *dx); void dx_agent_free(dx_agent_t *agent); @@ -53,10 +54,13 @@ dx_dispatch_t *dx_dispatch(const char *config_path) { dx_dispatch_t *dx = NEW(dx_dispatch_t); - int thread_count = 0; - const char *container_name = 0; - const char *router_area = 0; - const char *router_id = 0; + int thread_count = 0; + const char *container_name = 0; + const char *router_mode_str = 0; + const char *router_area = 0; + const char *router_id = 0; + + dx_router_mode_t router_mode = DX_ROUTER_MODE_STANDALONE; DEQ_INIT(dx->config_listeners); DEQ_INIT(dx->config_connectors); @@ -78,8 +82,9 @@ dx_dispatch_t *dx_dispatch(const char *config_path) count = dx_config_item_count(dx->config, CONF_ROUTER); if (count == 1) { - router_area = dx_config_item_value_string(dx->config, CONF_ROUTER, 0, "area"); - router_id = dx_config_item_value_string(dx->config, CONF_ROUTER, 0, "router-id"); + router_mode_str = dx_config_item_value_string(dx->config, CONF_ROUTER, 0, "mode"); + router_area = dx_config_item_value_string(dx->config, CONF_ROUTER, 0, "area"); + router_id = dx_config_item_value_string(dx->config, CONF_ROUTER, 0, "router-id"); } } @@ -89,6 +94,12 @@ dx_dispatch_t *dx_dispatch(const char *config_path) if (!container_name) container_name = "00000000-0000-0000-0000-000000000000"; // TODO - gen a real uuid + if (router_mode_str && strcmp(router_mode_str, "interior") == 0) + router_mode = DX_ROUTER_MODE_INTERIOR; + + if (router_mode_str && strcmp(router_mode_str, "edge") == 0) + router_mode = DX_ROUTER_MODE_EDGE; + if (!router_area) router_area = "area"; @@ -97,13 +108,13 @@ dx_dispatch_t *dx_dispatch(const char *config_path) dx->server = dx_server(thread_count, container_name); dx->container = dx_container(dx); - dx->router = dx_router(dx, router_area, router_id); + dx->router = dx_router(dx, router_mode, router_area, router_id); dx->agent = dx_agent(dx); dx_alloc_setup_agent(dx); dx_server_setup_agent(dx); dx_container_setup_agent(dx); - dx_router_setup_agent(dx); + dx_router_setup_late(dx); return dx; } @@ -126,6 +137,7 @@ static void load_server_config(dx_dispatch_t *dx, dx_server_config_t *config, co { config->host = dx_config_item_value_string(dx->config, section, i, "addr"); config->port = dx_config_item_value_string(dx->config, section, i, "port"); + config->role = dx_config_item_value_string(dx->config, section, i, "role"); config->sasl_mechanisms = dx_config_item_value_string(dx->config, section, i, "sasl-mechanisms"); config->ssl_enabled = diff --git a/qpid/extras/dispatch/src/hash.c b/qpid/extras/dispatch/src/hash.c index 1f7d8aa3f5..0cb32acd05 100644 --- a/qpid/extras/dispatch/src/hash.c +++ b/qpid/extras/dispatch/src/hash.c @@ -23,18 +23,18 @@ #include <stdio.h> #include <string.h> -typedef struct hash_item_t { - DEQ_LINKS(struct hash_item_t); +typedef struct dx_hash_item_t { + DEQ_LINKS(struct dx_hash_item_t); unsigned char *key; union { void *val; const void *val_const; } v; -} hash_item_t; +} dx_hash_item_t; -ALLOC_DECLARE(hash_item_t); -ALLOC_DEFINE(hash_item_t); -DEQ_DECLARE(hash_item_t, items_t); +ALLOC_DECLARE(dx_hash_item_t); +ALLOC_DEFINE(dx_hash_item_t); +DEQ_DECLARE(dx_hash_item_t, items_t); typedef struct bucket_t { @@ -42,7 +42,7 @@ typedef struct bucket_t { } bucket_t; -struct hash_t { +struct dx_hash_t { bucket_t *buckets; unsigned int bucket_count; unsigned int bucket_mask; @@ -52,8 +52,17 @@ struct hash_t { }; +struct dx_hash_handle_t { + bucket_t *bucket; + dx_hash_item_t *item; +}; + +ALLOC_DECLARE(dx_hash_handle_t); +ALLOC_DEFINE(dx_hash_handle_t); + + // djb2 hash algorithm -static unsigned long hash_function(dx_field_iterator_t *iter) +static unsigned long dx_hash_function(dx_field_iterator_t *iter) { unsigned long hash = 5381; int c; @@ -68,10 +77,10 @@ static unsigned long hash_function(dx_field_iterator_t *iter) } -hash_t *hash(int bucket_exponent, int batch_size, int value_is_const) +dx_hash_t *dx_hash(int bucket_exponent, int batch_size, int value_is_const) { int i; - hash_t *h = NEW(hash_t); + dx_hash_t *h = NEW(dx_hash_t); if (!h) return 0; @@ -90,22 +99,22 @@ hash_t *hash(int bucket_exponent, int batch_size, int value_is_const) } -void hash_free(hash_t *h) +void dx_hash_free(dx_hash_t *h) { // TODO - Implement this } -size_t hash_size(hash_t *h) +size_t dx_hash_size(dx_hash_t *h) { return h ? h->size : 0; } -static hash_item_t *hash_internal_insert(hash_t *h, dx_field_iterator_t *key, int *exists) +static dx_hash_item_t *dx_hash_internal_insert(dx_hash_t *h, dx_field_iterator_t *key, int *exists, dx_hash_handle_t **handle) { - unsigned long idx = hash_function(key) & h->bucket_mask; - hash_item_t *item = DEQ_HEAD(h->buckets[idx].items); + unsigned long idx = dx_hash_function(key) & h->bucket_mask; + dx_hash_item_t *item = DEQ_HEAD(h->buckets[idx].items); while (item) { if (dx_field_iterator_equal(key, item->key)) @@ -115,10 +124,12 @@ static hash_item_t *hash_internal_insert(hash_t *h, dx_field_iterator_t *key, in if (item) { *exists = 1; + if (handle) + *handle = 0; return item; } - item = new_hash_item_t(); + item = new_dx_hash_item_t(); if (!item) return 0; @@ -128,14 +139,24 @@ static hash_item_t *hash_internal_insert(hash_t *h, dx_field_iterator_t *key, in DEQ_INSERT_TAIL(h->buckets[idx].items, item); h->size++; *exists = 0; + + // + // If a pointer to a handle-pointer was supplied, create a handle for this item. + // + if (handle) { + *handle = new_dx_hash_handle_t(); + (*handle)->bucket = &h->buckets[idx]; + (*handle)->item = item; + } + return item; } -dx_error_t hash_insert(hash_t *h, dx_field_iterator_t *key, void *val) +dx_error_t dx_hash_insert(dx_hash_t *h, dx_field_iterator_t *key, void *val, dx_hash_handle_t **handle) { - int exists = 0; - hash_item_t *item = hash_internal_insert(h, key, &exists); + int exists = 0; + dx_hash_item_t *item = dx_hash_internal_insert(h, key, &exists, handle); if (!item) return DX_ERROR_ALLOC; @@ -149,12 +170,12 @@ dx_error_t hash_insert(hash_t *h, dx_field_iterator_t *key, void *val) } -dx_error_t hash_insert_const(hash_t *h, dx_field_iterator_t *key, const void *val) +dx_error_t dx_hash_insert_const(dx_hash_t *h, dx_field_iterator_t *key, const void *val, dx_hash_handle_t **handle) { assert(h->is_const); - int error = 0; - hash_item_t *item = hash_internal_insert(h, key, &error); + int error = 0; + dx_hash_item_t *item = dx_hash_internal_insert(h, key, &error, handle); if (item) item->v.val_const = val; @@ -162,10 +183,10 @@ dx_error_t hash_insert_const(hash_t *h, dx_field_iterator_t *key, const void *va } -static hash_item_t *hash_internal_retrieve(hash_t *h, dx_field_iterator_t *key) +static dx_hash_item_t *dx_hash_internal_retrieve(dx_hash_t *h, dx_field_iterator_t *key) { - unsigned long idx = hash_function(key) & h->bucket_mask; - hash_item_t *item = DEQ_HEAD(h->buckets[idx].items); + unsigned long idx = dx_hash_function(key) & h->bucket_mask; + dx_hash_item_t *item = DEQ_HEAD(h->buckets[idx].items); while (item) { if (dx_field_iterator_equal(key, item->key)) @@ -177,9 +198,9 @@ static hash_item_t *hash_internal_retrieve(hash_t *h, dx_field_iterator_t *key) } -dx_error_t hash_retrieve(hash_t *h, dx_field_iterator_t *key, void **val) +dx_error_t dx_hash_retrieve(dx_hash_t *h, dx_field_iterator_t *key, void **val) { - hash_item_t *item = hash_internal_retrieve(h, key); + dx_hash_item_t *item = dx_hash_internal_retrieve(h, key); if (item) *val = item->v.val; else @@ -189,11 +210,11 @@ dx_error_t hash_retrieve(hash_t *h, dx_field_iterator_t *key, void **val) } -dx_error_t hash_retrieve_const(hash_t *h, dx_field_iterator_t *key, const void **val) +dx_error_t dx_hash_retrieve_const(dx_hash_t *h, dx_field_iterator_t *key, const void **val) { assert(h->is_const); - hash_item_t *item = hash_internal_retrieve(h, key); + dx_hash_item_t *item = dx_hash_internal_retrieve(h, key); if (item) *val = item->v.val_const; else @@ -203,10 +224,10 @@ dx_error_t hash_retrieve_const(hash_t *h, dx_field_iterator_t *key, const void * } -dx_error_t hash_remove(hash_t *h, dx_field_iterator_t *key) +dx_error_t dx_hash_remove(dx_hash_t *h, dx_field_iterator_t *key) { - unsigned long idx = hash_function(key) & h->bucket_mask; - hash_item_t *item = DEQ_HEAD(h->buckets[idx].items); + unsigned long idx = dx_hash_function(key) & h->bucket_mask; + dx_hash_item_t *item = DEQ_HEAD(h->buckets[idx].items); while (item) { if (dx_field_iterator_equal(key, item->key)) @@ -217,7 +238,7 @@ dx_error_t hash_remove(hash_t *h, dx_field_iterator_t *key) if (item) { free(item->key); DEQ_REMOVE(h->buckets[idx].items, item); - free_hash_item_t(item); + free_dx_hash_item_t(item); h->size--; return DX_ERROR_NONE; } @@ -225,3 +246,40 @@ dx_error_t hash_remove(hash_t *h, dx_field_iterator_t *key) return DX_ERROR_NOT_FOUND; } + +void dx_hash_handle_free(dx_hash_handle_t *handle) +{ + if (handle) + free_dx_hash_handle_t(handle); +} + + +const unsigned char *dx_hash_key_by_handle(const dx_hash_handle_t *handle) +{ + if (handle) + return handle->item->key; + return 0; +} + + +dx_error_t dx_hash_remove_by_handle(dx_hash_t *h, dx_hash_handle_t *handle) +{ + unsigned char *key = 0; + dx_error_t error = dx_hash_remove_by_handle2(h, handle, &key); + if (key) + free(key); + return error; +} + + +dx_error_t dx_hash_remove_by_handle2(dx_hash_t *h, dx_hash_handle_t *handle, unsigned char **key) +{ + if (!handle) + return DX_ERROR_NOT_FOUND; + *key = handle->item->key; + DEQ_REMOVE(handle->bucket->items, handle->item); + free_dx_hash_item_t(handle->item); + h->size--; + return DX_ERROR_NONE; +} + diff --git a/qpid/extras/dispatch/src/message.c b/qpid/extras/dispatch/src/message.c index 772c0d4e16..86ffcefcee 100644 --- a/qpid/extras/dispatch/src/message.c +++ b/qpid/extras/dispatch/src/message.c @@ -376,7 +376,7 @@ static dx_field_location_t *dx_message_field_location(dx_message_t *msg, dx_mess } -dx_message_t *dx_allocate_message() +dx_message_t *dx_message() { dx_message_pvt_t *msg = (dx_message_pvt_t*) new_dx_message_t(); if (!msg) @@ -400,7 +400,7 @@ dx_message_t *dx_allocate_message() } -void dx_free_message(dx_message_t *in_msg) +void dx_message_free(dx_message_t *in_msg) { uint32_t rc; dx_message_pvt_t *msg = (dx_message_pvt_t*) in_msg; @@ -417,14 +417,14 @@ void dx_free_message(dx_message_t *in_msg) dx_buffer_t *buf = DEQ_HEAD(content->buffers); while (buf) { DEQ_REMOVE_HEAD(content->buffers); - dx_free_buffer(buf); + dx_buffer_free(buf); buf = DEQ_HEAD(content->buffers); } buf = DEQ_HEAD(content->new_delivery_annotations); while (buf) { DEQ_REMOVE_HEAD(content->new_delivery_annotations); - dx_free_buffer(buf); + dx_buffer_free(buf); buf = DEQ_HEAD(content->new_delivery_annotations); } @@ -474,9 +474,11 @@ dx_parsed_field_t *dx_message_delivery_annotations(dx_message_t *in_msg) !dx_parse_is_map(content->parsed_delivery_annotations)) { dx_field_iterator_free(da); dx_parse_free(content->parsed_delivery_annotations); + content->parsed_delivery_annotations = 0; return 0; } + dx_field_iterator_free(da); return content->parsed_delivery_annotations; } @@ -506,7 +508,7 @@ dx_message_t *dx_message_receive(dx_delivery_t *delivery) // link it and the delivery together. // if (!msg) { - msg = (dx_message_pvt_t*) dx_allocate_message(); + msg = (dx_message_pvt_t*) dx_message(); dx_delivery_set_context(delivery, (void*) msg); } @@ -517,7 +519,7 @@ dx_message_t *dx_message_receive(dx_delivery_t *delivery) // buf = DEQ_TAIL(msg->content->buffers); if (!buf) { - buf = dx_allocate_buffer(); + buf = dx_buffer(); DEQ_INSERT_TAIL(msg->content->buffers, buf); } @@ -538,7 +540,7 @@ dx_message_t *dx_message_receive(dx_delivery_t *delivery) // if (dx_buffer_size(buf) == 0) { DEQ_REMOVE_TAIL(msg->content->buffers); - dx_free_buffer(buf); + dx_buffer_free(buf); } dx_delivery_set_context(delivery, 0); return (dx_message_t*) msg; @@ -556,7 +558,7 @@ dx_message_t *dx_message_receive(dx_delivery_t *delivery) // tail of the message's list. // if (dx_buffer_capacity(buf) == 0) { - buf = dx_allocate_buffer(); + buf = dx_buffer(); DEQ_INSERT_TAIL(msg->content->buffers, buf); } } else diff --git a/qpid/extras/dispatch/src/message_private.h b/qpid/extras/dispatch/src/message_private.h index 27b81bbb4c..c57cea5f0d 100644 --- a/qpid/extras/dispatch/src/message_private.h +++ b/qpid/extras/dispatch/src/message_private.h @@ -67,7 +67,7 @@ typedef struct { sys_mutex_t *lock; uint32_t ref_count; // The number of messages referencing this dx_buffer_list_t buffers; // The buffer chain containing the message - dx_buffer_list_t new_delivery_annotations; // The buffer chain containing the new delivery annotations + dx_buffer_list_t new_delivery_annotations; // The buffer chain containing the new delivery annotations (MOVE TO MSG_PVT) dx_field_location_t section_message_header; // The message header list dx_field_location_t section_delivery_annotation; // The delivery annotation map dx_field_location_t section_message_annotation; // The message annotation map diff --git a/qpid/extras/dispatch/src/python_embedded.c b/qpid/extras/dispatch/src/python_embedded.c index 0b0cc11025..a6e35018c6 100644 --- a/qpid/extras/dispatch/src/python_embedded.c +++ b/qpid/extras/dispatch/src/python_embedded.c @@ -398,11 +398,12 @@ typedef struct { PyObject *handler; PyObject *handler_rx_call; dx_dispatch_t *dx; - dx_address_t *address; + Py_ssize_t addr_count; + dx_address_t **addrs; } IoAdapter; -static void dx_io_rx_handler(void *context, dx_message_t *msg) +static void dx_io_rx_handler(void *context, dx_message_t *msg, int link_id) { IoAdapter *self = (IoAdapter*) context; @@ -451,42 +452,60 @@ static void dx_io_rx_handler(void *context, dx_message_t *msg) return; } + sys_mutex_lock(lock); PyObject *pAP = dx_field_to_py(ap_map); PyObject *pBody = dx_field_to_py(body_map); - PyObject *pArgs = PyTuple_New(2); + PyObject *pArgs = PyTuple_New(3); PyTuple_SetItem(pArgs, 0, pAP); PyTuple_SetItem(pArgs, 1, pBody); + PyTuple_SetItem(pArgs, 2, PyInt_FromLong((long) link_id)); PyObject *pValue = PyObject_CallObject(self->handler_rx_call, pArgs); Py_DECREF(pArgs); if (pValue) { Py_DECREF(pValue); } + sys_mutex_unlock(lock); + + dx_field_iterator_free(ap); + dx_field_iterator_free(body); + dx_parse_free(ap_map); + dx_parse_free(body_map); } static int IoAdapter_init(IoAdapter *self, PyObject *args, PyObject *kwds) { - const char *address; - if (!PyArg_ParseTuple(args, "Os", &self->handler, &address)) + PyObject *addrs; + if (!PyArg_ParseTuple(args, "OO", &self->handler, &addrs)) return -1; self->handler_rx_call = PyObject_GetAttrString(self->handler, "receive"); if (!self->handler_rx_call || !PyCallable_Check(self->handler_rx_call)) return -1; + if (!PyTuple_Check(addrs)) + return -1; + Py_INCREF(self->handler); Py_INCREF(self->handler_rx_call); - self->dx = dispatch; - self->address = dx_router_register_address(self->dx, address, dx_io_rx_handler, self); + self->dx = dispatch; + self->addr_count = PyTuple_Size(addrs); + self->addrs = NEW_PTR_ARRAY(dx_address_t, self->addr_count); + for (Py_ssize_t idx = 0; idx < self->addr_count; idx++) + self->addrs[idx] = dx_router_register_address(self->dx, + PyString_AS_STRING(PyTuple_GetItem(addrs, idx)), + dx_io_rx_handler, self); return 0; } static void IoAdapter_dealloc(IoAdapter* self) { - dx_router_unregister_address(self->address); + for (Py_ssize_t idx = 0; idx < self->addr_count; idx++) + dx_router_unregister_address(self->addrs[idx]); + free(self->addrs); Py_DECREF(self->handler); Py_DECREF(self->handler_rx_call); self->ob_type->tp_free((PyObject*)self); @@ -507,10 +526,10 @@ static PyObject* dx_python_send(PyObject *self, PyObject *args) field = dx_compose(DX_PERFORMATIVE_DELIVERY_ANNOTATIONS, field); dx_compose_start_map(field); - dx_compose_insert_string(field, "qdx.ingress"); + dx_compose_insert_string(field, DX_DA_INGRESS); dx_compose_insert_string(field, dx_router_id(ioa->dx)); - dx_compose_insert_string(field, "qdx.trace"); + dx_compose_insert_string(field, DX_DA_TRACE); dx_compose_start_list(field); dx_compose_insert_string(field, dx_router_id(ioa->dx)); dx_compose_end_list(field); @@ -530,10 +549,10 @@ static PyObject* dx_python_send(PyObject *self, PyObject *args) field = dx_compose(DX_PERFORMATIVE_BODY_AMQP_VALUE, field); dx_py_to_composed(body, field); - dx_message_t *msg = dx_allocate_message(); + dx_message_t *msg = dx_message(); dx_message_compose_2(msg, field); dx_router_send2(ioa->dx, address, msg); - dx_free_message(msg); + dx_message_free(msg); dx_compose_free(field); Py_INCREF(Py_None); @@ -625,7 +644,8 @@ static void dx_python_setup() // // Add LogAdapter // - Py_INCREF(&LogAdapterType); + PyTypeObject *laType = &LogAdapterType; + Py_INCREF(laType); PyModule_AddObject(m, "LogAdapter", (PyObject*) &LogAdapterType); dx_register_log_constant(m, "LOG_TRACE", LOG_TRACE); @@ -637,10 +657,22 @@ static void dx_python_setup() dx_register_log_constant(m, "LOG_CRITICAL", LOG_CRITICAL); // - Py_INCREF(&IoAdapterType); + PyTypeObject *ioaType = &IoAdapterType; + Py_INCREF(ioaType); PyModule_AddObject(m, "IoAdapter", (PyObject*) &IoAdapterType); Py_INCREF(m); dispatch_module = m; } } + +void dx_python_lock() +{ + sys_mutex_lock(lock); +} + +void dx_python_unlock() +{ + sys_mutex_unlock(lock); +} + diff --git a/qpid/extras/dispatch/src/router_agent.c b/qpid/extras/dispatch/src/router_agent.c new file mode 100644 index 0000000000..d4b719732c --- /dev/null +++ b/qpid/extras/dispatch/src/router_agent.c @@ -0,0 +1,187 @@ +/* + * 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. + */ + +#include <qpid/dispatch/python_embedded.h> +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <qpid/dispatch.h> +#include <qpid/dispatch/agent.h> +#include "dispatch_private.h" +#include "router_private.h" + +//static char *module = "router.agent"; + +#define DX_ROUTER_CLASS_ROUTER 1 +#define DX_ROUTER_CLASS_LINK 2 +#define DX_ROUTER_CLASS_NODE 3 +#define DX_ROUTER_CLASS_ADDRESS 4 + +typedef struct dx_router_class_t { + dx_router_t *router; + int class_id; +} dx_router_class_t; + + +static void dx_router_schema_handler(void *context, void *correlator) +{ +} + + +static const char *dx_router_addr_text(dx_address_t *addr) +{ + if (addr) { + const unsigned char *text = dx_hash_key_by_handle(addr->hash_handle); + if (text) + return (const char*) text; + } + return 0; +} + + +static void dx_router_query_router(dx_router_t *router, void *cor) +{ + dx_agent_value_string(cor, "area", router->router_area); + dx_agent_value_string(cor, "router_id", router->router_id); + + sys_mutex_lock(router->lock); + dx_agent_value_uint(cor, "addr_count", DEQ_SIZE(router->addrs)); + dx_agent_value_uint(cor, "link_count", DEQ_SIZE(router->links)); + dx_agent_value_uint(cor, "node_count", DEQ_SIZE(router->routers)); + sys_mutex_unlock(router->lock); + + dx_agent_value_complete(cor, 0); +} + + +static void dx_router_query_link(dx_router_t *router, void *cor) +{ + sys_mutex_lock(router->lock); + dx_router_link_t *link = DEQ_HEAD(router->links); + const char *link_type = "?"; + const char *link_dir; + + while (link) { + dx_agent_value_uint(cor, "index", link->mask_bit); + switch (link->link_type) { + case DX_LINK_ENDPOINT: link_type = "endpoint"; break; + case DX_LINK_ROUTER: link_type = "inter-router"; break; + case DX_LINK_AREA: link_type = "inter-area"; break; + } + dx_agent_value_string(cor, "link-type", link_type); + + if (link->link_direction == DX_INCOMING) + link_dir = "in"; + else + link_dir = "out"; + dx_agent_value_string(cor, "link-dir", link_dir); + + const char *text = dx_router_addr_text(link->owning_addr); + if (text) + dx_agent_value_string(cor, "owning-addr", text); + else + dx_agent_value_null(cor, "owning-addr"); + + link = DEQ_NEXT(link); + dx_agent_value_complete(cor, link != 0); + } + sys_mutex_unlock(router->lock); +} + + +static void dx_router_query_node(dx_router_t *router, void *cor) +{ + sys_mutex_lock(router->lock); + dx_router_node_t *node = DEQ_HEAD(router->routers); + while (node) { + dx_agent_value_uint(cor, "index", node->mask_bit); + dx_agent_value_string(cor, "addr", dx_router_addr_text(node->owning_addr)); + if (node->next_hop) + dx_agent_value_uint(cor, "next-hop", node->next_hop->mask_bit); + else + dx_agent_value_null(cor, "next-hop"); + if (node->peer_link) + dx_agent_value_uint(cor, "router-link", node->peer_link->mask_bit); + else + dx_agent_value_null(cor, "router-link"); + node = DEQ_NEXT(node); + dx_agent_value_complete(cor, node != 0); + } + sys_mutex_unlock(router->lock); +} + + +static void dx_router_query_address(dx_router_t *router, void *cor) +{ + sys_mutex_lock(router->lock); + dx_address_t *addr = DEQ_HEAD(router->addrs); + while (addr) { + dx_agent_value_string(cor, "addr", dx_router_addr_text(addr)); + dx_agent_value_boolean(cor, "in-process", addr->handler != 0); + dx_agent_value_uint(cor, "subscriber-count", DEQ_SIZE(addr->rlinks)); + dx_agent_value_uint(cor, "remote-count", DEQ_SIZE(addr->rnodes)); + dx_agent_value_uint(cor, "deliveries-ingress", addr->deliveries_ingress); + dx_agent_value_uint(cor, "deliveries-egress", addr->deliveries_egress); + dx_agent_value_uint(cor, "deliveries-transit", addr->deliveries_transit); + dx_agent_value_uint(cor, "deliveries-to-container", addr->deliveries_to_container); + dx_agent_value_uint(cor, "deliveries-from-container", addr->deliveries_from_container); + addr = DEQ_NEXT(addr); + dx_agent_value_complete(cor, addr != 0); + } + sys_mutex_unlock(router->lock); +} + + +static void dx_router_query_handler(void* context, const char *id, void *correlator) +{ + dx_router_class_t *cls = (dx_router_class_t*) context; + switch (cls->class_id) { + case DX_ROUTER_CLASS_ROUTER: dx_router_query_router(cls->router, correlator); break; + case DX_ROUTER_CLASS_LINK: dx_router_query_link(cls->router, correlator); break; + case DX_ROUTER_CLASS_NODE: dx_router_query_node(cls->router, correlator); break; + case DX_ROUTER_CLASS_ADDRESS: dx_router_query_address(cls->router, correlator); break; + } +} + + +static dx_agent_class_t *dx_router_setup_class(dx_router_t *router, const char *fqname, int id) +{ + dx_router_class_t *cls = NEW(dx_router_class_t); + cls->router = router; + cls->class_id = id; + + return dx_agent_register_class(router->dx, fqname, cls, + dx_router_schema_handler, + dx_router_query_handler); +} + + +void dx_router_agent_setup(dx_router_t *router) +{ + router->class_router = + dx_router_setup_class(router, "org.apache.qpid.dispatch.router", DX_ROUTER_CLASS_ROUTER); + router->class_link = + dx_router_setup_class(router, "org.apache.qpid.dispatch.router.link", DX_ROUTER_CLASS_LINK); + router->class_node = + dx_router_setup_class(router, "org.apache.qpid.dispatch.router.node", DX_ROUTER_CLASS_NODE); + router->class_address = + dx_router_setup_class(router, "org.apache.qpid.dispatch.router.address", DX_ROUTER_CLASS_ADDRESS); +} + diff --git a/qpid/extras/dispatch/src/router_node.c b/qpid/extras/dispatch/src/router_node.c index d2704c4bd5..931f4551bb 100644 --- a/qpid/extras/dispatch/src/router_node.c +++ b/qpid/extras/dispatch/src/router_node.c @@ -21,17 +21,17 @@ #include <stdio.h> #include <string.h> #include <stdbool.h> +#include <stdlib.h> #include <qpid/dispatch.h> #include "dispatch_private.h" +#include "router_private.h" static char *module = "ROUTER"; -static void dx_router_python_setup(dx_router_t *router); -static void dx_pyrouter_tick(dx_router_t *router); - -static char *router_address = "_local/qdxrouter"; -static char *local_prefix = "_local/"; -//static char *topo_prefix = "_topo/"; +static char *router_role = "inter-router"; +static char *local_prefix = "_local/"; +static char *topo_prefix = "_topo/"; +static char *direct_prefix; /** * Address Types and Processing: @@ -48,86 +48,197 @@ static char *local_prefix = "_local/"; * <mobile> M<mobile> forward handler */ +ALLOC_DEFINE(dx_routed_event_t); +ALLOC_DEFINE(dx_router_link_t); +ALLOC_DEFINE(dx_router_node_t); +ALLOC_DEFINE(dx_router_ref_t); +ALLOC_DEFINE(dx_router_link_ref_t); +ALLOC_DEFINE(dx_address_t); +ALLOC_DEFINE(dx_router_conn_t); -typedef struct dx_router_link_t dx_router_link_t; -typedef struct dx_router_node_t dx_router_node_t; +void dx_router_add_link_ref_LH(dx_router_link_ref_list_t *ref_list, dx_router_link_t *link) +{ + dx_router_link_ref_t *ref = new_dx_router_link_ref_t(); + DEQ_ITEM_INIT(ref); + ref->link = link; + link->ref = ref; + DEQ_INSERT_TAIL(*ref_list, ref); +} -typedef enum { - DX_LINK_ENDPOINT, // A link to a connected endpoint - DX_LINK_ROUTER, // A link to a peer router in the same area - DX_LINK_AREA // A link to a peer router in a different area (area boundary) -} dx_link_type_t; +void dx_router_del_link_ref_LH(dx_router_link_ref_list_t *ref_list, dx_router_link_t *link) +{ + if (link->ref) { + DEQ_REMOVE(*ref_list, link->ref); + free_dx_router_link_ref_t(link->ref); + link->ref = 0; + } +} -typedef struct dx_routed_event_t { - DEQ_LINKS(struct dx_routed_event_t); - dx_delivery_t *delivery; - dx_message_t *message; - bool settle; - uint64_t disposition; -} dx_routed_event_t; -ALLOC_DECLARE(dx_routed_event_t); -ALLOC_DEFINE(dx_routed_event_t); -DEQ_DECLARE(dx_routed_event_t, dx_routed_event_list_t); - - -struct dx_router_link_t { - DEQ_LINKS(dx_router_link_t); - dx_direction_t link_direction; - dx_link_type_t link_type; - dx_address_t *owning_addr; // [ref] Address record that owns this link - dx_link_t *link; // [own] Link pointer - dx_router_link_t *connected_link; // [ref] If this is a link-route, reference the connected link - dx_router_link_t *peer_link; // [ref] If this is a bidirectional link-route, reference the peer link - dx_routed_event_list_t event_fifo; // FIFO of outgoing delivery/link events (no messages) - dx_routed_event_list_t msg_fifo; // FIFO of outgoing message deliveries -}; - -ALLOC_DECLARE(dx_router_link_t); -ALLOC_DEFINE(dx_router_link_t); -DEQ_DECLARE(dx_router_link_t, dx_router_link_list_t); +void dx_router_add_node_ref_LH(dx_router_ref_list_t *ref_list, dx_router_node_t *rnode) +{ + dx_router_ref_t *ref = new_dx_router_ref_t(); + DEQ_ITEM_INIT(ref); + ref->router = rnode; + rnode->ref_count++; + DEQ_INSERT_TAIL(*ref_list, ref); +} -struct dx_router_node_t { - DEQ_LINKS(dx_router_node_t); - const char *id; - dx_router_node_t *next_hop; // Next hop node _if_ this is not a neighbor node - dx_router_link_t *peer_link; // Outgoing link _if_ this is a neighbor node - // list of valid origins (pointers to router_node) - (bit masks?) -}; -ALLOC_DECLARE(dx_router_node_t); -ALLOC_DEFINE(dx_router_node_t); -DEQ_DECLARE(dx_router_node_t, dx_router_node_list_t); +void dx_router_del_node_ref_LH(dx_router_ref_list_t *ref_list, dx_router_node_t *rnode) +{ + dx_router_ref_t *ref = DEQ_HEAD(*ref_list); + while (ref) { + if (ref->router == rnode) { + DEQ_REMOVE(*ref_list, ref); + free_dx_router_ref_t(ref); + rnode->ref_count--; + break; + } + ref = DEQ_NEXT(ref); + } +} -struct dx_address_t { - dx_router_message_cb handler; // In-Process Consumer - void *handler_context; - dx_router_link_list_t rlinks; // Locally-Connected Consumers - dx_router_node_list_t rnodes; // Remotely-Connected Consumers -}; +/** + * Check an address to see if it no longer has any associated destinations. + * Depending on its policy, the address may be eligible for being closed out + * (i.e. Logging its terminal statistics and freeing its resources). + */ +void dx_router_check_addr(dx_router_t *router, dx_address_t *addr, int was_local) +{ + if (addr == 0) + return; -ALLOC_DECLARE(dx_address_t); -ALLOC_DEFINE(dx_address_t); + unsigned char *key = 0; + int to_delete = 0; + int no_more_locals = 0; + + sys_mutex_lock(router->lock); + + // + // If the address has no handlers or destinations, it should be deleted. + // + if (addr->handler == 0 && DEQ_SIZE(addr->rlinks) == 0 && DEQ_SIZE(addr->rnodes) == 0) + to_delete = 1; + + // + // If we have just removed a local linkage and it was the last local linkage, + // we need to notify the router module that there is no longer a local + // presence of this address. + // + if (was_local && DEQ_SIZE(addr->rlinks) == 0) + no_more_locals = 1; + + if (to_delete) { + // + // Delete the address but grab the hash key so we can use it outside the + // critical section. + // + dx_hash_remove_by_handle2(router->addr_hash, addr->hash_handle, &key); + DEQ_REMOVE(router->addrs, addr); + dx_hash_handle_free(addr->hash_handle); + free_dx_address_t(addr); + } + + // + // If we're not deleting but there are no more locals, get a copy of the hash key. + // + if (!to_delete && no_more_locals) { + const unsigned char *key_const = dx_hash_key_by_handle(addr->hash_handle); + key = (unsigned char*) malloc(strlen((const char*) key_const) + 1); + strcpy((char*) key, (const char*) key_const); + } + + sys_mutex_unlock(router->lock); + // + // If the address is mobile-class and it was just removed from a local link, + // tell the router module that it is no longer attached locally. + // + if (no_more_locals && key && key[0] == 'M') + dx_router_mobile_removed(router, (const char*) key); -struct dx_router_t { - dx_dispatch_t *dx; - const char *router_area; - const char *router_id; - dx_node_t *node; - dx_router_link_list_t in_links; - dx_router_node_list_t routers; - dx_message_list_t in_fifo; - sys_mutex_t *lock; - dx_timer_t *timer; - hash_t *out_hash; - uint64_t dtag; - PyObject *pyRouter; - PyObject *pyTick; -}; + // + // Free the key that was not freed by the hash table. + // + if (key) + free(key); +} + + +/** + * Determine whether a connection is configured in the inter-router role. + */ +static int dx_router_connection_is_inter_router(const dx_connection_t *conn) +{ + if (!conn) + return 0; + + const dx_server_config_t *cf = dx_connection_config(conn); + if (cf && strcmp(cf->role, router_role) == 0) + return 1; + + return 0; +} + + +/** + * Determine whether a terminus has router capability + */ +static int dx_router_terminus_is_router(pn_terminus_t *term) +{ + pn_data_t *cap = pn_terminus_capabilities(term); + + pn_data_rewind(cap); + pn_data_next(cap); + if (cap && pn_data_type(cap) == PN_SYMBOL) { + pn_bytes_t sym = pn_data_get_symbol(cap); + if (sym.size == strlen(DX_CAPABILITY_ROUTER) && + strcmp(sym.start, DX_CAPABILITY_ROUTER) == 0) + return 1; + } + + return 0; +} + + +static void dx_router_generate_temp_addr(dx_router_t *router, char *buffer, size_t length) +{ + static const char *table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+_"; + char discriminator[11]; + long int rnd = random(); + int idx; + + for (idx = 0; idx < 6; idx++) + discriminator[idx] = table[(rnd >> (idx * 6)) & 63]; + discriminator[idx] = '\0'; + + snprintf(buffer, length, "amqp:/%s%s/%s/temp.%s", topo_prefix, router->router_area, router->router_id, discriminator); +} + + +static int dx_router_find_mask_bit_LH(dx_router_t *router, dx_link_t *link) +{ + dx_router_conn_t *shared = (dx_router_conn_t*) dx_link_get_conn_context(link); + if (shared) + return shared->mask_bit; + + int mask_bit; + if (dx_bitmask_first_set(router->neighbor_free_mask, &mask_bit)) { + dx_bitmask_clear_bit(router->neighbor_free_mask, mask_bit); + } else { + dx_log(module, LOG_CRITICAL, "Exceeded maximum inter-router link count"); + return -1; + } + + shared = new_dx_router_conn_t(); + shared->mask_bit = mask_bit; + dx_link_set_conn_context(link, shared); + return mask_bit; +} /** @@ -191,7 +302,7 @@ static int router_writable_link_handler(void* context, dx_link_t *link) DEQ_REMOVE_HEAD(to_send); // - // Get a delivery for the send. This will be the current deliver on the link. + // Get a delivery for the send. This will be the current delivery on the link. // tag++; delivery = dx_delivery(link, pn_dtag((char*) &tag, 8)); @@ -215,7 +326,7 @@ static int router_writable_link_handler(void* context, dx_link_t *link) pn_link_advance(pn_link); event_count++; - dx_free_message(re->message); + dx_message_free(re->message); free_dx_routed_event_t(re); re = DEQ_HEAD(to_send); } @@ -250,17 +361,18 @@ static int router_writable_link_handler(void* context, dx_link_t *link) } -static void router_annotate_message(dx_router_t *router, dx_message_t *msg) +static dx_field_iterator_t *router_annotate_message(dx_router_t *router, dx_message_t *msg, int *drop) { - dx_parsed_field_t *in_da = dx_message_delivery_annotations(msg); - dx_composed_field_t *out_da = dx_compose(DX_PERFORMATIVE_DELIVERY_ANNOTATIONS, 0); + dx_parsed_field_t *in_da = dx_message_delivery_annotations(msg); + dx_composed_field_t *out_da = dx_compose(DX_PERFORMATIVE_DELIVERY_ANNOTATIONS, 0); + dx_field_iterator_t *ingress_iter = 0; dx_parsed_field_t *trace = 0; dx_parsed_field_t *ingress = 0; if (in_da) { - trace = dx_parse_value_by_key(in_da, "qdx.trace"); - ingress = dx_parse_value_by_key(in_da, "qdx.ingress"); + trace = dx_parse_value_by_key(in_da, DX_DA_TRACE); + ingress = dx_parse_value_by_key(in_da, DX_DA_INGRESS); } dx_compose_start_map(out_da); @@ -268,38 +380,48 @@ static void router_annotate_message(dx_router_t *router, dx_message_t *msg) // // If there is a trace field, append this router's ID to the trace. // - if (trace && dx_parse_is_list(trace)) { - dx_compose_insert_string(out_da, "qdx.trace"); - dx_compose_start_list(out_da); - - uint32_t idx = 0; - dx_parsed_field_t *trace_item = dx_parse_sub_value(trace, idx); - while (trace_item) { - dx_field_iterator_t *iter = dx_parse_raw(trace_item); - dx_compose_insert_string_iterator(out_da, iter); - idx++; - trace_item = dx_parse_sub_value(trace, idx); + dx_compose_insert_string(out_da, DX_DA_TRACE); + dx_compose_start_list(out_da); + if (trace) { + if (dx_parse_is_list(trace)) { + uint32_t idx = 0; + dx_parsed_field_t *trace_item = dx_parse_sub_value(trace, idx); + while (trace_item) { + dx_field_iterator_t *iter = dx_parse_raw(trace_item); + if (dx_field_iterator_equal(iter, (unsigned char*) direct_prefix)) + *drop = 1; + dx_field_iterator_reset(iter); + dx_compose_insert_string_iterator(out_da, iter); + idx++; + trace_item = dx_parse_sub_value(trace, idx); + } } - - dx_compose_insert_string(out_da, router->router_id); - dx_compose_end_list(out_da); } + dx_compose_insert_string(out_da, direct_prefix); + dx_compose_end_list(out_da); + // // If there is no ingress field, annotate the ingress as this router else // keep the original field. // - dx_compose_insert_string(out_da, "qdx.ingress"); + dx_compose_insert_string(out_da, DX_DA_INGRESS); if (ingress && dx_parse_is_scalar(ingress)) { - dx_field_iterator_t *iter = dx_parse_raw(ingress); - dx_compose_insert_string_iterator(out_da, iter); + ingress_iter = dx_parse_raw(ingress); + dx_compose_insert_string_iterator(out_da, ingress_iter); } else - dx_compose_insert_string(out_da, router->router_id); + dx_compose_insert_string(out_da, direct_prefix); dx_compose_end_map(out_da); dx_message_set_delivery_annotations(msg, out_da); dx_compose_free(out_da); + + // + // Return the iterator to the ingress field _if_ it was present. + // If we added the ingress, return NULL. + // + return ingress_iter; } @@ -318,10 +440,7 @@ static void router_rx_handler(void* context, dx_link_t *link, dx_delivery_t *del // Receive the message into a local representation. If the returned message // pointer is NULL, we have not yet received a complete message. // - sys_mutex_lock(router->lock); msg = dx_message_receive(delivery); - sys_mutex_unlock(router->lock); - if (!msg) return; @@ -380,84 +499,155 @@ static void router_rx_handler(void* context, dx_link_t *link, dx_delivery_t *del if (iter) { dx_field_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH); - hash_retrieve(router->out_hash, iter, (void*) &addr); + + // + // Note: This function is going to need to be refactored so we can put an + // asynchronous address lookup here. In the event there is a translation + // of the address (via namespace), it will have to be done here after + // obtaining the iterator and before doing the hash lookup. + // + // Note that this lookup is only done for global/mobile class addresses. + // + + dx_hash_retrieve(router->addr_hash, iter, (void*) &addr); dx_field_iterator_reset_view(iter, ITER_VIEW_NO_HOST); - int is_local = dx_field_iterator_prefix(iter, local_prefix); + int is_local = dx_field_iterator_prefix(iter, local_prefix); + int is_direct = dx_field_iterator_prefix(iter, direct_prefix); dx_field_iterator_free(iter); if (addr) { // + // If the incoming link is an endpoint link, count this as an ingress delivery. + // + if (rlink->link_type == DX_LINK_ENDPOINT) + addr->deliveries_ingress++; + + // // To field is valid and contains a known destination. Handle the various // cases for forwarding. // // - // Interpret and update the delivery annotations of the message + // Interpret and update the delivery annotations of the message. As a convenience, + // this function returns the iterator to the ingress field (if it exists). // - router_annotate_message(router, msg); + int drop = 0; + dx_field_iterator_t *ingress_iter = router_annotate_message(router, msg, &drop); // // Forward to the in-process handler for this message if there is one. The // actual invocation of the handler will occur later after we've released // the lock. // - if (addr->handler) { + if (!drop && addr->handler) { in_process_copy = dx_message_copy(msg); handler = addr->handler; handler_context = addr->handler_context; + addr->deliveries_to_container++; } // // If the address form is local (i.e. is prefixed by _local), don't forward // outside of the router process. // - if (!is_local) { + if (!drop && !is_local) { // // Forward to all of the local links receiving this address. // - dx_router_link_t *dest_link = DEQ_HEAD(addr->rlinks); - while (dest_link) { + dx_router_link_ref_t *dest_link_ref = DEQ_HEAD(addr->rlinks); + while (dest_link_ref) { dx_routed_event_t *re = new_dx_routed_event_t(); DEQ_ITEM_INIT(re); re->delivery = 0; re->message = dx_message_copy(msg); re->settle = 0; re->disposition = 0; - DEQ_INSERT_TAIL(dest_link->msg_fifo, re); + DEQ_INSERT_TAIL(dest_link_ref->link->msg_fifo, re); fanout++; if (fanout == 1 && !dx_delivery_settled(delivery)) re->delivery = delivery; - dx_link_activate(dest_link->link); - dest_link = DEQ_NEXT(dest_link); + addr->deliveries_egress++; + dx_link_activate(dest_link_ref->link->link); + dest_link_ref = DEQ_NEXT(dest_link_ref); } // - // Forward to the next-hops for remote destinations. + // If the address form is direct to this router node, don't relay it on + // to any other part of the network. // - dx_router_node_t *dest_node = DEQ_HEAD(addr->rnodes); - while (dest_node) { - if (dest_node->next_hop) - dest_link = dest_node->next_hop->peer_link; - else - dest_link = dest_node->peer_link; - if (dest_link) { - dx_routed_event_t *re = new_dx_routed_event_t(); - DEQ_ITEM_INIT(re); - re->delivery = 0; - re->message = dx_message_copy(msg); - re->settle = 0; - re->disposition = 0; - DEQ_INSERT_TAIL(dest_link->msg_fifo, re); - - fanout++; - if (fanout == 1) - re->delivery = delivery; - - dx_link_activate(dest_link->link); + if (!is_direct) { + // + // Get the mask bit associated with the ingress router for the message. + // This will be compared against the "valid_origin" masks for each + // candidate destination router. + // + int origin = -1; + if (ingress_iter) { + dx_field_iterator_reset_view(ingress_iter, ITER_VIEW_ADDRESS_HASH); + dx_address_t *origin_addr; + dx_hash_retrieve(router->addr_hash, ingress_iter, (void*) &origin_addr); + if (origin_addr && DEQ_SIZE(origin_addr->rnodes) == 1) { + dx_router_ref_t *rref = DEQ_HEAD(origin_addr->rnodes); + origin = rref->router->mask_bit; + } + } else + origin = 0; + + // + // Forward to the next-hops for remote destinations. + // + if (origin >= 0) { + dx_router_ref_t *dest_node_ref = DEQ_HEAD(addr->rnodes); + dx_router_link_t *dest_link; + dx_bitmask_t *link_set = dx_bitmask(0); + + // + // Loop over the target nodes for this address. Build a set of outgoing links + // for which there are valid targets. We do this to avoid sending more than one + // message down a given link. It's possible that there are multiple destinations + // for this address that are all reachable over the same link. In this case, we + // will send only one copy of the message over the link and allow a downstream + // router to fan the message out. + // + while (dest_node_ref) { + if (dest_node_ref->router->next_hop) + dest_link = dest_node_ref->router->next_hop->peer_link; + else + dest_link = dest_node_ref->router->peer_link; + if (dest_link && dx_bitmask_value(dest_node_ref->router->valid_origins, origin)) + dx_bitmask_set_bit(link_set, dest_link->mask_bit); + dest_node_ref = DEQ_NEXT(dest_node_ref); + } + + // + // Send a copy of the message outbound on each identified link. + // + int link_bit; + while (dx_bitmask_first_set(link_set, &link_bit)) { + dx_bitmask_clear_bit(link_set, link_bit); + dest_link = router->out_links_by_mask_bit[link_bit]; + if (dest_link) { + dx_routed_event_t *re = new_dx_routed_event_t(); + DEQ_ITEM_INIT(re); + re->delivery = 0; + re->message = dx_message_copy(msg); + re->settle = 0; + re->disposition = 0; + DEQ_INSERT_TAIL(dest_link->msg_fifo, re); + + fanout++; + if (fanout == 1 && !dx_delivery_settled(delivery)) + re->delivery = delivery; + + addr->deliveries_transit++; + dx_link_activate(dest_link->link); + } + } + + dx_bitmask_free(link_set); } - dest_node = DEQ_NEXT(dest_node); } } } @@ -470,8 +660,7 @@ static void router_rx_handler(void* context, dx_link_t *link, dx_delivery_t *del dx_delivery_free(delivery, PN_ACCEPTED); } else if (fanout == 0) { dx_delivery_free(delivery, PN_RELEASED); - } else if (fanout > 1) - dx_delivery_free(delivery, PN_ACCEPTED); + } } } else { // @@ -481,13 +670,15 @@ static void router_rx_handler(void* context, dx_link_t *link, dx_delivery_t *del } sys_mutex_unlock(router->lock); - dx_free_message(msg); + dx_message_free(msg); // // Invoke the in-process handler now that the lock is released. // - if (handler) - handler(handler_context, in_process_copy); + if (handler) { + handler(handler_context, in_process_copy, rlink->mask_bit); + dx_message_free(in_process_copy); + } } @@ -538,28 +729,37 @@ static void router_disp_handler(void* context, dx_link_t *link, dx_delivery_t *d */ static int router_incoming_link_handler(void* context, dx_link_t *link) { - dx_router_t *router = (dx_router_t*) context; - dx_router_link_t *rlink = new_dx_router_link_t(); - pn_link_t *pn_link = dx_link_pn(link); + dx_router_t *router = (dx_router_t*) context; + pn_link_t *pn_link = dx_link_pn(link); + int is_router = dx_router_terminus_is_router(dx_link_remote_source(link)); + + if (is_router && !dx_router_connection_is_inter_router(dx_link_connection(link))) { + dx_log(module, LOG_WARNING, "Incoming link claims router capability but is not on an inter-router connection"); + pn_link_close(pn_link); + return 0; + } + dx_router_link_t *rlink = new_dx_router_link_t(); DEQ_ITEM_INIT(rlink); + rlink->link_type = is_router ? DX_LINK_ROUTER : DX_LINK_ENDPOINT; rlink->link_direction = DX_INCOMING; - rlink->link_type = DX_LINK_ENDPOINT; rlink->owning_addr = 0; rlink->link = link; rlink->connected_link = 0; rlink->peer_link = 0; + rlink->ref = 0; DEQ_INIT(rlink->event_fifo); DEQ_INIT(rlink->msg_fifo); dx_link_set_context(link, rlink); sys_mutex_lock(router->lock); - DEQ_INSERT_TAIL(router->in_links, rlink); + rlink->mask_bit = is_router ? dx_router_find_mask_bit_LH(router, link) : 0; + DEQ_INSERT_TAIL(router->links, rlink); sys_mutex_unlock(router->lock); - pn_terminus_copy(pn_link_source(pn_link), pn_link_remote_source(pn_link)); - pn_terminus_copy(pn_link_target(pn_link), pn_link_remote_target(pn_link)); + pn_terminus_copy(dx_link_source(link), dx_link_remote_source(link)); + pn_terminus_copy(dx_link_target(link), dx_link_remote_target(link)); pn_link_flow(pn_link, 1000); pn_link_open(pn_link); @@ -579,52 +779,125 @@ static int router_outgoing_link_handler(void* context, dx_link_t *link) { dx_router_t *router = (dx_router_t*) context; pn_link_t *pn_link = dx_link_pn(link); - const char *r_tgt = pn_terminus_get_address(pn_link_remote_target(pn_link)); + const char *r_src = pn_terminus_get_address(dx_link_remote_source(link)); + int is_dynamic = pn_terminus_is_dynamic(dx_link_remote_source(link)); + int is_router = dx_router_terminus_is_router(dx_link_remote_target(link)); + int propagate = 0; + dx_field_iterator_t *iter = 0; + + if (is_router && !dx_router_connection_is_inter_router(dx_link_connection(link))) { + dx_log(module, LOG_WARNING, "Outgoing link claims router capability but is not on an inter-router connection"); + pn_link_close(pn_link); + return 0; + } - if (!r_tgt) { + // + // If this link is not a router link and it has no source address, we can't + // accept it. + // + if (r_src == 0 && !is_router && !is_dynamic) { pn_link_close(pn_link); return 0; } - dx_field_iterator_t *iter = dx_field_iterator_string(r_tgt, ITER_VIEW_NO_HOST); - dx_router_link_t *rlink = new_dx_router_link_t(); - int is_router = dx_field_iterator_equal(iter, (unsigned char*) router_address); + // + // If this is an endpoint link with a source address, make sure the address is + // appropriate for endpoint links. If it is not mobile address, it cannot be + // bound to an endpoint link. + // + if(r_src && !is_router && !is_dynamic) { + iter = dx_field_iterator_string(r_src, ITER_VIEW_ADDRESS_HASH); + unsigned char prefix = dx_field_iterator_octet(iter); + dx_field_iterator_reset(iter); + if (prefix != 'M') { + dx_field_iterator_free(iter); + pn_link_close(pn_link); + dx_log(module, LOG_WARNING, "Rejected an outgoing endpoint link with a router address: %s", r_src); + return 0; + } + } + + // + // Create a router_link record for this link. Some of the fields will be + // modified in the different cases below. + // + dx_router_link_t *rlink = new_dx_router_link_t(); DEQ_ITEM_INIT(rlink); - rlink->link_direction = DX_OUTGOING; rlink->link_type = is_router ? DX_LINK_ROUTER : DX_LINK_ENDPOINT; + rlink->link_direction = DX_OUTGOING; + rlink->owning_addr = 0; rlink->link = link; rlink->connected_link = 0; rlink->peer_link = 0; + rlink->ref = 0; DEQ_INIT(rlink->event_fifo); DEQ_INIT(rlink->msg_fifo); dx_link_set_context(link, rlink); - - dx_field_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH); - dx_address_t *addr; + pn_terminus_copy(dx_link_source(link), dx_link_remote_source(link)); + pn_terminus_copy(dx_link_target(link), dx_link_remote_target(link)); sys_mutex_lock(router->lock); - hash_retrieve(router->out_hash, iter, (void**) &addr); - if (!addr) { - addr = new_dx_address_t(); - addr->handler = 0; - addr->handler_context = 0; - DEQ_INIT(addr->rlinks); - DEQ_INIT(addr->rnodes); - hash_insert(router->out_hash, iter, addr); + rlink->mask_bit = is_router ? dx_router_find_mask_bit_LH(router, link) : 0; + + if (is_router) { + // + // If this is a router link, put it in the hello_address link-list. + // + dx_router_add_link_ref_LH(&router->hello_addr->rlinks, rlink); + rlink->owning_addr = router->hello_addr; + router->out_links_by_mask_bit[rlink->mask_bit] = rlink; + + } else { + // + // If this is an endpoint link, check the source. If it is dynamic, we will + // assign it an ephemeral and routable address. If it has a non-dymanic + // address, that address needs to be set up in the address list. + // + char temp_addr[1000]; // FIXME + dx_address_t *addr; + + if (is_dynamic) { + dx_router_generate_temp_addr(router, temp_addr, 1000); + iter = dx_field_iterator_string(temp_addr, ITER_VIEW_ADDRESS_HASH); + pn_terminus_set_address(dx_link_source(link), temp_addr); + dx_log(module, LOG_INFO, "Assigned temporary routable address: %s", temp_addr); + } else + dx_log(module, LOG_INFO, "Registered local address: %s", r_src); + + dx_hash_retrieve(router->addr_hash, iter, (void**) &addr); + if (!addr) { + addr = new_dx_address_t(); + memset(addr, 0, sizeof(dx_address_t)); + DEQ_ITEM_INIT(addr); + DEQ_INIT(addr->rlinks); + DEQ_INIT(addr->rnodes); + dx_hash_insert(router->addr_hash, iter, addr, &addr->hash_handle); + DEQ_INSERT_TAIL(router->addrs, addr); + } + + rlink->owning_addr = addr; + dx_router_add_link_ref_LH(&addr->rlinks, rlink); + + // + // If this is not a dynamic address and it is the first local subscription + // to the address, supply the address to the router module for propagation + // to other nodes. + // + propagate = (!is_dynamic) && (DEQ_SIZE(addr->rlinks) == 1); } - dx_field_iterator_free(iter); - rlink->owning_addr = addr; - DEQ_INSERT_TAIL(addr->rlinks, rlink); + DEQ_INSERT_TAIL(router->links, rlink); + sys_mutex_unlock(router->lock); + + if (propagate) + dx_router_mobile_added(router, iter); - pn_terminus_copy(pn_link_source(pn_link), pn_link_remote_source(pn_link)); - pn_terminus_copy(pn_link_target(pn_link), pn_link_remote_target(pn_link)); + if (iter) + dx_field_iterator_free(iter); pn_link_open(pn_link); - sys_mutex_unlock(router->lock); - dx_log(module, LOG_TRACE, "Registered new local address: %s", r_tgt); return 0; } @@ -634,40 +907,60 @@ static int router_outgoing_link_handler(void* context, dx_link_t *link) */ static int router_link_detach_handler(void* context, dx_link_t *link, int closed) { - dx_router_t *router = (dx_router_t*) context; - pn_link_t *pn_link = dx_link_pn(link); - dx_router_link_t *rlink = (dx_router_link_t*) dx_link_get_context(link); - const char *r_tgt = pn_terminus_get_address(pn_link_remote_target(pn_link)); + dx_router_t *router = (dx_router_t*) context; + dx_router_link_t *rlink = (dx_router_link_t*) dx_link_get_context(link); + dx_router_conn_t *shared = (dx_router_conn_t*) dx_link_get_conn_context(link); + dx_address_t *oaddr = 0; + + if (shared) { + dx_link_set_conn_context(link, 0); + free_dx_router_conn_t(shared); + } if (!rlink) return 0; sys_mutex_lock(router->lock); - if (pn_link_is_sender(pn_link)) { - DEQ_REMOVE(rlink->owning_addr->rlinks, rlink); - - if ((rlink->owning_addr->handler == 0) && - (DEQ_SIZE(rlink->owning_addr->rlinks) == 0) && - (DEQ_SIZE(rlink->owning_addr->rnodes) == 0)) { - dx_field_iterator_t *iter = dx_field_iterator_string(r_tgt, ITER_VIEW_ADDRESS_HASH); - dx_address_t *addr; - if (iter) { - hash_retrieve(router->out_hash, iter, (void**) &addr); - if (addr == rlink->owning_addr) { - hash_remove(router->out_hash, iter); - free_dx_router_link_t(rlink); - free_dx_address_t(addr); - dx_log(module, LOG_TRACE, "Removed local address: %s", r_tgt); - } - dx_field_iterator_free(iter); - } - } - } else { - DEQ_REMOVE(router->in_links, rlink); - free_dx_router_link_t(rlink); + + // + // If the link is outgoing, we must disassociate it from its address. + // + if (rlink->link_direction == DX_OUTGOING && rlink->owning_addr) { + dx_router_del_link_ref_LH(&rlink->owning_addr->rlinks, rlink); + oaddr = rlink->owning_addr; } + // + // If this is an outgoing inter-router link, we must remove the by-mask-bit + // index reference to this link. + // + if (rlink->link_type == DX_LINK_ROUTER && rlink->link_direction == DX_OUTGOING) { + if (router->out_links_by_mask_bit[rlink->mask_bit] == rlink) + router->out_links_by_mask_bit[rlink->mask_bit] = 0; + else + dx_log(module, LOG_CRITICAL, "Outgoing router link closing but not in index: bit=%d", rlink->mask_bit); + } + + // + // If this is an incoming inter-router link, we must free the mask_bit. + // + if (rlink->link_type == DX_LINK_ROUTER && rlink->link_direction == DX_INCOMING) + dx_bitmask_set_bit(router->neighbor_free_mask, rlink->mask_bit); + + // + // Remove the link from the master list-of-links. + // + DEQ_REMOVE(router->links, rlink); sys_mutex_unlock(router->lock); + + // + // Check to see if the owning address should be deleted + // + dx_router_check_addr(router, oaddr, 1); + + // TODO - wrap the free to handle the recursive items + free_dx_router_link_t(rlink); + return 0; } @@ -679,28 +972,47 @@ static void router_inbound_open_handler(void *type_context, dx_connection_t *con static void router_outbound_open_handler(void *type_context, dx_connection_t *conn) { - // TODO - Make sure this connection is annotated as an inter-router transport. - // Ignore otherwise + // + // Check the configured role of this connection. If it is not the inter-router + // role, ignore it. + // + if (!dx_router_connection_is_inter_router(conn)) { + dx_log(module, LOG_WARNING, "Outbound connection set up without inter-router role"); + return; + } dx_router_t *router = (dx_router_t*) type_context; - dx_field_iterator_t *aiter = dx_field_iterator_string(router_address, ITER_VIEW_ADDRESS_HASH); dx_link_t *sender; dx_link_t *receiver; dx_router_link_t *rlink; + int mask_bit = 0; + size_t clen = strlen(DX_CAPABILITY_ROUTER); // - // Create an incoming link and put it in the in-links collection. The address - // of the remote source of this link is '_local/qdxrouter'. + // Allocate a mask bit to designate the pair of links connected to the neighbor router // - receiver = dx_link(router->node, conn, DX_INCOMING, "inter-router-rx"); - pn_terminus_set_address(dx_link_remote_source(receiver), router_address); - pn_terminus_set_address(dx_link_target(receiver), router_address); + sys_mutex_lock(router->lock); + if (dx_bitmask_first_set(router->neighbor_free_mask, &mask_bit)) { + dx_bitmask_clear_bit(router->neighbor_free_mask, mask_bit); + } else { + sys_mutex_unlock(router->lock); + dx_log(module, LOG_CRITICAL, "Exceeded maximum inter-router link count"); + return; + } - rlink = new_dx_router_link_t(); + // + // Create an incoming link with router source capability + // + receiver = dx_link(router->node, conn, DX_INCOMING, DX_INTERNODE_LINK_NAME_1); + // TODO - We don't want to have to cast away the constness of the literal string here! + // See PROTON-429 + pn_data_put_symbol(pn_terminus_capabilities(dx_link_target(receiver)), pn_bytes(clen, (char*) DX_CAPABILITY_ROUTER)); + rlink = new_dx_router_link_t(); DEQ_ITEM_INIT(rlink); - rlink->link_direction = DX_INCOMING; + rlink->mask_bit = mask_bit; rlink->link_type = DX_LINK_ROUTER; + rlink->link_direction = DX_INCOMING; rlink->owning_addr = 0; rlink->link = receiver; rlink->connected_link = 0; @@ -709,53 +1021,46 @@ static void router_outbound_open_handler(void *type_context, dx_connection_t *co DEQ_INIT(rlink->msg_fifo); dx_link_set_context(receiver, rlink); - - sys_mutex_lock(router->lock); - DEQ_INSERT_TAIL(router->in_links, rlink); - sys_mutex_unlock(router->lock); + DEQ_INSERT_TAIL(router->links, rlink); // - // Create an outgoing link with a local source of '_local/qdxrouter' and place - // it in the routing table. + // Create an outgoing link with router target capability // - sender = dx_link(router->node, conn, DX_OUTGOING, "inter-router-tx"); - pn_terminus_set_address(dx_link_remote_target(sender), router_address); - pn_terminus_set_address(dx_link_source(sender), router_address); + sender = dx_link(router->node, conn, DX_OUTGOING, DX_INTERNODE_LINK_NAME_2); + // TODO - We don't want to have to cast away the constness of the literal string here! + // See PROTON-429 + pn_data_put_symbol(pn_terminus_capabilities(dx_link_source(sender)), pn_bytes(clen, (char *) DX_CAPABILITY_ROUTER)); rlink = new_dx_router_link_t(); - DEQ_ITEM_INIT(rlink); - rlink->link_direction = DX_OUTGOING; + rlink->mask_bit = mask_bit; rlink->link_type = DX_LINK_ROUTER; + rlink->link_direction = DX_OUTGOING; + rlink->owning_addr = router->hello_addr; rlink->link = sender; rlink->connected_link = 0; rlink->peer_link = 0; DEQ_INIT(rlink->event_fifo); DEQ_INIT(rlink->msg_fifo); - dx_link_set_context(sender, rlink); - - dx_address_t *addr; + // + // Add the new outgoing link to the hello_address's list of links. + // + dx_router_add_link_ref_LH(&router->hello_addr->rlinks, rlink); - sys_mutex_lock(router->lock); - hash_retrieve(router->out_hash, aiter, (void**) &addr); - if (!addr) { - addr = new_dx_address_t(); - addr->handler = 0; - addr->handler_context = 0; - DEQ_INIT(addr->rlinks); - DEQ_INIT(addr->rnodes); - hash_insert(router->out_hash, aiter, addr); - } + // + // Index this link from the by-maskbit index so we can later find it quickly + // when provided with the mask bit. + // + router->out_links_by_mask_bit[mask_bit] = rlink; - rlink->owning_addr = addr; - DEQ_INSERT_TAIL(addr->rlinks, rlink); + dx_link_set_context(sender, rlink); + DEQ_INSERT_TAIL(router->links, rlink); sys_mutex_unlock(router->lock); pn_link_open(dx_link_pn(receiver)); pn_link_open(dx_link_pn(sender)); pn_link_flow(dx_link_pn(receiver), 1000); - dx_field_iterator_free(aiter); } @@ -767,7 +1072,6 @@ static void dx_router_timer_handler(void *context) // Periodic processing. // dx_pyrouter_tick(router); - dx_timer_schedule(router->timer, 1000); } @@ -786,31 +1090,61 @@ static dx_node_type_t router_node = {"router", 0, 0, static int type_registered = 0; -dx_router_t *dx_router(dx_dispatch_t *dx, const char *area, const char *id) +dx_router_t *dx_router(dx_dispatch_t *dx, dx_router_mode_t mode, const char *area, const char *id) { if (!type_registered) { type_registered = 1; dx_container_register_node_type(dx, &router_node); } + size_t dplen = 9 + strlen(area) + strlen(id); + direct_prefix = (char*) malloc(dplen); + strcpy(direct_prefix, "_topo/"); + strcat(direct_prefix, area); + strcat(direct_prefix, "/"); + strcat(direct_prefix, id); + strcat(direct_prefix, "/"); + dx_router_t *router = NEW(dx_router_t); router_node.type_context = router; + dx->router = router; router->dx = dx; + router->router_mode = mode; router->router_area = area; router->router_id = id; router->node = dx_container_set_default_node_type(dx, &router_node, (void*) router, DX_DIST_BOTH); - DEQ_INIT(router->in_links); + DEQ_INIT(router->addrs); + router->addr_hash = dx_hash(10, 32, 0); + + DEQ_INIT(router->links); DEQ_INIT(router->routers); - DEQ_INIT(router->in_fifo); - router->lock = sys_mutex(); - router->timer = dx_timer(dx, dx_router_timer_handler, (void*) router); - router->out_hash = hash(10, 32, 0); - router->dtag = 1; - router->pyRouter = 0; - router->pyTick = 0; + router->out_links_by_mask_bit = NEW_PTR_ARRAY(dx_router_link_t, dx_bitmask_width()); + router->routers_by_mask_bit = NEW_PTR_ARRAY(dx_router_node_t, dx_bitmask_width()); + for (int idx = 0; idx < dx_bitmask_width(); idx++) { + router->out_links_by_mask_bit[idx] = 0; + router->routers_by_mask_bit[idx] = 0; + } + + router->neighbor_free_mask = dx_bitmask(1); + router->lock = sys_mutex(); + router->timer = dx_timer(dx, dx_router_timer_handler, (void*) router); + router->dtag = 1; + router->pyRouter = 0; + router->pyTick = 0; + router->pyAdded = 0; + router->pyRemoved = 0; + + // + // Create addresses for all of the routers in the topology. It will be registered + // locally later in the initialization sequence. + // + if (router->router_mode == DX_ROUTER_MODE_INTERIOR) { + router->router_addr = dx_router_register_address(dx, "qdxrouter", 0, 0); + router->hello_addr = dx_router_register_address(dx, "qdxhello", 0, 0); + } // // Inform the field iterator module of this router's id and area. The field iterator @@ -823,18 +1157,21 @@ dx_router_t *dx_router(dx_dispatch_t *dx, const char *area, const char *id) // dx_python_start(); - dx_log(module, LOG_INFO, "Router started, area=%s id=%s", area, id); + switch (router->router_mode) { + case DX_ROUTER_MODE_STANDALONE: dx_log(module, LOG_INFO, "Router started in Standalone mode"); break; + case DX_ROUTER_MODE_INTERIOR: dx_log(module, LOG_INFO, "Router started in Interior mode, area=%s id=%s", area, id); break; + case DX_ROUTER_MODE_EDGE: dx_log(module, LOG_INFO, "Router started in Edge mode"); break; + } return router; } -void dx_router_setup_agent(dx_dispatch_t *dx) +void dx_router_setup_late(dx_dispatch_t *dx) { + dx_router_agent_setup(dx->router); dx_router_python_setup(dx->router); dx_timer_schedule(dx->router->timer, 1000); - - // TODO } @@ -849,8 +1186,7 @@ void dx_router_free(dx_router_t *router) const char *dx_router_id(const dx_dispatch_t *dx) { - dx_router_t *router = dx->router; - return router->router_id; + return direct_prefix; } @@ -869,14 +1205,16 @@ dx_address_t *dx_router_register_address(dx_dispatch_t *dx, iter = dx_field_iterator_string(addr_string, ITER_VIEW_NO_HOST); sys_mutex_lock(router->lock); - hash_retrieve(router->out_hash, iter, (void**) &addr); + dx_hash_retrieve(router->addr_hash, iter, (void**) &addr); if (!addr) { addr = new_dx_address_t(); - addr->handler = 0; - addr->handler_context = 0; + memset(addr, 0, sizeof(dx_address_t)); + DEQ_ITEM_INIT(addr); DEQ_INIT(addr->rlinks); DEQ_INIT(addr->rnodes); - hash_insert(router->out_hash, iter, addr); + dx_hash_insert(router->addr_hash, iter, addr, &addr->hash_handle); + DEQ_ITEM_INIT(addr); + DEQ_INSERT_TAIL(router->addrs, addr); } dx_field_iterator_free(iter); @@ -885,7 +1223,8 @@ dx_address_t *dx_router_register_address(dx_dispatch_t *dx, sys_mutex_unlock(router->lock); - dx_log(module, LOG_TRACE, "In-Process Address Registered: %s", address); + if (handler) + dx_log(module, LOG_INFO, "In-Process Address Registered: %s", address); return addr; } @@ -905,34 +1244,49 @@ void dx_router_send(dx_dispatch_t *dx, dx_field_iterator_reset_view(address, ITER_VIEW_ADDRESS_HASH); sys_mutex_lock(router->lock); - hash_retrieve(router->out_hash, address, (void*) &addr); + dx_hash_retrieve(router->addr_hash, address, (void*) &addr); if (addr) { // // Forward to all of the local links receiving this address. // - dx_router_link_t *dest_link = DEQ_HEAD(addr->rlinks); - while (dest_link) { + addr->deliveries_from_container++; + dx_router_link_ref_t *dest_link_ref = DEQ_HEAD(addr->rlinks); + while (dest_link_ref) { dx_routed_event_t *re = new_dx_routed_event_t(); DEQ_ITEM_INIT(re); re->delivery = 0; re->message = dx_message_copy(msg); re->settle = 0; re->disposition = 0; - DEQ_INSERT_TAIL(dest_link->msg_fifo, re); + DEQ_INSERT_TAIL(dest_link_ref->link->msg_fifo, re); - dx_link_activate(dest_link->link); - dest_link = DEQ_NEXT(dest_link); + dx_link_activate(dest_link_ref->link->link); + addr->deliveries_egress++; + + dest_link_ref = DEQ_NEXT(dest_link_ref); } // // Forward to the next-hops for remote destinations. // - dx_router_node_t *dest_node = DEQ_HEAD(addr->rnodes); - while (dest_node) { - if (dest_node->next_hop) - dest_link = dest_node->next_hop->peer_link; + dx_router_ref_t *dest_node_ref = DEQ_HEAD(addr->rnodes); + dx_router_link_t *dest_link; + dx_bitmask_t *link_set = dx_bitmask(0); + + while (dest_node_ref) { + if (dest_node_ref->router->next_hop) + dest_link = dest_node_ref->router->next_hop->peer_link; else - dest_link = dest_node->peer_link; + dest_link = dest_node_ref->router->peer_link; + if (dest_link) + dx_bitmask_set_bit(link_set, dest_link->mask_bit); + dest_node_ref = DEQ_NEXT(dest_node_ref); + } + + int link_bit; + while (dx_bitmask_first_set(link_set, &link_bit)) { + dx_bitmask_clear_bit(link_set, link_bit); + dest_link = router->out_links_by_mask_bit[link_bit]; if (dest_link) { dx_routed_event_t *re = new_dx_routed_event_t(); DEQ_ITEM_INIT(re); @@ -942,9 +1296,11 @@ void dx_router_send(dx_dispatch_t *dx, re->disposition = 0; DEQ_INSERT_TAIL(dest_link->msg_fifo, re); dx_link_activate(dest_link->link); + addr->deliveries_transit++; } - dest_node = DEQ_NEXT(dest_node); } + + dx_bitmask_free(link_set); } sys_mutex_unlock(router->lock); // TOINVESTIGATE Move this higher? } @@ -959,222 +1315,3 @@ void dx_router_send2(dx_dispatch_t *dx, dx_field_iterator_free(iter); } - -//=============================================================================== -// Python Router Adapter -//=============================================================================== - -typedef struct { - PyObject_HEAD - dx_router_t *router; -} RouterAdapter; - - -static PyObject* dx_router_node_updated(PyObject *self, PyObject *args) -{ - //RouterAdapter *adapter = (RouterAdapter*) self; - //dx_router_t *router = adapter->router; - const char *address; - int is_reachable; - int is_neighbor; - - if (!PyArg_ParseTuple(args, "sii", &address, &is_reachable, &is_neighbor)) - return 0; - - // TODO - - Py_INCREF(Py_None); - return Py_None; -} - - -static PyObject* dx_router_add_route(PyObject *self, PyObject *args) -{ - //RouterAdapter *adapter = (RouterAdapter*) self; - const char *addr; - const char *peer; - - if (!PyArg_ParseTuple(args, "ss", &addr, &peer)) - return 0; - - // TODO - - Py_INCREF(Py_None); - return Py_None; -} - - -static PyObject* dx_router_del_route(PyObject *self, PyObject *args) -{ - //RouterAdapter *adapter = (RouterAdapter*) self; - const char *addr; - const char *peer; - - if (!PyArg_ParseTuple(args, "ss", &addr, &peer)) - return 0; - - // TODO - - Py_INCREF(Py_None); - return Py_None; -} - - -static PyMethodDef RouterAdapter_methods[] = { - {"node_updated", dx_router_node_updated, METH_VARARGS, "Update the status of a remote router node"}, - {"add_route", dx_router_add_route, METH_VARARGS, "Add a newly discovered route"}, - {"del_route", dx_router_del_route, METH_VARARGS, "Delete a route"}, - {0, 0, 0, 0} -}; - -static PyTypeObject RouterAdapterType = { - PyObject_HEAD_INIT(0) - 0, /* ob_size*/ - "dispatch.RouterAdapter", /* tp_name*/ - sizeof(RouterAdapter), /* tp_basicsize*/ - 0, /* tp_itemsize*/ - 0, /* tp_dealloc*/ - 0, /* tp_print*/ - 0, /* tp_getattr*/ - 0, /* tp_setattr*/ - 0, /* tp_compare*/ - 0, /* tp_repr*/ - 0, /* tp_as_number*/ - 0, /* tp_as_sequence*/ - 0, /* tp_as_mapping*/ - 0, /* tp_hash */ - 0, /* tp_call*/ - 0, /* tp_str*/ - 0, /* tp_getattro*/ - 0, /* tp_setattro*/ - 0, /* tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /* tp_flags*/ - "Dispatch Router Adapter", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - RouterAdapter_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ - 0, /* tp_free */ - 0, /* tp_is_gc */ - 0, /* tp_bases */ - 0, /* tp_mro */ - 0, /* tp_cache */ - 0, /* tp_subclasses */ - 0, /* tp_weaklist */ - 0, /* tp_del */ - 0 /* tp_version_tag */ -}; - - -static void dx_router_python_setup(dx_router_t *router) -{ - PyObject *pDispatchModule = dx_python_module(); - - RouterAdapterType.tp_new = PyType_GenericNew; - if (PyType_Ready(&RouterAdapterType) < 0) { - PyErr_Print(); - dx_log(module, LOG_CRITICAL, "Unable to initialize the Python Router Adapter"); - return; - } - - Py_INCREF(&RouterAdapterType); - PyModule_AddObject(pDispatchModule, "RouterAdapter", (PyObject*) &RouterAdapterType); - - // - // Attempt to import the Python Router module - // - PyObject* pName; - PyObject* pId; - PyObject* pArea; - PyObject* pModule; - PyObject* pClass; - PyObject* pArgs; - - pName = PyString_FromString("qpid.dispatch.router"); - pModule = PyImport_Import(pName); - Py_DECREF(pName); - if (!pModule) { - dx_log(module, LOG_CRITICAL, "Can't Locate 'router' Python module"); - return; - } - - pClass = PyObject_GetAttrString(pModule, "RouterEngine"); - if (!pClass || !PyClass_Check(pClass)) { - dx_log(module, LOG_CRITICAL, "Can't Locate 'RouterEngine' class in the 'router' module"); - return; - } - - PyObject *adapterType = PyObject_GetAttrString(pDispatchModule, "RouterAdapter"); - PyObject *adapterInstance = PyObject_CallObject(adapterType, 0); - assert(adapterInstance); - - ((RouterAdapter*) adapterInstance)->router = router; - - // - // Constructor Arguments for RouterEngine - // - pArgs = PyTuple_New(3); - - // arg 0: adapter instance - PyTuple_SetItem(pArgs, 0, adapterInstance); - - // arg 1: router_id - pId = PyString_FromString(router->router_id); - PyTuple_SetItem(pArgs, 1, pId); - - // arg 2: area id - pArea = PyString_FromString(router->router_area); - PyTuple_SetItem(pArgs, 2, pArea); - - // - // Instantiate the router - // - router->pyRouter = PyInstance_New(pClass, pArgs, 0); - Py_DECREF(pArgs); - Py_DECREF(adapterType); - - if (!router->pyRouter) { - PyErr_Print(); - dx_log(module, LOG_CRITICAL, "'RouterEngine' class cannot be instantiated"); - return; - } - - router->pyTick = PyObject_GetAttrString(router->pyRouter, "handleTimerTick"); - if (!router->pyTick || !PyCallable_Check(router->pyTick)) { - dx_log(module, LOG_CRITICAL, "'RouterEngine' class has no handleTimerTick method"); - return; - } -} - - -static void dx_pyrouter_tick(dx_router_t *router) -{ - PyObject *pArgs; - PyObject *pValue; - - if (router->pyTick) { - pArgs = PyTuple_New(0); - pValue = PyObject_CallObject(router->pyTick, pArgs); - if (PyErr_Occurred()) { - PyErr_Print(); - } - Py_DECREF(pArgs); - if (pValue) { - Py_DECREF(pValue); - } - } -} - diff --git a/qpid/extras/dispatch/src/router_private.h b/qpid/extras/dispatch/src/router_private.h new file mode 100644 index 0000000000..066188b3f7 --- /dev/null +++ b/qpid/extras/dispatch/src/router_private.h @@ -0,0 +1,177 @@ +#ifndef __router_private_h__ +#define __router_private_h__ 1 +/* + * 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. + */ + +typedef struct dx_router_link_t dx_router_link_t; +typedef struct dx_router_node_t dx_router_node_t; +typedef struct dx_router_ref_t dx_router_ref_t; +typedef struct dx_router_link_ref_t dx_router_link_ref_t; +typedef struct dx_router_conn_t dx_router_conn_t; + +void dx_router_python_setup(dx_router_t *router); +void dx_pyrouter_tick(dx_router_t *router); +void dx_router_agent_setup(dx_router_t *router); + +typedef enum { + DX_ROUTER_MODE_STANDALONE, // Standalone router. No routing protocol participation + DX_ROUTER_MODE_INTERIOR, // Interior router. Full participation in routing protocol. + DX_ROUTER_MODE_EDGE // Edge router. No routing protocol participation, access via other protocols. +} dx_router_mode_t; + +typedef enum { + DX_LINK_ENDPOINT, // A link to a connected endpoint + DX_LINK_ROUTER, // A link to a peer router in the same area + DX_LINK_AREA // A link to a peer router in a different area (area boundary) +} dx_link_type_t; + + +typedef struct dx_routed_event_t { + DEQ_LINKS(struct dx_routed_event_t); + dx_delivery_t *delivery; + dx_message_t *message; + bool settle; + uint64_t disposition; +} dx_routed_event_t; + +ALLOC_DECLARE(dx_routed_event_t); +DEQ_DECLARE(dx_routed_event_t, dx_routed_event_list_t); + + +struct dx_router_link_t { + DEQ_LINKS(dx_router_link_t); + int mask_bit; // Unique mask bit if this is an inter-router link + dx_link_type_t link_type; + dx_direction_t link_direction; + dx_address_t *owning_addr; // [ref] Address record that owns this link + dx_link_t *link; // [own] Link pointer + dx_router_link_t *connected_link; // [ref] If this is a link-route, reference the connected link + dx_router_link_t *peer_link; // [ref] If this is a bidirectional link-route, reference the peer link + dx_router_link_ref_t *ref; // Pointer to a containing reference object + dx_routed_event_list_t event_fifo; // FIFO of outgoing delivery/link events (no messages) + dx_routed_event_list_t msg_fifo; // FIFO of outgoing message deliveries +}; + +ALLOC_DECLARE(dx_router_link_t); +DEQ_DECLARE(dx_router_link_t, dx_router_link_list_t); + +struct dx_router_node_t { + DEQ_LINKS(dx_router_node_t); + dx_address_t *owning_addr; + int mask_bit; + dx_router_node_t *next_hop; // Next hop node _if_ this is not a neighbor node + dx_router_link_t *peer_link; // Outgoing link _if_ this is a neighbor node + uint32_t ref_count; + dx_bitmask_t *valid_origins; +}; + +ALLOC_DECLARE(dx_router_node_t); +DEQ_DECLARE(dx_router_node_t, dx_router_node_list_t); + +struct dx_router_ref_t { + DEQ_LINKS(dx_router_ref_t); + dx_router_node_t *router; +}; + +ALLOC_DECLARE(dx_router_ref_t); +DEQ_DECLARE(dx_router_ref_t, dx_router_ref_list_t); + + +struct dx_router_link_ref_t { + DEQ_LINKS(dx_router_link_ref_t); + dx_router_link_t *link; +}; + +ALLOC_DECLARE(dx_router_link_ref_t); +DEQ_DECLARE(dx_router_link_ref_t, dx_router_link_ref_list_t); + + +struct dx_router_conn_t { + int mask_bit; +}; + +ALLOC_DECLARE(dx_router_conn_t); + + +struct dx_address_t { + DEQ_LINKS(dx_address_t); + dx_router_message_cb handler; // In-Process Consumer + void *handler_context; // In-Process Consumer context + dx_router_link_ref_list_t rlinks; // Locally-Connected Consumers + dx_router_ref_list_t rnodes; // Remotely-Connected Consumers + dx_hash_handle_t *hash_handle; // Linkage back to the hash table entry + + uint64_t deliveries_ingress; + uint64_t deliveries_egress; + uint64_t deliveries_transit; + uint64_t deliveries_to_container; + uint64_t deliveries_from_container; +}; + +ALLOC_DECLARE(dx_address_t); +DEQ_DECLARE(dx_address_t, dx_address_list_t); + + +struct dx_router_t { + dx_dispatch_t *dx; + dx_router_mode_t router_mode; + const char *router_area; + const char *router_id; + dx_node_t *node; + + dx_address_list_t addrs; + dx_hash_t *addr_hash; + dx_address_t *router_addr; + dx_address_t *hello_addr; + + dx_router_link_list_t links; + dx_router_node_list_t routers; + dx_router_link_t **out_links_by_mask_bit; + dx_router_node_t **routers_by_mask_bit; + + dx_bitmask_t *neighbor_free_mask; + sys_mutex_t *lock; + dx_timer_t *timer; + uint64_t dtag; + + PyObject *pyRouter; + PyObject *pyTick; + PyObject *pyAdded; + PyObject *pyRemoved; + + dx_agent_class_t *class_router; + dx_agent_class_t *class_link; + dx_agent_class_t *class_node; + dx_agent_class_t *class_address; +}; + + + +void dx_router_check_addr(dx_router_t *router, dx_address_t *addr, int was_local); +void dx_router_add_link_ref_LH(dx_router_link_ref_list_t *ref_list, dx_router_link_t *link); +void dx_router_del_link_ref_LH(dx_router_link_ref_list_t *ref_list, dx_router_link_t *link); + +void dx_router_add_node_ref_LH(dx_router_ref_list_t *ref_list, dx_router_node_t *rnode); +void dx_router_del_node_ref_LH(dx_router_ref_list_t *ref_list, dx_router_node_t *rnode); + +void dx_router_mobile_added(dx_router_t *router, dx_field_iterator_t *iter); +void dx_router_mobile_removed(dx_router_t *router, const char *addr); + + +#endif diff --git a/qpid/extras/dispatch/src/router_pynode.c b/qpid/extras/dispatch/src/router_pynode.c new file mode 100644 index 0000000000..82b08c0785 --- /dev/null +++ b/qpid/extras/dispatch/src/router_pynode.c @@ -0,0 +1,682 @@ +/* + * 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. + */ + +#include <qpid/dispatch/python_embedded.h> +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <stdlib.h> +#include <qpid/dispatch.h> +#include "dispatch_private.h" +#include "router_private.h" + +static char *module = "router.pynode"; + +typedef struct { + PyObject_HEAD + dx_router_t *router; +} RouterAdapter; + + +static char *dx_add_router(dx_router_t *router, const char *address, int router_maskbit, int link_maskbit) +{ + if (router_maskbit >= dx_bitmask_width() || router_maskbit < 0) + return "Router bit mask out of range"; + + if (link_maskbit >= dx_bitmask_width() || link_maskbit < -1) + return "Link bit mask out of range"; + + sys_mutex_lock(router->lock); + if (router->routers_by_mask_bit[router_maskbit] != 0) { + sys_mutex_unlock(router->lock); + return "Adding router over already existing router"; + } + + if (link_maskbit >= 0 && router->out_links_by_mask_bit[link_maskbit] == 0) { + sys_mutex_unlock(router->lock); + return "Adding neighbor router with invalid link reference"; + } + + // + // Hash lookup the address to ensure there isn't an existing router address. + // + dx_field_iterator_t *iter = dx_field_iterator_string(address, ITER_VIEW_ADDRESS_HASH); + dx_address_t *addr; + + dx_hash_retrieve(router->addr_hash, iter, (void**) &addr); + assert(addr == 0); + + // + // Create an address record for this router and insert it in the hash table. + // This record will be found whenever a "foreign" topological address to this + // remote router is looked up. + // + addr = new_dx_address_t(); + memset(addr, 0, sizeof(dx_address_t)); + DEQ_ITEM_INIT(addr); + DEQ_INIT(addr->rlinks); + DEQ_INIT(addr->rnodes); + dx_hash_insert(router->addr_hash, iter, addr, &addr->hash_handle); + DEQ_INSERT_TAIL(router->addrs, addr); + dx_field_iterator_free(iter); + + // + // Create a router-node record to represent the remote router. + // + dx_router_node_t *rnode = new_dx_router_node_t(); + DEQ_ITEM_INIT(rnode); + rnode->owning_addr = addr; + rnode->mask_bit = router_maskbit; + rnode->next_hop = 0; + rnode->peer_link = 0; + rnode->ref_count = 0; + rnode->valid_origins = dx_bitmask(0); + + DEQ_INSERT_TAIL(router->routers, rnode); + + // + // Link the router record to the address record. + // + dx_router_add_node_ref_LH(&addr->rnodes, rnode); + + // + // Link the router record to the router address record. + // + dx_router_add_node_ref_LH(&router->router_addr->rnodes, rnode); + + // + // Add the router record to the mask-bit index. + // + router->routers_by_mask_bit[router_maskbit] = rnode; + + // + // If this is a neighbor router, add the peer_link reference to the + // router record. + // + if (link_maskbit >= 0) + rnode->peer_link = router->out_links_by_mask_bit[link_maskbit]; + + sys_mutex_unlock(router->lock); + return 0; +} + + +static char *dx_del_router(dx_router_t *router, int router_maskbit) +{ + if (router_maskbit >= dx_bitmask_width() || router_maskbit < 0) + return "Router bit mask out of range"; + + sys_mutex_lock(router->lock); + if (router->routers_by_mask_bit[router_maskbit] == 0) { + sys_mutex_unlock(router->lock); + return "Deleting nonexistent router"; + } + + dx_router_node_t *rnode = router->routers_by_mask_bit[router_maskbit]; + dx_address_t *oaddr = rnode->owning_addr; + assert(oaddr); + + // + // Unlink the router node from the address record + // + dx_router_del_node_ref_LH(&oaddr->rnodes, rnode); + + // + // While the router node has a non-zero reference count, look for addresses + // to unlink the node from. + // + dx_address_t *addr = DEQ_HEAD(router->addrs); + while (addr && rnode->ref_count > 0) { + dx_router_del_node_ref_LH(&addr->rnodes, rnode); + addr = DEQ_NEXT(addr); + } + assert(rnode->ref_count == 0); + + // + // Free the router node and the owning address records. + // + dx_bitmask_free(rnode->valid_origins); + DEQ_REMOVE(router->routers, rnode); + free_dx_router_node_t(rnode); + + dx_hash_remove_by_handle(router->addr_hash, oaddr->hash_handle); + DEQ_REMOVE(router->addrs, oaddr); + dx_hash_handle_free(oaddr->hash_handle); + router->routers_by_mask_bit[router_maskbit] = 0; + free_dx_address_t(oaddr); + + sys_mutex_unlock(router->lock); + return 0; +} + + +static PyObject* dx_add_remote_router(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + const char *address; + int router_maskbit; + + if (!PyArg_ParseTuple(args, "si", &address, &router_maskbit)) + return 0; + + char *error = dx_add_router(router, address, router_maskbit, -1); + if (error) { + PyErr_SetString(PyExc_Exception, error); + return 0; + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyObject* dx_del_remote_router(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + int router_maskbit; + + if (!PyArg_ParseTuple(args, "i", &router_maskbit)) + return 0; + + char *error = dx_del_router(router, router_maskbit); + if (error) { + PyErr_SetString(PyExc_Exception, error); + return 0; + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyObject* dx_set_next_hop(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + int router_maskbit; + int next_hop_maskbit; + + if (!PyArg_ParseTuple(args, "ii", &router_maskbit, &next_hop_maskbit)) + return 0; + + if (router_maskbit >= dx_bitmask_width() || router_maskbit < 0) { + PyErr_SetString(PyExc_Exception, "Router bit mask out of range"); + return 0; + } + + if (next_hop_maskbit >= dx_bitmask_width() || next_hop_maskbit < 0) { + PyErr_SetString(PyExc_Exception, "Next Hop bit mask out of range"); + return 0; + } + + if (router->routers_by_mask_bit[router_maskbit] == 0) { + PyErr_SetString(PyExc_Exception, "Router Not Found"); + return 0; + } + + if (router->routers_by_mask_bit[next_hop_maskbit] == 0) { + PyErr_SetString(PyExc_Exception, "Next Hop Not Found"); + return 0; + } + + if (router_maskbit != next_hop_maskbit) { + dx_router_node_t *rnode = router->routers_by_mask_bit[router_maskbit]; + rnode->next_hop = router->routers_by_mask_bit[next_hop_maskbit]; + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyObject* dx_set_valid_origins(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + int router_maskbit; + PyObject *origin_list; + Py_ssize_t idx; + + if (!PyArg_ParseTuple(args, "iO", &router_maskbit, &origin_list)) + return 0; + + if (router_maskbit >= dx_bitmask_width() || router_maskbit < 0) { + PyErr_SetString(PyExc_Exception, "Router bit mask out of range"); + return 0; + } + + if (router->routers_by_mask_bit[router_maskbit] == 0) { + PyErr_SetString(PyExc_Exception, "Router Not Found"); + return 0; + } + + if (!PyList_Check(origin_list)) { + PyErr_SetString(PyExc_Exception, "Expected List as argument 2"); + return 0; + } + + Py_ssize_t origin_count = PyList_Size(origin_list); + dx_router_node_t *rnode = router->routers_by_mask_bit[router_maskbit]; + int maskbit; + + for (idx = 0; idx < origin_count; idx++) { + maskbit = PyInt_AS_LONG(PyList_GetItem(origin_list, idx)); + + if (maskbit >= dx_bitmask_width() || maskbit < 0) { + PyErr_SetString(PyExc_Exception, "Origin bit mask out of range"); + return 0; + } + + if (router->routers_by_mask_bit[maskbit] == 0) { + PyErr_SetString(PyExc_Exception, "Origin router Not Found"); + return 0; + } + } + + dx_bitmask_clear_all(rnode->valid_origins); + dx_bitmask_set_bit(rnode->valid_origins, 0); // This router is a valid origin for all destinations + for (idx = 0; idx < origin_count; idx++) { + maskbit = PyInt_AS_LONG(PyList_GetItem(origin_list, idx)); + dx_bitmask_set_bit(rnode->valid_origins, maskbit); + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyObject* dx_add_neighbor_router(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + const char *address; + int router_maskbit; + int link_maskbit; + + if (!PyArg_ParseTuple(args, "sii", &address, &router_maskbit, &link_maskbit)) + return 0; + + char *error = dx_add_router(router, address, router_maskbit, link_maskbit); + if (error) { + PyErr_SetString(PyExc_Exception, error); + return 0; + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyObject* dx_del_neighbor_router(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + int router_maskbit; + + if (!PyArg_ParseTuple(args, "i", &router_maskbit)) + return 0; + + char *error = dx_del_router(router, router_maskbit); + if (error) { + PyErr_SetString(PyExc_Exception, error); + return 0; + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyObject* dx_map_destination(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + const char *addr_string; + int maskbit; + dx_address_t *addr; + dx_field_iterator_t *iter; + + if (!PyArg_ParseTuple(args, "si", &addr_string, &maskbit)) + return 0; + + if (maskbit >= dx_bitmask_width() || maskbit < 0) { + PyErr_SetString(PyExc_Exception, "Router bit mask out of range"); + return 0; + } + + if (router->routers_by_mask_bit[maskbit] == 0) { + PyErr_SetString(PyExc_Exception, "Router Not Found"); + return 0; + } + + iter = dx_field_iterator_string(addr_string, ITER_VIEW_ADDRESS_HASH); + + sys_mutex_lock(router->lock); + dx_hash_retrieve(router->addr_hash, iter, (void**) &addr); + if (!addr) { + addr = new_dx_address_t(); + memset(addr, 0, sizeof(dx_address_t)); + DEQ_ITEM_INIT(addr); + DEQ_INIT(addr->rlinks); + DEQ_INIT(addr->rnodes); + dx_hash_insert(router->addr_hash, iter, addr, &addr->hash_handle); + DEQ_ITEM_INIT(addr); + DEQ_INSERT_TAIL(router->addrs, addr); + } + dx_field_iterator_free(iter); + + dx_router_node_t *rnode = router->routers_by_mask_bit[maskbit]; + dx_router_add_node_ref_LH(&addr->rnodes, rnode); + + sys_mutex_unlock(router->lock); + + dx_log(module, LOG_DEBUG, "Remote Destination '%s' Mapped to router %d", addr_string, maskbit); + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyObject* dx_unmap_destination(PyObject *self, PyObject *args) +{ + RouterAdapter *adapter = (RouterAdapter*) self; + dx_router_t *router = adapter->router; + const char *addr_string; + int maskbit; + dx_address_t *addr; + + if (!PyArg_ParseTuple(args, "si", &addr_string, &maskbit)) + return 0; + + if (maskbit >= dx_bitmask_width() || maskbit < 0) { + PyErr_SetString(PyExc_Exception, "Router bit mask out of range"); + return 0; + } + + if (router->routers_by_mask_bit[maskbit] == 0) { + PyErr_SetString(PyExc_Exception, "Router Not Found"); + return 0; + } + + dx_router_node_t *rnode = router->routers_by_mask_bit[maskbit]; + dx_field_iterator_t *iter = dx_field_iterator_string(addr_string, ITER_VIEW_ADDRESS_HASH); + + sys_mutex_lock(router->lock); + dx_hash_retrieve(router->addr_hash, iter, (void**) &addr); + dx_field_iterator_free(iter); + + if (!addr) { + PyErr_SetString(PyExc_Exception, "Address Not Found"); + sys_mutex_unlock(router->lock); + return 0; + } + + dx_router_del_node_ref_LH(&addr->rnodes, rnode); + sys_mutex_unlock(router->lock); + + dx_router_check_addr(router, addr, 0); + + dx_log(module, LOG_DEBUG, "Remote Destination '%s' Unmapped from router %d", addr_string, maskbit); + + Py_INCREF(Py_None); + return Py_None; +} + + +static PyMethodDef RouterAdapter_methods[] = { + {"add_remote_router", dx_add_remote_router, METH_VARARGS, "A new remote/reachable router has been discovered"}, + {"del_remote_router", dx_del_remote_router, METH_VARARGS, "We've lost reachability to a remote router"}, + {"set_next_hop", dx_set_next_hop, METH_VARARGS, "Set the next hop for a remote router"}, + {"set_valid_origins", dx_set_valid_origins, METH_VARARGS, "Set the valid origins for a remote router"}, + {"add_neighbor_router", dx_add_neighbor_router, METH_VARARGS, "A new neighbor router has been discovered"}, + {"del_neighbor_router", dx_del_neighbor_router, METH_VARARGS, "We've lost reachability to a neighbor router"}, + {"map_destination", dx_map_destination, METH_VARARGS, "Add a newly discovered destination mapping"}, + {"unmap_destination", dx_unmap_destination, METH_VARARGS, "Delete a destination mapping"}, + {0, 0, 0, 0} +}; + +static PyTypeObject RouterAdapterType = { + PyObject_HEAD_INIT(0) + 0, /* ob_size*/ + "dispatch.RouterAdapter", /* tp_name*/ + sizeof(RouterAdapter), /* tp_basicsize*/ + 0, /* tp_itemsize*/ + 0, /* tp_dealloc*/ + 0, /* tp_print*/ + 0, /* tp_getattr*/ + 0, /* tp_setattr*/ + 0, /* tp_compare*/ + 0, /* tp_repr*/ + 0, /* tp_as_number*/ + 0, /* tp_as_sequence*/ + 0, /* tp_as_mapping*/ + 0, /* tp_hash */ + 0, /* tp_call*/ + 0, /* tp_str*/ + 0, /* tp_getattro*/ + 0, /* tp_setattro*/ + 0, /* tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /* tp_flags*/ + "Dispatch Router Adapter", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + RouterAdapter_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0, /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ + 0, /* tp_del */ + 0 /* tp_version_tag */ +}; + + +void dx_router_python_setup(dx_router_t *router) +{ + // + // If we are not operating as an interior router, don't start the + // router module. + // + if (router->router_mode != DX_ROUTER_MODE_INTERIOR) + return; + + PyObject *pDispatchModule = dx_python_module(); + + RouterAdapterType.tp_new = PyType_GenericNew; + if (PyType_Ready(&RouterAdapterType) < 0) { + PyErr_Print(); + dx_log(module, LOG_CRITICAL, "Unable to initialize the Python Router Adapter"); + return; + } + + PyTypeObject *raType = &RouterAdapterType; + Py_INCREF(raType); + PyModule_AddObject(pDispatchModule, "RouterAdapter", (PyObject*) &RouterAdapterType); + + // + // Attempt to import the Python Router module + // + PyObject* pName; + PyObject* pId; + PyObject* pArea; + PyObject* pMaxRouters; + PyObject* pModule; + PyObject* pClass; + PyObject* pArgs; + + pName = PyString_FromString("qpid.dispatch.router"); + pModule = PyImport_Import(pName); + Py_DECREF(pName); + if (!pModule) { + dx_log(module, LOG_CRITICAL, "Can't Locate 'router' Python module"); + return; + } + + pClass = PyObject_GetAttrString(pModule, "RouterEngine"); + if (!pClass || !PyClass_Check(pClass)) { + dx_log(module, LOG_CRITICAL, "Can't Locate 'RouterEngine' class in the 'router' module"); + return; + } + + PyObject *adapterType = PyObject_GetAttrString(pDispatchModule, "RouterAdapter"); + PyObject *adapterInstance = PyObject_CallObject(adapterType, 0); + assert(adapterInstance); + + ((RouterAdapter*) adapterInstance)->router = router; + + // + // Constructor Arguments for RouterEngine + // + pArgs = PyTuple_New(4); + + // arg 0: adapter instance + PyTuple_SetItem(pArgs, 0, adapterInstance); + + // arg 1: router_id + pId = PyString_FromString(router->router_id); + PyTuple_SetItem(pArgs, 1, pId); + + // arg 2: area_id + pArea = PyString_FromString(router->router_area); + PyTuple_SetItem(pArgs, 2, pArea); + + // arg 3: max_routers + pMaxRouters = PyInt_FromLong((long) dx_bitmask_width()); + PyTuple_SetItem(pArgs, 3, pMaxRouters); + + // + // Instantiate the router + // + router->pyRouter = PyInstance_New(pClass, pArgs, 0); + Py_DECREF(pArgs); + Py_DECREF(adapterType); + + if (!router->pyRouter) { + PyErr_Print(); + dx_log(module, LOG_CRITICAL, "'RouterEngine' class cannot be instantiated"); + return; + } + + router->pyTick = PyObject_GetAttrString(router->pyRouter, "handleTimerTick"); + if (!router->pyTick || !PyCallable_Check(router->pyTick)) { + dx_log(module, LOG_CRITICAL, "'RouterEngine' class has no handleTimerTick method"); + return; + } + + router->pyAdded = PyObject_GetAttrString(router->pyRouter, "addressAdded"); + if (!router->pyAdded || !PyCallable_Check(router->pyAdded)) { + dx_log(module, LOG_CRITICAL, "'RouterEngine' class has no addressAdded method"); + return; + } + + router->pyRemoved = PyObject_GetAttrString(router->pyRouter, "addressRemoved"); + if (!router->pyRemoved || !PyCallable_Check(router->pyRemoved)) { + dx_log(module, LOG_CRITICAL, "'RouterEngine' class has no addressRemoved method"); + return; + } +} + + +void dx_pyrouter_tick(dx_router_t *router) +{ + PyObject *pArgs; + PyObject *pValue; + + if (router->pyTick && router->router_mode == DX_ROUTER_MODE_INTERIOR) { + dx_python_lock(); + pArgs = PyTuple_New(0); + pValue = PyObject_CallObject(router->pyTick, pArgs); + if (PyErr_Occurred()) { + PyErr_Print(); + } + Py_DECREF(pArgs); + if (pValue) { + Py_DECREF(pValue); + } + dx_python_unlock(); + } +} + + +void dx_router_mobile_added(dx_router_t *router, dx_field_iterator_t *iter) +{ + PyObject *pArgs; + PyObject *pValue; + + if (router->pyAdded && router->router_mode == DX_ROUTER_MODE_INTERIOR) { + dx_field_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH); + char *address = (char*) dx_field_iterator_copy(iter); + + dx_python_lock(); + pArgs = PyTuple_New(1); + PyTuple_SetItem(pArgs, 0, PyString_FromString(address)); + pValue = PyObject_CallObject(router->pyAdded, pArgs); + if (PyErr_Occurred()) { + PyErr_Print(); + } + Py_DECREF(pArgs); + if (pValue) { + Py_DECREF(pValue); + } + dx_python_unlock(); + + free(address); + } +} + + +void dx_router_mobile_removed(dx_router_t *router, const char *address) +{ + PyObject *pArgs; + PyObject *pValue; + + if (router->pyRemoved && router->router_mode == DX_ROUTER_MODE_INTERIOR) { + dx_python_lock(); + pArgs = PyTuple_New(1); + PyTuple_SetItem(pArgs, 0, PyString_FromString(address)); + pValue = PyObject_CallObject(router->pyRemoved, pArgs); + if (PyErr_Occurred()) { + PyErr_Print(); + } + Py_DECREF(pArgs); + if (pValue) { + Py_DECREF(pValue); + } + dx_python_unlock(); + } +} + diff --git a/qpid/extras/dispatch/src/server.c b/qpid/extras/dispatch/src/server.c index 5420d3b776..65e181bd2c 100644 --- a/qpid/extras/dispatch/src/server.c +++ b/qpid/extras/dispatch/src/server.c @@ -109,6 +109,8 @@ static void thread_process_listeners(dx_server_t *dx_server) ctx->listener = (dx_listener_t*) pn_listener_context(listener); ctx->connector = 0; ctx->context = ctx->listener->context; + ctx->user_context = 0; + ctx->link_context = 0; ctx->ufd = 0; pn_connection_t *conn = pn_connection(); @@ -327,7 +329,6 @@ static void *thread_run(void *arg) dx_connection_t *ctx; int error; int poll_result; - int timer_holdoff = 0; if (!thread) return 0; @@ -372,35 +373,20 @@ static void *thread_run(void *arg) // // Service pending timers. // - if (DEQ_SIZE(dx_server->pending_timers) > 0) { - dx_timer_list_t local_list; - dx_timer_t *timer = DEQ_HEAD(dx_server->pending_timers); - - DEQ_INIT(local_list); - while (timer) { - DEQ_REMOVE_HEAD(dx_server->pending_timers); - DEQ_INSERT_TAIL(local_list, timer); - timer = DEQ_HEAD(dx_server->pending_timers); - } + dx_timer_t *timer = DEQ_HEAD(dx_server->pending_timers); + if (timer) { + DEQ_REMOVE_HEAD(dx_server->pending_timers); // - // Release the lock and invoke the connection handlers. + // Mark the timer as idle in case it reschedules itself. // - sys_mutex_unlock(dx_server->lock); - - timer = DEQ_HEAD(local_list); - while (timer) { - DEQ_REMOVE_HEAD(local_list); - - // - // Mark the timer as idle in case it reschedules itself. - // - dx_timer_idle_LH(timer); - - timer->handler(timer->context); - timer = DEQ_HEAD(local_list); - } + dx_timer_idle_LH(timer); + // + // Release the lock and invoke the connection handler. + // + sys_mutex_unlock(dx_server->lock); + timer->handler(timer->context); pn_driver_wakeup(dx_server->driver); continue; } @@ -464,13 +450,10 @@ static void *thread_run(void *arg) // // Visit the timer module. // - if (poll_result == 0 || ++timer_holdoff == 100) { - struct timespec tv; - clock_gettime(CLOCK_REALTIME, &tv); - long milliseconds = tv.tv_sec * 1000 + tv.tv_nsec / 1000000; - dx_timer_visit_LH(milliseconds); - timer_holdoff = 0; - } + struct timespec tv; + clock_gettime(CLOCK_REALTIME, &tv); + long milliseconds = tv.tv_sec * 1000 + tv.tv_nsec / 1000000; + dx_timer_visit_LH(milliseconds); // // Process listeners (incoming connections). @@ -630,6 +613,7 @@ static void cxtr_try_open(void *context) ctx->connector = ct; ctx->context = ct->context; ctx->user_context = 0; + ctx->link_context = 0; ctx->ufd = 0; // @@ -878,12 +862,32 @@ void *dx_connection_get_context(dx_connection_t *conn) } +void dx_connection_set_link_context(dx_connection_t *conn, void *context) +{ + conn->link_context = context; +} + + +void *dx_connection_get_link_context(dx_connection_t *conn) +{ + return conn->link_context; +} + + pn_connection_t *dx_connection_pn(dx_connection_t *conn) { return conn->pn_conn; } +const dx_server_config_t *dx_connection_config(const dx_connection_t *conn) +{ + if (conn->listener) + return conn->listener->config; + return conn->connector->config; +} + + dx_listener_t *dx_server_listen(dx_dispatch_t *dx, const dx_server_config_t *config, void *context) { dx_server_t *dx_server = dx->server; @@ -976,6 +980,7 @@ dx_user_fd_t *dx_user_fd(dx_dispatch_t *dx, int fd, void *context) ctx->connector = 0; ctx->context = 0; ctx->user_context = 0; + ctx->link_context = 0; ctx->ufd = ufd; ufd->context = context; diff --git a/qpid/extras/dispatch/src/server_private.h b/qpid/extras/dispatch/src/server_private.h index 5782492f22..86a3ef98f1 100644 --- a/qpid/extras/dispatch/src/server_private.h +++ b/qpid/extras/dispatch/src/server_private.h @@ -78,6 +78,7 @@ struct dx_connection_t { dx_connector_t *connector; void *context; // Copy of context from listener or connector void *user_context; + void *link_context; // Context shared by this connection's links dx_user_fd_t *ufd; }; diff --git a/qpid/extras/dispatch/tests/compose_test.c b/qpid/extras/dispatch/tests/compose_test.c index 9eaeeaa038..06292c221f 100644 --- a/qpid/extras/dispatch/tests/compose_test.c +++ b/qpid/extras/dispatch/tests/compose_test.c @@ -27,6 +27,105 @@ #include "compose_private.h" +static char *vector0 = + "\x00\x53\x77" // amqp-value + "\xd0\x00\x00\x01\x26\x00\x00\x00\x0a" // list32 with ten items + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x0a" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x0b" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + "\xd1\x00\x00\x00\x18\x00\x00\x00\x04" // map32 with two pairs + "\xa1\x06key001" // str8-utf8 + "\x52\x14" // smalluint + "\xa1\x06key002" // str8-utf8 + "\x52\x15" // smalluint + ; + +static int vector0_length = 302; + +static char *test_compose_list_of_maps(void *context) +{ + dx_composed_field_t *field = dx_compose(DX_PERFORMATIVE_BODY_AMQP_VALUE, 0); + + dx_compose_start_list(field); + + dx_compose_start_map(field); + dx_compose_insert_string(field, "key001"); + dx_compose_insert_uint(field, 10); + dx_compose_insert_string(field, "key002"); + dx_compose_insert_uint(field, 11); + dx_compose_end_map(field); + + for (int j = 0; j < 9; j++) { + dx_compose_start_map(field); + dx_compose_insert_string(field, "key001"); + dx_compose_insert_uint(field, 20); + dx_compose_insert_string(field, "key002"); + dx_compose_insert_uint(field, 21); + dx_compose_end_map(field); + } + + dx_compose_end_list(field); + + dx_buffer_t *buf = DEQ_HEAD(field->buffers); + + if (dx_buffer_size(buf) != vector0_length) return "Incorrect Length of Buffer"; + + char *left = vector0; + char *right = (char*) dx_buffer_base(buf); + int idx; + + for (idx = 0; idx < vector0_length; idx++) { + if (*left != *right) return "Pattern Mismatch"; + left++; + right++; + } + + dx_compose_free(field); + return 0; +} + static char *vector1 = "\x00\x53\x71" // delivery annotations "\xd1\x00\x00\x00\x3d\x00\x00\x00\x04" // map32 with two item pairs @@ -184,6 +283,7 @@ int compose_tests() int result = 0; dx_log_set_mask(LOG_NONE); + TEST_CASE(test_compose_list_of_maps, 0); TEST_CASE(test_compose_nested_composites, 0); TEST_CASE(test_compose_scalars, 0); diff --git a/qpid/extras/dispatch/tests/onerouter.conf b/qpid/extras/dispatch/tests/config-1/A.conf index 308d86473c..308d86473c 100644 --- a/qpid/extras/dispatch/tests/onerouter.conf +++ b/qpid/extras/dispatch/tests/config-1/A.conf diff --git a/qpid/extras/dispatch/tests/config-2/A.conf b/qpid/extras/dispatch/tests/config-2/A.conf new file mode 100644 index 0000000000..0d00efdbd4 --- /dev/null +++ b/qpid/extras/dispatch/tests/config-2/A.conf @@ -0,0 +1,60 @@ +## +## 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 +## + + +## +## Container section - Configures the general operation of the AMQP container. +## +container { + ## + ## worker-threads - The number of threads that will be created to + ## process message traffic and other application work (timers, non-amqp + ## file descriptors, etc.) + ## + ## The number of threads should be related to the number of available + ## processor cores. To fully utilize a quad-core system, set the + ## number of threads to 4. + ## + worker-threads: 4 + + ## + ## container-name - The name of the AMQP container. If not specified, + ## the container name will be set to a value of the container's + ## choosing. The automatically assigned container name is not + ## guaranteed to be persistent across restarts of the container. + ## + container-name: Qpid.Dispatch.Router.A +} + + +## +## Listeners and Connectors +## +listener { + role: inter-router + addr: 0.0.0.0 + port: 20001 + sasl-mechanisms: ANONYMOUS +} + +router { + mode: interior + area: A + router-id: QDR.A +} diff --git a/qpid/extras/dispatch/tests/config-2/B.conf b/qpid/extras/dispatch/tests/config-2/B.conf new file mode 100644 index 0000000000..8999c9878c --- /dev/null +++ b/qpid/extras/dispatch/tests/config-2/B.conf @@ -0,0 +1,68 @@ +## +## 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 +## + + +## +## Container section - Configures the general operation of the AMQP container. +## +container { + ## + ## worker-threads - The number of threads that will be created to + ## process message traffic and other application work (timers, non-amqp + ## file descriptors, etc.) + ## + ## The number of threads should be related to the number of available + ## processor cores. To fully utilize a quad-core system, set the + ## number of threads to 4. + ## + worker-threads: 4 + + ## + ## container-name - The name of the AMQP container. If not specified, + ## the container name will be set to a value of the container's + ## choosing. The automatically assigned container name is not + ## guaranteed to be persistent across restarts of the container. + ## + container-name: Qpid.Dispatch.Router.B +} + + +## +## Listeners and Connectors +## +listener { + role: inter-router + addr: 0.0.0.0 + port: 20002 + sasl-mechanisms: ANONYMOUS +} + +connector { + label: Router Uplink + role: inter-router + addr: 0.0.0.0 + port: 20001 + sasl-mechanisms: ANONYMOUS +} + +router { + mode: interior + area: A + router-id: QDR.B +} diff --git a/qpid/extras/dispatch/tests/config-3-linear/A.conf b/qpid/extras/dispatch/tests/config-3-linear/A.conf new file mode 100644 index 0000000000..3f18ad668c --- /dev/null +++ b/qpid/extras/dispatch/tests/config-3-linear/A.conf @@ -0,0 +1,70 @@ +## +## 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 +## + + +## +## Container section - Configures the general operation of the AMQP container. +## +container { + ## + ## worker-threads - The number of threads that will be created to + ## process message traffic and other application work (timers, non-amqp + ## file descriptors, etc.) + ## + ## The number of threads should be related to the number of available + ## processor cores. To fully utilize a quad-core system, set the + ## number of threads to 4. + ## + worker-threads: 4 + + ## + ## container-name - The name of the AMQP container. If not specified, + ## the container name will be set to a value of the container's + ## choosing. The automatically assigned container name is not + ## guaranteed to be persistent across restarts of the container. + ## + container-name: QDR.A +} + + +## +## Listeners and Connectors +## +listener { + addr: 0.0.0.0 + port: 20001 + sasl-mechanisms: ANONYMOUS +} + +connector { + label: Router Uplink + role: inter-router + addr: 0.0.0.0 + port: 20002 + sasl-mechanisms: ANONYMOUS +} + +router { + mode: interior + area: A + router-id: QDR.A +} + + + diff --git a/qpid/extras/dispatch/tests/config-3-linear/B.conf b/qpid/extras/dispatch/tests/config-3-linear/B.conf new file mode 100644 index 0000000000..10310306d6 --- /dev/null +++ b/qpid/extras/dispatch/tests/config-3-linear/B.conf @@ -0,0 +1,69 @@ +## +## 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 +## + + +## +## Container section - Configures the general operation of the AMQP container. +## +container { + ## + ## worker-threads - The number of threads that will be created to + ## process message traffic and other application work (timers, non-amqp + ## file descriptors, etc.) + ## + ## The number of threads should be related to the number of available + ## processor cores. To fully utilize a quad-core system, set the + ## number of threads to 4. + ## + worker-threads: 4 + + ## + ## container-name - The name of the AMQP container. If not specified, + ## the container name will be set to a value of the container's + ## choosing. The automatically assigned container name is not + ## guaranteed to be persistent across restarts of the container. + ## + container-name: QDR.B +} + + +## +## Listeners and Connectors +## +listener { + role: inter-router + addr: 0.0.0.0 + port: 20002 + sasl-mechanisms: ANONYMOUS +} + +connector { + label: Router Uplink + role: inter-router + addr: 0.0.0.0 + port: 20003 + sasl-mechanisms: ANONYMOUS +} + + +router { + mode: interior + area: A + router-id: QDR.B +} diff --git a/qpid/extras/dispatch/tests/config-3-linear/C.conf b/qpid/extras/dispatch/tests/config-3-linear/C.conf new file mode 100644 index 0000000000..83c0ff603d --- /dev/null +++ b/qpid/extras/dispatch/tests/config-3-linear/C.conf @@ -0,0 +1,61 @@ +## +## 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 +## + + +## +## Container section - Configures the general operation of the AMQP container. +## +container { + ## + ## worker-threads - The number of threads that will be created to + ## process message traffic and other application work (timers, non-amqp + ## file descriptors, etc.) + ## + ## The number of threads should be related to the number of available + ## processor cores. To fully utilize a quad-core system, set the + ## number of threads to 4. + ## + worker-threads: 4 + + ## + ## container-name - The name of the AMQP container. If not specified, + ## the container name will be set to a value of the container's + ## choosing. The automatically assigned container name is not + ## guaranteed to be persistent across restarts of the container. + ## + container-name: QDR.C +} + + +## +## Listeners and Connectors +## +listener { + role: inter-router + addr: 0.0.0.0 + port: 20003 + sasl-mechanisms: ANONYMOUS +} + + +router { + mode: interior + area: A + router-id: QDR.C +} diff --git a/qpid/extras/dispatch/tests/config-3-linear/topology.txt b/qpid/extras/dispatch/tests/config-3-linear/topology.txt new file mode 100644 index 0000000000..07d6d74008 --- /dev/null +++ b/qpid/extras/dispatch/tests/config-3-linear/topology.txt @@ -0,0 +1,12 @@ + + + +----------+ +----------+ +----------+ + |QDR.A | |QDR.B | |QDR.C | + |port: | |port: | |port: | + | 20001 |--------->| 20002 |--------->| 20003 | + | | | | | | + | | | | | | + +----------+ +----------+ +----------+ + + * The direction of the arrow shows the direction of the connection setup + Connector --> Listener diff --git a/qpid/extras/dispatch/tests/field_test.c b/qpid/extras/dispatch/tests/field_test.c index 59fc3c4cfb..33f9fe5d03 100644 --- a/qpid/extras/dispatch/tests/field_test.c +++ b/qpid/extras/dispatch/tests/field_test.c @@ -115,6 +115,10 @@ static char* test_view_address_hash(void *context) {"amqp:/_topo/my-area/all/local/sub", "Llocal/sub"}, {"amqp:/_topo/all/all/local/sub", "Llocal/sub"}, {"amqp://host:port/_local/my-addr", "Lmy-addr"}, + {"_topo/area/router/my-addr", "Aarea"}, + {"_topo/my-area/router/my-addr", "Rrouter"}, + {"_topo/my-area/my-router/my-addr", "Lmy-addr"}, + {"_topo/my-area/router", "Rrouter"}, {0, 0} }; int idx; diff --git a/qpid/extras/dispatch/tests/message_test.c b/qpid/extras/dispatch/tests/message_test.c index 2873f48740..59f4e444dc 100644 --- a/qpid/extras/dispatch/tests/message_test.c +++ b/qpid/extras/dispatch/tests/message_test.c @@ -47,7 +47,7 @@ static void set_content(dx_message_content_t *content, size_t len) dx_buffer_t *buf; while (len > (size_t) (cursor - buffer)) { - buf = dx_allocate_buffer(); + buf = dx_buffer(); size_t segment = dx_buffer_capacity(buf); size_t remaining = len - (size_t) (cursor - buffer); if (segment > remaining) @@ -62,7 +62,7 @@ static void set_content(dx_message_content_t *content, size_t len) static char* test_send_to_messenger(void *context) { - dx_message_t *msg = dx_allocate_message(); + dx_message_t *msg = dx_message(); dx_message_content_t *content = MSG_CONTENT(msg); dx_message_compose_1(msg, "test_addr_0", 0); @@ -78,7 +78,7 @@ static char* test_send_to_messenger(void *context) return "Address mismatch in received message"; pn_message_free(pn_msg); - dx_free_message(msg); + dx_message_free(msg); return 0; } @@ -93,7 +93,7 @@ static char* test_receive_from_messenger(void *context) int result = pn_message_encode(pn_msg, buffer, &size); if (result != 0) return "Error in pn_message_encode"; - dx_message_t *msg = dx_allocate_message(); + dx_message_t *msg = dx_message(); dx_message_content_t *content = MSG_CONTENT(msg); set_content(content, size); @@ -118,7 +118,7 @@ static char* test_receive_from_messenger(void *context) return "Incorrect field content returned from field_copy"; pn_message_free(pn_msg); - dx_free_message(msg); + dx_message_free(msg); return 0; } @@ -133,7 +133,7 @@ static char* test_insufficient_check_depth(void *context) int result = pn_message_encode(pn_msg, buffer, &size); if (result != 0) return "Error in pn_message_encode"; - dx_message_t *msg = dx_allocate_message(); + dx_message_t *msg = dx_message(); dx_message_content_t *content = MSG_CONTENT(msg); set_content(content, size); @@ -144,7 +144,7 @@ static char* test_insufficient_check_depth(void *context) dx_field_iterator_t *iter = dx_message_field_iterator(msg, DX_FIELD_TO); if (iter) return "Expected no iterator for the 'to' field"; - dx_free_message(msg); + dx_message_free(msg); return 0; } @@ -159,7 +159,7 @@ static char* test_check_multiple(void *context) int result = pn_message_encode(pn_msg, buffer, &size); if (result != 0) return "Error in pn_message_encode"; - dx_message_t *msg = dx_allocate_message(); + dx_message_t *msg = dx_message(); dx_message_content_t *content = MSG_CONTENT(msg); set_content(content, size); @@ -173,7 +173,7 @@ static char* test_check_multiple(void *context) valid = dx_message_check(msg, DX_DEPTH_PROPERTIES); if (!valid) return "dx_message_check returns 'invalid' for PROPERTIES"; - dx_free_message(msg); + dx_message_free(msg); return 0; } diff --git a/qpid/extras/dispatch/tests/router_engine_test.py b/qpid/extras/dispatch/tests/router_engine_test.py index 605e7568d3..e25ce35d22 100644 --- a/qpid/extras/dispatch/tests/router_engine_test.py +++ b/qpid/extras/dispatch/tests/router_engine_test.py @@ -18,393 +18,634 @@ # import unittest -from qpid.dispatch.router.router_engine import NeighborEngine, PathEngine, Configuration +from qpid.dispatch.router.router_engine import NeighborEngine, PathEngine, Configuration, NodeTracker from qpid.dispatch.router.data import LinkState, MessageHELLO class Adapter(object): - def __init__(self, domain): - self._domain = domain + def __init__(self, domain): + self._domain = domain - def log(self, level, text): - print "Adapter.log(%d): domain=%s, text=%s" % (level, self._domain, text) + def log(self, level, text): + print "Adapter.log(%d): domain=%s, text=%s" % (level, self._domain, text) - def send(self, dest, opcode, body): - print "Adapter.send: domain=%s, dest=%s, opcode=%s, body=%s" % (self._domain, dest, opcode, body) - def remote_bind(self, subject, peer): - print "Adapter.remote_bind: subject=%s, peer=%s" % (subject, peer) + def send(self, dest, opcode, body): + print "Adapter.send: domain=%s, dest=%s, opcode=%s, body=%s" % (self._domain, dest, opcode, body) - def remote_unbind(self, subject, peer): - print "Adapter.remote_unbind: subject=%s, peer=%s" % (subject, peer) + def remote_bind(self, subject, peer): + print "Adapter.remote_bind: subject=%s, peer=%s" % (subject, peer) + def remote_unbind(self, subject, peer): + print "Adapter.remote_unbind: subject=%s, peer=%s" % (subject, peer) + + def node_updated(self, address, reachable, neighbor, link_bit, router_bit): + print "Adapter.node_updated: address=%s, reachable=%r, neighbor=%r, link_bit=%d, router_bit=%d" % \ + (address, reachable, neighbor, link_bit, router_bit) -class DataTest(unittest.TestCase): - def test_link_state(self): - ls = LinkState(None, 'R1', 'area', 1, ['R2', 'R3']) - self.assertEqual(ls.id, 'R1') - self.assertEqual(ls.area, 'area') - self.assertEqual(ls.ls_seq, 1) - self.assertEqual(ls.peers, ['R2', 'R3']) - ls.bump_sequence() - self.assertEqual(ls.id, 'R1') - self.assertEqual(ls.area, 'area') - self.assertEqual(ls.ls_seq, 2) - self.assertEqual(ls.peers, ['R2', 'R3']) - - result = ls.add_peer('R4') - self.assertTrue(result) - self.assertEqual(ls.peers, ['R2', 'R3', 'R4']) - result = ls.add_peer('R2') - self.assertFalse(result) - self.assertEqual(ls.peers, ['R2', 'R3', 'R4']) - - result = ls.del_peer('R3') - self.assertTrue(result) - self.assertEqual(ls.peers, ['R2', 'R4']) - result = ls.del_peer('R5') - self.assertFalse(result) - self.assertEqual(ls.peers, ['R2', 'R4']) - - encoded = ls.to_dict() - new_ls = LinkState(encoded) - self.assertEqual(new_ls.id, 'R1') - self.assertEqual(new_ls.area, 'area') - self.assertEqual(new_ls.ls_seq, 2) - self.assertEqual(new_ls.peers, ['R2', 'R4']) - - - def test_hello_message(self): - msg1 = MessageHELLO(None, 'R1', 'area', ['R2', 'R3', 'R4']) - self.assertEqual(msg1.get_opcode(), "HELLO") - self.assertEqual(msg1.id, 'R1') - self.assertEqual(msg1.area, 'area') - self.assertEqual(msg1.seen_peers, ['R2', 'R3', 'R4']) - encoded = msg1.to_dict() - msg2 = MessageHELLO(encoded) - self.assertEqual(msg2.get_opcode(), "HELLO") - self.assertEqual(msg2.id, 'R1') - self.assertEqual(msg2.area, 'area') - self.assertEqual(msg2.seen_peers, ['R2', 'R3', 'R4']) - self.assertTrue(msg2.is_seen('R3')) - self.assertFalse(msg2.is_seen('R9')) +class DataTest(unittest.TestCase): + def test_link_state(self): + ls = LinkState(None, 'R1', 'area', 1, ['R2', 'R3']) + self.assertEqual(ls.id, 'R1') + self.assertEqual(ls.area, 'area') + self.assertEqual(ls.ls_seq, 1) + self.assertEqual(ls.peers, ['R2', 'R3']) + ls.bump_sequence() + self.assertEqual(ls.id, 'R1') + self.assertEqual(ls.area, 'area') + self.assertEqual(ls.ls_seq, 2) + self.assertEqual(ls.peers, ['R2', 'R3']) + + result = ls.add_peer('R4') + self.assertTrue(result) + self.assertEqual(ls.peers, ['R2', 'R3', 'R4']) + result = ls.add_peer('R2') + self.assertFalse(result) + self.assertEqual(ls.peers, ['R2', 'R3', 'R4']) + + result = ls.del_peer('R3') + self.assertTrue(result) + self.assertEqual(ls.peers, ['R2', 'R4']) + result = ls.del_peer('R5') + self.assertFalse(result) + self.assertEqual(ls.peers, ['R2', 'R4']) + + encoded = ls.to_dict() + new_ls = LinkState(encoded) + self.assertEqual(new_ls.id, 'R1') + self.assertEqual(new_ls.area, 'area') + self.assertEqual(new_ls.ls_seq, 2) + self.assertEqual(new_ls.peers, ['R2', 'R4']) + + + def test_hello_message(self): + msg1 = MessageHELLO(None, 'R1', 'area', ['R2', 'R3', 'R4']) + self.assertEqual(msg1.get_opcode(), "HELLO") + self.assertEqual(msg1.id, 'R1') + self.assertEqual(msg1.area, 'area') + self.assertEqual(msg1.seen_peers, ['R2', 'R3', 'R4']) + encoded = msg1.to_dict() + msg2 = MessageHELLO(encoded) + self.assertEqual(msg2.get_opcode(), "HELLO") + self.assertEqual(msg2.id, 'R1') + self.assertEqual(msg2.area, 'area') + self.assertEqual(msg2.seen_peers, ['R2', 'R3', 'R4']) + self.assertTrue(msg2.is_seen('R3')) + self.assertFalse(msg2.is_seen('R9')) + + +class NodeTrackerTest(unittest.TestCase): + def log(self, level, text): + pass + + def add_neighbor_router(self, address, router_bit, link_bit): + self.address = address + self.router_bit = router_bit + self.link_bit = link_bit + self.calls += 1 + + def del_neighbor_router(self, router_bit): + self.address = None + self.router_bit = router_bit + self.link_bit = None + self.calls += 1 + + def add_remote_router(self, address, router_bit): + self.address = address + self.router_bit = router_bit + self.link_bit = None + self.calls += 1 + + def del_remote_router(self, router_bit): + self.address = None + self.router_bit = router_bit + self.link_bit = None + self.calls += 1 + + def reset(self): + self.address = None + self.router_bit = None + self.link_bit = None + self.area = "area" + self.calls = 0 + + def test_node_tracker_limits(self): + tracker = NodeTracker(self, 5) + + self.reset() + tracker.new_neighbor('A', 1) + self.assertEqual(self.address, 'amqp:/_topo/area/A') + self.assertEqual(self.link_bit, 1) + self.assertEqual(self.router_bit, 1) + self.assertEqual(self.calls, 1) + + self.reset() + tracker.new_neighbor('B', 5) + self.assertEqual(self.address, 'amqp:/_topo/area/B') + self.assertEqual(self.link_bit, 5) + self.assertEqual(self.router_bit, 2) + self.assertEqual(self.calls, 1) + + self.reset() + tracker.new_neighbor('C', 6) + self.assertEqual(self.address, 'amqp:/_topo/area/C') + self.assertEqual(self.link_bit, 6) + self.assertEqual(self.router_bit, 3) + self.assertEqual(self.calls, 1) + + self.reset() + tracker.new_neighbor('D', 7) + self.assertEqual(self.address, 'amqp:/_topo/area/D') + self.assertEqual(self.link_bit, 7) + self.assertEqual(self.router_bit, 4) + self.assertEqual(self.calls, 1) + + self.reset() + try: + tracker.new_neighbor('E', 9) + AssertFalse("We shouldn't be here") + except: + pass + + self.reset() + tracker.lost_neighbor('C') + self.assertEqual(self.router_bit, 3) + self.assertEqual(self.calls, 1) + + self.reset() + tracker.new_neighbor('E', 9) + self.assertEqual(self.address, 'amqp:/_topo/area/E') + self.assertEqual(self.link_bit, 9) + self.assertEqual(self.router_bit, 3) + self.assertEqual(self.calls, 1) + + + def test_node_tracker_remote_neighbor(self): + tracker = NodeTracker(self, 5) + + self.reset() + tracker.new_node('A') + self.assertEqual(self.address, 'amqp:/_topo/area/A') + self.assertFalse(self.link_bit) + self.assertEqual(self.router_bit, 1) + self.assertEqual(self.calls, 1) + + self.reset() + tracker.new_neighbor('A', 3) + self.assertEqual(self.address, 'amqp:/_topo/area/A') + self.assertEqual(self.link_bit, 3) + self.assertEqual(self.router_bit, 1) + self.assertEqual(self.calls, 2) + + self.reset() + tracker.lost_node('A') + self.assertFalse(self.address) + self.assertFalse(self.link_bit) + self.assertFalse(self.router_bit) + self.assertEqual(self.calls, 0) + + self.reset() + tracker.lost_neighbor('A') + self.assertEqual(self.router_bit, 1) + self.assertEqual(self.calls, 1) + + + def test_node_tracker_neighbor_remote(self): + tracker = NodeTracker(self, 5) + + self.reset() + tracker.new_neighbor('A', 3) + self.assertEqual(self.address, 'amqp:/_topo/area/A') + self.assertEqual(self.link_bit, 3) + self.assertEqual(self.router_bit, 1) + self.assertEqual(self.calls, 1) + + self.reset() + tracker.new_node('A') + self.assertFalse(self.address) + self.assertFalse(self.link_bit) + self.assertFalse(self.router_bit) + self.assertEqual(self.calls, 0) + + self.reset() + tracker.lost_neighbor('A') + self.assertEqual(self.address, 'amqp:/_topo/area/A') + self.assertEqual(self.router_bit, 1) + self.assertEqual(self.calls, 2) + + self.reset() + tracker.lost_node('A') + self.assertEqual(self.router_bit, 1) + self.assertEqual(self.calls, 1) class NeighborTest(unittest.TestCase): - def log(self, level, text): - pass - - def send(self, dest, msg): - self.sent.append((dest, msg)) - - def local_link_state_changed(self, link_state): - self.local_link_state = link_state - - def new_neighbor(self, rid): - self.neighbors[rid] = None - - def lost_neighbor(self, rid): - self.neighbors.pop(rid) - - def setUp(self): - self.sent = [] - self.local_link_state = None - self.id = "R1" - self.area = "area" - self.config = Configuration() - self.neighbors = {} - - def test_hello_sent(self): - self.sent = [] - self.local_link_state = None - self.engine = NeighborEngine(self) - self.engine.tick(0.5) - self.assertEqual(self.sent, []) - self.engine.tick(1.5) - self.assertEqual(len(self.sent), 1) - dest, msg = self.sent.pop(0) - self.assertEqual(dest, "_local/qdxrouter") - self.assertEqual(msg.get_opcode(), "HELLO") - self.assertEqual(msg.id, self.id) - self.assertEqual(msg.area, self.area) - self.assertEqual(msg.seen_peers, []) - self.assertEqual(self.local_link_state, None) - - def test_sees_peer(self): - self.sent = [] - self.local_link_state = None - self.engine = NeighborEngine(self) - self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', []), 2.0) - self.engine.tick(5.0) - self.assertEqual(len(self.sent), 1) - dest, msg = self.sent.pop(0) - self.assertEqual(msg.seen_peers, ['R2']) - - def test_establish_peer(self): - self.sent = [] - self.local_link_state = None - self.engine = NeighborEngine(self) - self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', ['R1']), 0.5) - self.engine.tick(1.0) - self.engine.tick(2.0) - self.engine.tick(3.0) - self.assertEqual(self.local_link_state.id, 'R1') - self.assertEqual(self.local_link_state.area, 'area') - self.assertEqual(self.local_link_state.ls_seq, 1) - self.assertEqual(self.local_link_state.peers, ['R2']) - - def test_establish_multiple_peers(self): - self.sent = [] - self.local_link_state = None - self.engine = NeighborEngine(self) - self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', ['R1']), 0.5) - self.engine.tick(1.0) - self.engine.handle_hello(MessageHELLO(None, 'R3', 'area', ['R1', 'R2']), 1.5) - self.engine.tick(2.0) - self.engine.handle_hello(MessageHELLO(None, 'R4', 'area', ['R1']), 2.5) - self.engine.handle_hello(MessageHELLO(None, 'R5', 'area', ['R2']), 2.5) - self.engine.handle_hello(MessageHELLO(None, 'R6', 'area', ['R1']), 2.5) - self.engine.tick(3.0) - self.assertEqual(self.local_link_state.id, 'R1') - self.assertEqual(self.local_link_state.area, 'area') - self.assertEqual(self.local_link_state.ls_seq, 3) - self.local_link_state.peers.sort() - self.assertEqual(self.local_link_state.peers, ['R2', 'R3', 'R4', 'R6']) - - def test_timeout_peer(self): - self.sent = [] - self.local_link_state = None - self.engine = NeighborEngine(self) - self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', ['R3', 'R1']), 2.0) - self.engine.tick(5.0) - self.engine.tick(17.1) - self.assertEqual(self.local_link_state.id, 'R1') - self.assertEqual(self.local_link_state.area, 'area') - self.assertEqual(self.local_link_state.ls_seq, 2) - self.assertEqual(self.local_link_state.peers, []) + def log(self, level, text): + pass + + def send(self, dest, msg): + self.sent.append((dest, msg)) + + def local_link_state_changed(self, link_state): + self.local_link_state = link_state + + def new_neighbor(self, rid, lbit): + self.neighbors[rid] = None + + def lost_neighbor(self, rid): + self.neighbors.pop(rid) + + def setUp(self): + self.sent = [] + self.local_link_state = None + self.id = "R1" + self.area = "area" + self.config = Configuration() + self.neighbors = {} + + def test_hello_sent(self): + self.sent = [] + self.local_link_state = None + self.engine = NeighborEngine(self) + self.engine.tick(0.5) + self.assertEqual(self.sent, []) + self.engine.tick(1.5) + self.assertEqual(len(self.sent), 1) + dest, msg = self.sent.pop(0) + self.assertEqual(dest, "amqp:/_local/qdxhello") + self.assertEqual(msg.get_opcode(), "HELLO") + self.assertEqual(msg.id, self.id) + self.assertEqual(msg.area, self.area) + self.assertEqual(msg.seen_peers, []) + self.assertEqual(self.local_link_state, None) + + def test_sees_peer(self): + self.sent = [] + self.local_link_state = None + self.engine = NeighborEngine(self) + self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', []), 2.0, 0) + self.engine.tick(5.0) + self.assertEqual(len(self.sent), 1) + dest, msg = self.sent.pop(0) + self.assertEqual(msg.seen_peers, ['R2']) + + def test_establish_peer(self): + self.sent = [] + self.local_link_state = None + self.engine = NeighborEngine(self) + self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', ['R1']), 0.5, 0) + self.engine.tick(1.0) + self.engine.tick(2.0) + self.engine.tick(3.0) + self.assertEqual(self.local_link_state.id, 'R1') + self.assertEqual(self.local_link_state.area, 'area') + self.assertEqual(self.local_link_state.ls_seq, 1) + self.assertEqual(self.local_link_state.peers, ['R2']) + + def test_establish_multiple_peers(self): + self.sent = [] + self.local_link_state = None + self.engine = NeighborEngine(self) + self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', ['R1']), 0.5, 0) + self.engine.tick(1.0) + self.engine.handle_hello(MessageHELLO(None, 'R3', 'area', ['R1', 'R2']), 1.5, 0) + self.engine.tick(2.0) + self.engine.handle_hello(MessageHELLO(None, 'R4', 'area', ['R1']), 2.5, 0) + self.engine.handle_hello(MessageHELLO(None, 'R5', 'area', ['R2']), 2.5, 0) + self.engine.handle_hello(MessageHELLO(None, 'R6', 'area', ['R1']), 2.5, 0) + self.engine.tick(3.0) + self.assertEqual(self.local_link_state.id, 'R1') + self.assertEqual(self.local_link_state.area, 'area') + self.assertEqual(self.local_link_state.ls_seq, 3) + self.local_link_state.peers.sort() + self.assertEqual(self.local_link_state.peers, ['R2', 'R3', 'R4', 'R6']) + + def test_timeout_peer(self): + self.sent = [] + self.local_link_state = None + self.engine = NeighborEngine(self) + self.engine.handle_hello(MessageHELLO(None, 'R2', 'area', ['R3', 'R1']), 2.0, 0) + self.engine.tick(5.0) + self.engine.tick(17.1) + self.assertEqual(self.local_link_state.id, 'R1') + self.assertEqual(self.local_link_state.area, 'area') + self.assertEqual(self.local_link_state.ls_seq, 2) + self.assertEqual(self.local_link_state.peers, []) class PathTest(unittest.TestCase): - def setUp(self): - self.id = 'R1' - self.area = 'area' - self.next_hops = None - self.engine = PathEngine(self) - - def log(self, level, text): - pass - - def next_hops_changed(self, nh): - self.next_hops = nh - - def test_topology1(self): - """ - - +====+ +----+ +----+ - | R1 |------| R2 |------| R3 | - +====+ +----+ +----+ - - """ - collection = { 'R1': LinkState(None, 'R1', 'area', 1, ['R2']), - 'R2': LinkState(None, 'R2', 'area', 1, ['R1', 'R3']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R2']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 2) - self.assertEqual(self.next_hops['R2'], 'R2') - self.assertEqual(self.next_hops['R3'], 'R2') - - def test_topology2(self): - """ - - +====+ +----+ +----+ - | R1 |------| R2 |------| R4 | - +====+ +----+ +----+ - | | - +----+ +----+ +----+ - | R3 |------| R5 |------| R6 | - +----+ +----+ +----+ - - """ - collection = { 'R1': LinkState(None, 'R1', 'area', 1, ['R2']), - 'R2': LinkState(None, 'R2', 'area', 1, ['R1', 'R3', 'R4']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R2', 'R5']), - 'R4': LinkState(None, 'R4', 'area', 1, ['R2', 'R5']), - 'R5': LinkState(None, 'R5', 'area', 1, ['R3', 'R4', 'R6']), - 'R6': LinkState(None, 'R6', 'area', 1, ['R5']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 5) - self.assertEqual(self.next_hops['R2'], 'R2') - self.assertEqual(self.next_hops['R3'], 'R2') - self.assertEqual(self.next_hops['R4'], 'R2') - self.assertEqual(self.next_hops['R5'], 'R2') - self.assertEqual(self.next_hops['R6'], 'R2') - - def test_topology3(self): - """ - - +----+ +----+ +----+ - | R2 |------| R3 |------| R4 | - +----+ +----+ +----+ - | | - +====+ +----+ +----+ - | R1 |------| R5 |------| R6 | - +====+ +----+ +----+ - - """ - collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), - 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), - 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), - 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), - 'R6': LinkState(None, 'R6', 'area', 1, ['R5']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 5) - self.assertEqual(self.next_hops['R2'], 'R3') - self.assertEqual(self.next_hops['R3'], 'R3') - self.assertEqual(self.next_hops['R4'], 'R3') - self.assertEqual(self.next_hops['R5'], 'R5') - self.assertEqual(self.next_hops['R6'], 'R5') - - def test_topology4(self): - """ - - +----+ +----+ +----+ - | R2 |------| R3 |------| R4 | - +----+ +----+ +----+ - | | - +====+ +----+ +----+ - | R1 |------| R5 |------| R6 |------ R7 (no ls from R7) - +====+ +----+ +----+ - - """ - collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), - 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), - 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), - 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), - 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 6) - self.assertEqual(self.next_hops['R2'], 'R3') - self.assertEqual(self.next_hops['R3'], 'R3') - self.assertEqual(self.next_hops['R4'], 'R3') - self.assertEqual(self.next_hops['R5'], 'R5') - self.assertEqual(self.next_hops['R6'], 'R5') - self.assertEqual(self.next_hops['R7'], 'R5') - - def test_topology5(self): - """ - - +----+ +----+ +----+ - | R2 |------| R3 |------| R4 | - +----+ +----+ +----+ - | | | - | +====+ +----+ +----+ - +--------| R1 |------| R5 |------| R6 |------ R7 (no ls from R7) - +====+ +----+ +----+ - - """ - collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3', 'R1']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), - 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), - 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5', 'R2']), - 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), - 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 6) - self.assertEqual(self.next_hops['R2'], 'R2') - self.assertEqual(self.next_hops['R3'], 'R3') - self.assertEqual(self.next_hops['R4'], 'R3') - self.assertEqual(self.next_hops['R5'], 'R5') - self.assertEqual(self.next_hops['R6'], 'R5') - self.assertEqual(self.next_hops['R7'], 'R5') - - def test_topology5_with_asymmetry1(self): - """ - - +----+ +----+ +----+ - | R2 |------| R3 |------| R4 | - +----+ +----+ +----+ - ^ | | - ^ +====+ +----+ +----+ - +-<-<-<--| R1 |------| R5 |------| R6 |------ R7 (no ls from R7) - +====+ +----+ +----+ - - """ - collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), - 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), - 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5', 'R2']), - 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), - 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 6) - self.assertEqual(self.next_hops['R2'], 'R2') - self.assertEqual(self.next_hops['R3'], 'R3') - self.assertEqual(self.next_hops['R4'], 'R3') - self.assertEqual(self.next_hops['R5'], 'R5') - self.assertEqual(self.next_hops['R6'], 'R5') - self.assertEqual(self.next_hops['R7'], 'R5') - - def test_topology5_with_asymmetry2(self): - """ - - +----+ +----+ +----+ - | R2 |------| R3 |------| R4 | - +----+ +----+ +----+ - v | | - v +====+ +----+ +----+ - +->->->->| R1 |------| R5 |------| R6 |------ R7 (no ls from R7) - +====+ +----+ +----+ - - """ - collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3', 'R1']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), - 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), - 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), - 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), - 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 6) - self.assertEqual(self.next_hops['R2'], 'R3') - self.assertEqual(self.next_hops['R3'], 'R3') - self.assertEqual(self.next_hops['R4'], 'R3') - self.assertEqual(self.next_hops['R5'], 'R5') - self.assertEqual(self.next_hops['R6'], 'R5') - self.assertEqual(self.next_hops['R7'], 'R5') - - def test_topology5_with_asymmetry3(self): - """ - - +----+ +----+ +----+ - | R2 |------| R3 |------| R4 | - +----+ +----+ +----+ - v | | - v +====+ +----+ +----+ - +->->->->| R1 |------| R5 |<-<-<-| R6 |------ R7 (no ls from R7) - +====+ +----+ +----+ - - """ - collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3', 'R1']), - 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), - 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), - 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), - 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4']), - 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } - self.engine.ls_collection_changed(collection) - self.engine.tick(1.0) - self.assertEqual(len(self.next_hops), 4) - self.assertEqual(self.next_hops['R2'], 'R3') - self.assertEqual(self.next_hops['R3'], 'R3') - self.assertEqual(self.next_hops['R4'], 'R3') - self.assertEqual(self.next_hops['R5'], 'R5') + def setUp(self): + self.id = 'R1' + self.area = 'area' + self.next_hops = None + self.valid_origins = None + self.engine = PathEngine(self) + + def log(self, level, text): + pass + + def next_hops_changed(self, nh): + self.next_hops = nh + + def valid_origins_changed(self, vo): + self.valid_origins = vo + + def test_topology1(self): + """ + + +====+ +----+ +----+ + | R1 |------| R2 |------| R3 | + +====+ +----+ +----+ + + """ + collection = { 'R1': LinkState(None, 'R1', 'area', 1, ['R2']), + 'R2': LinkState(None, 'R2', 'area', 1, ['R1', 'R3']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R2']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 2) + self.assertEqual(self.next_hops['R2'], 'R2') + self.assertEqual(self.next_hops['R3'], 'R2') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.assertEqual(self.valid_origins['R2'], []) + self.assertEqual(self.valid_origins['R3'], []) + + def test_topology2(self): + """ + + +====+ +----+ +----+ + | R1 |------| R2 |------| R4 | + +====+ +----+ +----+ + | | + +----+ +----+ +----+ + | R3 |------| R5 |------| R6 | + +----+ +----+ +----+ + + """ + collection = { 'R1': LinkState(None, 'R1', 'area', 1, ['R2']), + 'R2': LinkState(None, 'R2', 'area', 1, ['R1', 'R3', 'R4']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R2', 'R5']), + 'R4': LinkState(None, 'R4', 'area', 1, ['R2', 'R5']), + 'R5': LinkState(None, 'R5', 'area', 1, ['R3', 'R4', 'R6']), + 'R6': LinkState(None, 'R6', 'area', 1, ['R5']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 5) + self.assertEqual(self.next_hops['R2'], 'R2') + self.assertEqual(self.next_hops['R3'], 'R2') + self.assertEqual(self.next_hops['R4'], 'R2') + self.assertEqual(self.next_hops['R5'], 'R2') + self.assertEqual(self.next_hops['R6'], 'R2') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.valid_origins['R4'].sort() + self.valid_origins['R5'].sort() + self.valid_origins['R6'].sort() + self.assertEqual(self.valid_origins['R2'], []) + self.assertEqual(self.valid_origins['R3'], []) + self.assertEqual(self.valid_origins['R4'], []) + self.assertEqual(self.valid_origins['R5'], []) + self.assertEqual(self.valid_origins['R6'], []) + + def test_topology3(self): + """ + + +----+ +----+ +----+ + | R2 |------| R3 |------| R4 | + +----+ +----+ +----+ + | | + +====+ +----+ +----+ + | R1 |------| R5 |------| R6 | + +====+ +----+ +----+ + + """ + collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), + 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), + 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), + 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), + 'R6': LinkState(None, 'R6', 'area', 1, ['R5']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 5) + self.assertEqual(self.next_hops['R2'], 'R3') + self.assertEqual(self.next_hops['R3'], 'R3') + self.assertEqual(self.next_hops['R4'], 'R3') + self.assertEqual(self.next_hops['R5'], 'R5') + self.assertEqual(self.next_hops['R6'], 'R5') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.valid_origins['R4'].sort() + self.valid_origins['R5'].sort() + self.valid_origins['R6'].sort() + self.assertEqual(self.valid_origins['R2'], ['R5', 'R6']) + self.assertEqual(self.valid_origins['R3'], ['R5', 'R6']) + self.assertEqual(self.valid_origins['R4'], []) + self.assertEqual(self.valid_origins['R5'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R6'], ['R2', 'R3']) + + def test_topology4(self): + """ + + +----+ +----+ +----+ + | R2 |------| R3 |------| R4 | + +----+ +----+ +----+ + | | + +====+ +----+ +----+ + | R1 |------| R5 |------| R6 |------ R7 (no ls from R7) + +====+ +----+ +----+ + + """ + collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), + 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), + 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), + 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), + 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 6) + self.assertEqual(self.next_hops['R2'], 'R3') + self.assertEqual(self.next_hops['R3'], 'R3') + self.assertEqual(self.next_hops['R4'], 'R3') + self.assertEqual(self.next_hops['R5'], 'R5') + self.assertEqual(self.next_hops['R6'], 'R5') + self.assertEqual(self.next_hops['R7'], 'R5') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.valid_origins['R4'].sort() + self.valid_origins['R5'].sort() + self.valid_origins['R6'].sort() + self.valid_origins['R7'].sort() + self.assertEqual(self.valid_origins['R2'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R3'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R4'], []) + self.assertEqual(self.valid_origins['R5'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R6'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R7'], ['R2', 'R3']) + + def test_topology5(self): + """ + + +----+ +----+ +----+ + | R2 |------| R3 |------| R4 | + +----+ +----+ +----+ + | | | + | +====+ +----+ +----+ + +--------| R1 |------| R5 |------| R6 |------ R7 (no ls from R7) + +====+ +----+ +----+ + + """ + collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3', 'R1']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), + 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), + 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5', 'R2']), + 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), + 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 6) + self.assertEqual(self.next_hops['R2'], 'R2') + self.assertEqual(self.next_hops['R3'], 'R3') + self.assertEqual(self.next_hops['R4'], 'R3') + self.assertEqual(self.next_hops['R5'], 'R5') + self.assertEqual(self.next_hops['R6'], 'R5') + self.assertEqual(self.next_hops['R7'], 'R5') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.valid_origins['R4'].sort() + self.valid_origins['R5'].sort() + self.valid_origins['R6'].sort() + self.valid_origins['R7'].sort() + self.assertEqual(self.valid_origins['R2'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R3'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R4'], []) + self.assertEqual(self.valid_origins['R5'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R6'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R7'], ['R2', 'R3']) + + def test_topology5_with_asymmetry1(self): + """ + + +----+ +----+ +----+ + | R2 |------| R3 |------| R4 | + +----+ +----+ +----+ + ^ | | + ^ +====+ +----+ +----+ + +-<-<-<--| R1 |------| R5 |------| R6 |------ R7 (no ls from R7) + +====+ +----+ +----+ + + """ + collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), + 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), + 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5', 'R2']), + 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), + 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 6) + self.assertEqual(self.next_hops['R2'], 'R2') + self.assertEqual(self.next_hops['R3'], 'R3') + self.assertEqual(self.next_hops['R4'], 'R3') + self.assertEqual(self.next_hops['R5'], 'R5') + self.assertEqual(self.next_hops['R6'], 'R5') + self.assertEqual(self.next_hops['R7'], 'R5') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.valid_origins['R4'].sort() + self.valid_origins['R5'].sort() + self.valid_origins['R6'].sort() + self.valid_origins['R7'].sort() + self.assertEqual(self.valid_origins['R2'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R3'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R4'], []) + self.assertEqual(self.valid_origins['R5'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R6'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R7'], ['R2', 'R3']) + + def test_topology5_with_asymmetry2(self): + """ + + +----+ +----+ +----+ + | R2 |------| R3 |------| R4 | + +----+ +----+ +----+ + v | | + v +====+ +----+ +----+ + +->->->->| R1 |------| R5 |------| R6 |------ R7 (no ls from R7) + +====+ +----+ +----+ + + """ + collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3', 'R1']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), + 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), + 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), + 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4', 'R6']), + 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 6) + self.assertEqual(self.next_hops['R2'], 'R3') + self.assertEqual(self.next_hops['R3'], 'R3') + self.assertEqual(self.next_hops['R4'], 'R3') + self.assertEqual(self.next_hops['R5'], 'R5') + self.assertEqual(self.next_hops['R6'], 'R5') + self.assertEqual(self.next_hops['R7'], 'R5') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.valid_origins['R4'].sort() + self.valid_origins['R5'].sort() + self.valid_origins['R6'].sort() + self.valid_origins['R7'].sort() + self.assertEqual(self.valid_origins['R2'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R3'], ['R5', 'R6', 'R7']) + self.assertEqual(self.valid_origins['R4'], []) + self.assertEqual(self.valid_origins['R5'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R6'], ['R2', 'R3']) + self.assertEqual(self.valid_origins['R7'], ['R2', 'R3']) + + def test_topology5_with_asymmetry3(self): + """ + + +----+ +----+ +----+ + | R2 |------| R3 |------| R4 | + +----+ +----+ +----+ + v | | + v +====+ +----+ +----+ + +->->->->| R1 |------| R5 |<-<-<-| R6 |------ R7 (no ls from R7) + +====+ +----+ +----+ + + """ + collection = { 'R2': LinkState(None, 'R2', 'area', 1, ['R3', 'R1']), + 'R3': LinkState(None, 'R3', 'area', 1, ['R1', 'R2', 'R4']), + 'R4': LinkState(None, 'R4', 'area', 1, ['R3', 'R5']), + 'R1': LinkState(None, 'R1', 'area', 1, ['R3', 'R5']), + 'R5': LinkState(None, 'R5', 'area', 1, ['R1', 'R4']), + 'R6': LinkState(None, 'R6', 'area', 1, ['R5', 'R7']) } + self.engine.ls_collection_changed(collection) + self.engine.tick(1.0) + self.assertEqual(len(self.next_hops), 4) + self.assertEqual(self.next_hops['R2'], 'R3') + self.assertEqual(self.next_hops['R3'], 'R3') + self.assertEqual(self.next_hops['R4'], 'R3') + self.assertEqual(self.next_hops['R5'], 'R5') + + self.valid_origins['R2'].sort() + self.valid_origins['R3'].sort() + self.valid_origins['R4'].sort() + self.valid_origins['R5'].sort() + self.assertEqual(self.valid_origins['R2'], ['R5']) + self.assertEqual(self.valid_origins['R3'], ['R5']) + self.assertEqual(self.valid_origins['R4'], []) + self.assertEqual(self.valid_origins['R5'], ['R2', 'R3']) if __name__ == '__main__': - unittest.main() + unittest.main() diff --git a/qpid/extras/dispatch/tests/system_tests_one_router.py b/qpid/extras/dispatch/tests/system_tests_one_router.py index b25568d283..1154b3ed37 100644 --- a/qpid/extras/dispatch/tests/system_tests_one_router.py +++ b/qpid/extras/dispatch/tests/system_tests_one_router.py @@ -29,7 +29,7 @@ class RouterTest(unittest.TestCase): if 'CTEST_SOURCE_DIR' not in os.environ: raise Exception("Environment variable 'CTEST_SOURCE_DIR' not set") srcdir = os.environ['CTEST_SOURCE_DIR'] - self.router = subprocess.Popen(['../router/dispatch-router', '-c', '%s/onerouter.conf' % srcdir], + self.router = subprocess.Popen(['../router/dispatch-router', '-c', '%s/config-1/A.conf' % srcdir], stderr=subprocess.PIPE, stdout=subprocess.PIPE) time.sleep(1) @@ -138,6 +138,65 @@ class RouterTest(unittest.TestCase): M4.stop() + def test_2a_multicast_unsettled(self): + addr = "amqp://0.0.0.0:20000/pre_settled/multicast/1" + M1 = Messenger() + M2 = Messenger() + M3 = Messenger() + M4 = Messenger() + + M1.timeout = 1.0 + M2.timeout = 1.0 + M3.timeout = 1.0 + M4.timeout = 1.0 + + M1.outgoing_window = 5 + M2.incoming_window = 5 + M3.incoming_window = 5 + M4.incoming_window = 5 + + M1.start() + M2.start() + M3.start() + M4.start() + self.subscribe(M2, addr) + self.subscribe(M3, addr) + self.subscribe(M4, addr) + + tm = Message() + rm = Message() + + tm.address = addr + for i in range(2): + tm.body = {'number': i} + M1.put(tm) + M1.send(0) + + for i in range(2): + M2.recv(1) + trk = M2.get(rm) + M2.accept(trk) + M2.settle(trk) + self.assertEqual(i, rm.body['number']) + + M3.recv(1) + trk = M3.get(rm) + M3.accept(trk) + M3.settle(trk) + self.assertEqual(i, rm.body['number']) + + M4.recv(1) + trk = M4.get(rm) + M4.accept(trk) + M4.settle(trk) + self.assertEqual(i, rm.body['number']) + + M1.stop() + M2.stop() + M3.stop() + M4.stop() + + def test_3_propagated_disposition(self): addr = "amqp://0.0.0.0:20000/unsettled/1" M1 = Messenger() @@ -310,8 +369,8 @@ class RouterTest(unittest.TestCase): self.assertEqual(i, rm.body['number']) da = rm.instructions self.assertEqual(da.__class__, dict) - self.assertEqual(da['qdx.ingress'], 'Qpid.Dispatch.Router.A') - self.assertFalse('qdx.trace' in da) + self.assertEqual(da['qdx.ingress'], '_topo/area/Qpid.Dispatch.Router.A/') + self.assertEqual(da['qdx.trace'], ['_topo/area/Qpid.Dispatch.Router.A/']) ## ## Pre-existing ingress @@ -329,7 +388,7 @@ class RouterTest(unittest.TestCase): da = rm.instructions self.assertEqual(da.__class__, dict) self.assertEqual(da['qdx.ingress'], 'ingress-router') - self.assertFalse('qdx.trace' in da) + self.assertEqual(da['qdx.trace'], ['_topo/area/Qpid.Dispatch.Router.A/']) ## ## Invalid trace type @@ -346,8 +405,8 @@ class RouterTest(unittest.TestCase): self.assertEqual(i, rm.body['number']) da = rm.instructions self.assertEqual(da.__class__, dict) - self.assertEqual(da['qdx.ingress'], 'Qpid.Dispatch.Router.A') - self.assertFalse('qdx.trace' in da) + self.assertEqual(da['qdx.ingress'], '_topo/area/Qpid.Dispatch.Router.A/') + self.assertEqual(da['qdx.trace'], ['_topo/area/Qpid.Dispatch.Router.A/']) ## ## Empty trace @@ -364,8 +423,8 @@ class RouterTest(unittest.TestCase): self.assertEqual(i, rm.body['number']) da = rm.instructions self.assertEqual(da.__class__, dict) - self.assertEqual(da['qdx.ingress'], 'Qpid.Dispatch.Router.A') - self.assertEqual(da['qdx.trace'], ['Qpid.Dispatch.Router.A']) + self.assertEqual(da['qdx.ingress'], '_topo/area/Qpid.Dispatch.Router.A/') + self.assertEqual(da['qdx.trace'], ['_topo/area/Qpid.Dispatch.Router.A/']) ## ## Non-empty trace @@ -382,8 +441,8 @@ class RouterTest(unittest.TestCase): self.assertEqual(i, rm.body['number']) da = rm.instructions self.assertEqual(da.__class__, dict) - self.assertEqual(da['qdx.ingress'], 'Qpid.Dispatch.Router.A') - self.assertEqual(da['qdx.trace'], ['first.hop', 'Qpid.Dispatch.Router.A']) + self.assertEqual(da['qdx.ingress'], '_topo/area/Qpid.Dispatch.Router.A/') + self.assertEqual(da['qdx.trace'], ['first.hop', '_topo/area/Qpid.Dispatch.Router.A/']) M1.stop() M2.stop() diff --git a/qpid/extras/dispatch/tests/tool_test.c b/qpid/extras/dispatch/tests/tool_test.c index ba047d928c..2c90714b3d 100644 --- a/qpid/extras/dispatch/tests/tool_test.c +++ b/qpid/extras/dispatch/tests/tool_test.c @@ -21,6 +21,8 @@ #include <stdio.h> #include <string.h> #include <qpid/dispatch/ctools.h> +#include <qpid/dispatch/bitmask.h> +#include "alloc_private.h" typedef struct item_t { DEQ_LINKS(struct item_t); @@ -188,12 +190,43 @@ static char* test_deq_basic2(void *context) } +static char* test_bitmask(void *context) +{ + dx_bitmask_t *bm; + int num; + + bm = dx_bitmask(0); + if (!bm) return "Can't allocate a bit mask"; + if (dx_bitmask_first_set(bm, &num)) return "Expected no first set bit"; + + dx_bitmask_set_bit(bm, 3); + dx_bitmask_set_bit(bm, 500); + + if (!dx_bitmask_first_set(bm, &num)) return "Expected first set bit"; + if (num != 3) return "Expected first set bit to be 3"; + + dx_bitmask_clear_bit(bm, num); + + if (!dx_bitmask_first_set(bm, &num)) return "Expected first set bit (2)"; + if (num != 500) return "Expected first set bit to be 500"; + + dx_bitmask_clear_bit(bm, num); + if (dx_bitmask_first_set(bm, &num)) return "Expected no first set bit (2)"; + + dx_bitmask_free(bm); + + return 0; +} + + int tool_tests(void) { int result = 0; + dx_alloc_initialize(); TEST_CASE(test_deq_basic, 0); TEST_CASE(test_deq_basic2, 0); + TEST_CASE(test_bitmask, 0); return result; } diff --git a/qpid/extras/dispatch/tools/src/py/qdstat b/qpid/extras/dispatch/tools/src/py/qdstat new file mode 100755 index 0000000000..32594b4d4e --- /dev/null +++ b/qpid/extras/dispatch/tools/src/py/qdstat @@ -0,0 +1,328 @@ +#!/usr/bin/env python + +# +# 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 os +from optparse import OptionParser, OptionGroup +import sys +import locale +import socket +import re +from proton import Messenger, Message + +home = os.environ.get("QD_TOOLS_HOME", os.path.normpath("/usr/share/qd-tools")) +sys.path.append(os.path.join(home, "python")) + +from qdtoollibs import Display, Header, Sorter, YN, Commas, TimeLong + + +class Config: + def __init__(self): + self._host = "0.0.0.0" + self._connTimeout = 10 + self._types = "" + self._limit = 50 + self._increasing = False + self._sortcol = None + +config = Config() +conn_options = {} + +def OptionsAndArguments(argv): + """ Set global variables for options, return arguments """ + + global config + global conn_options + + usage = \ +"""%prog -g [options] + %prog -c [options] + %prog -l [options] + %prog -n [options] + %prog -a [options] + %prog -m [options]""" + + parser = OptionParser(usage=usage) + + group1 = OptionGroup(parser, "General Options") + group1.add_option("-b", "--bus", action="store", type="string", default="0.0.0.0", metavar="<url>", + help="URL of the messaging bus to connect to") + group1.add_option("-t", "--timeout", action="store", type="int", default=10, metavar="<secs>", + help="Maximum time to wait for connection (in seconds)") + group1.add_option("--sasl-mechanism", action="store", type="string", metavar="<mech>", + help="SASL mechanism for authentication (e.g. EXTERNAL, ANONYMOUS, PLAIN, CRAM-MD5, DIGEST-MD5, GSSAPI). SASL automatically picks the most secure available mechanism - use this option to override.") + group1.add_option("--ssl-certificate", action="store", type="string", metavar="<cert>", help="Client SSL certificate (PEM Format)") + group1.add_option("--ssl-key", action="store", type="string", metavar="<key>", help="Client SSL private key (PEM Format)") + parser.add_option_group(group1) + + group2 = OptionGroup(parser, "Command Options") + group2.add_option("-g", "--general", help="Show General Broker Stats", action="store_const", const="g", dest="show") + group2.add_option("-c", "--connections", help="Show Connections", action="store_const", const="c", dest="show") + group2.add_option("-l", "--links", help="Show Router Links", action="store_const", const="l", dest="show") + group2.add_option("-n", "--nodes", help="Show Router Nodes", action="store_const", const="n", dest="show") + group2.add_option("-a", "--address", help="Show Router Addresses", action="store_const", const="a", dest="show") + group2.add_option("-m", "--memory", help="Show Broker Memory Stats", action="store_const", const="m", dest="show") + parser.add_option_group(group2) + + opts, args = parser.parse_args(args=argv) + + if not opts.show: + parser.error("You must specify one of these options: -g, -c, -l, -n, -a, or -m. For details, try $ qdstat --help") + + config._types = opts.show + config._host = opts.bus + config._connTimeout = opts.timeout + + return args + + +class BusManager: + def __init__(self): + pass + + def SetHost(self, host): + self.M = Messenger() + self.M.start() + self.M.route("amqp:/*", "amqp://%s/$1" % host) + self.address = "amqp:/_local/$management" + self.reply = "amqp:/temp.reply-address/0001" # FIX THIS! + self.M.subscribe(self.reply) + + def Disconnect(self): + self.M.stop() + + def displayConn(self): + pass + + def _addr_class(self, addr): + if not addr: + return "-" + if addr[0] == 'M' : return "mobile" + if addr[0] == 'R' : return "router" + if addr[0] == 'A' : return "area" + if addr[0] == 'L' : return "local" + return "unknown: %s" % addr[0] + + def _addr_text(self, addr): + if not addr: + return "-" + return addr[1:] + + def displayRouterLinks(self): + disp = Display(prefix=" ") + heads = [] + heads.append(Header("type")) + heads.append(Header("dir")) + heads.append(Header("rindex")) + heads.append(Header("class")) + heads.append(Header("addr")) + rows = [] + + request = Message() + response = Message() + + request.address = self.address + request.reply_to = self.reply + request.correlation_id = 1 + request.properties = {u'operation':u'GET', u'type':u'org.apache.qpid.dispatch.router.link'} + + self.M.put(request) + self.M.send() + + self.M.recv() + self.M.get(response) + + for link in response.body: + row = [] + row.append(link['link-type']) + row.append(link['link-dir']) + if link['link-type'] == "router": + row.append(link['index']) + else: + row.append('-') + row.append(self._addr_class(link['owning-addr'])) + row.append(self._addr_text(link['owning-addr'])) + rows.append(row) + title = "Router Links" + dispRows = rows + disp.formattedTable(title, heads, dispRows) + + def displayRouterNodes(self): + disp = Display(prefix=" ") + heads = [] + heads.append(Header("class")) + heads.append(Header("address")) + heads.append(Header("rindex")) + heads.append(Header("next-hop")) + heads.append(Header("link")) + rows = [] + + request = Message() + response = Message() + + request.address = self.address + request.reply_to = self.reply + request.correlation_id = 1 + request.properties = {u'operation':u'GET', u'type':u'org.apache.qpid.dispatch.router.node'} + + self.M.put(request) + self.M.send() + + self.M.recv() + self.M.get(response) + + for node in response.body: + row = [] + row.append(self._addr_class(node['addr'])) + row.append(self._addr_text(node['addr'])) + row.append(node['index']) + if node['next-hop'] != None: + row.append(node['next-hop']) + else: + row.append('-') + if node['router-link'] != None: + row.append(node['router-link']) + else: + row.append('-') + rows.append(row) + title = "Router Nodes" + dispRows = rows + disp.formattedTable(title, heads, dispRows) + + def displayAddresses(self): + disp = Display(prefix=" ") + heads = [] + heads.append(Header("class")) + heads.append(Header("address")) + heads.append(Header("in-proc", Header.Y)) + heads.append(Header("local", Header.COMMAS)) + heads.append(Header("remote", Header.COMMAS)) + heads.append(Header("in", Header.COMMAS)) + heads.append(Header("out", Header.COMMAS)) + heads.append(Header("thru", Header.COMMAS)) + heads.append(Header("to-proc", Header.COMMAS)) + heads.append(Header("from-proc", Header.COMMAS)) + rows = [] + + request = Message() + response = Message() + + request.address = self.address + request.reply_to = self.reply + request.correlation_id = 1 + request.properties = {u'operation':u'GET', u'type':u'org.apache.qpid.dispatch.router.address'} + + self.M.put(request) + self.M.send() + + self.M.recv() + self.M.get(response) + + for addr in response.body: + row = [] + row.append(self._addr_class(addr['addr'])) + row.append(self._addr_text(addr['addr'])) + row.append(addr['in-process']) + row.append(addr['subscriber-count']) + row.append(addr['remote-count']) + row.append(addr['deliveries-ingress']) + row.append(addr['deliveries-egress']) + row.append(addr['deliveries-transit']) + row.append(addr['deliveries-to-container']) + row.append(addr['deliveries-from-container']) + rows.append(row) + title = "Router Addresses" + sorter = Sorter(heads, rows, 'address', 0, True) + dispRows = sorter.getSorted() + disp.formattedTable(title, heads, dispRows) + + def displayMemory(self): + disp = Display(prefix=" ") + heads = [] + heads.append(Header("type")) + heads.append(Header("size", Header.COMMAS)) + heads.append(Header("batch")) + heads.append(Header("thread-max", Header.COMMAS)) + heads.append(Header("total", Header.COMMAS)) + heads.append(Header("in-threads", Header.COMMAS)) + heads.append(Header("rebal-in", Header.COMMAS)) + heads.append(Header("rebal-out", Header.COMMAS)) + rows = [] + + request = Message() + response = Message() + + request.address = self.address + request.reply_to = self.reply + request.correlation_id = 1 + request.properties = {u'operation':u'GET', u'type':u'org.apache.qpid.dispatch.allocator'} + + self.M.put(request) + self.M.send() + + self.M.recv() + self.M.get(response) + + for t in response.body: + row = [] + row.append(t['name']) + row.append(t['type_size']) + row.append(t['transfer_batch_size']) + row.append(t['local_free_list_max']) + row.append(t['total_alloc_from_heap']) + row.append(t['held_by_threads']) + row.append(t['batches_rebalanced_to_threads']) + row.append(t['batches_rebalanced_to_global']) + rows.append(row) + title = "Types" + sorter = Sorter(heads, rows, 'type', 0, True) + dispRows = sorter.getSorted() + disp.formattedTable(title, heads, dispRows) + + def displayMain(self, names, main): + if main == 'l': self.displayRouterLinks() + elif main == 'n': self.displayRouterNodes() + elif main == 'a': self.displayAddresses() + elif main == 'm': self.displayMemory() + + def display(self, names): + self.displayMain(names, config._types) + + +def main(argv=None): + + args = OptionsAndArguments(argv) + bm = BusManager() + + try: + bm.SetHost(config._host) + bm.display(args) + bm.Disconnect() + return 0 + except KeyboardInterrupt: + print + except Exception,e: + print "Failed: %s - %s" % (e.__class__.__name__, e) + + bm.Disconnect() # try to deallocate brokers + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py b/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py new file mode 100644 index 0000000000..378bf24ef1 --- /dev/null +++ b/qpid/extras/dispatch/tools/src/py/qdtoollibs/__init__.py @@ -0,0 +1,21 @@ +# +# 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. +# + +from qdtoollibs.disp import * + diff --git a/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py b/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py new file mode 100644 index 0000000000..529a727449 --- /dev/null +++ b/qpid/extras/dispatch/tools/src/py/qdtoollibs/disp.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python + +# +# 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. +# + +from time import strftime, gmtime + +def YN(val): + if val: + return 'Y' + return 'N' + +def Commas(value): + sval = str(value) + result = "" + while True: + if len(sval) == 0: + return result + left = sval[:-3] + right = sval[-3:] + result = right + result + if len(left) > 0: + result = ',' + result + sval = left + +def TimeLong(value): + return strftime("%c", gmtime(value / 1000000000)) + +def TimeShort(value): + return strftime("%X", gmtime(value / 1000000000)) + + +class Header: + """ """ + NONE = 1 + KMG = 2 + YN = 3 + Y = 4 + TIME_LONG = 5 + TIME_SHORT = 6 + DURATION = 7 + COMMAS = 8 + + def __init__(self, text, format=NONE): + self.text = text + self.format = format + + def __repr__(self): + return self.text + + def __str__(self): + return self.text + + def formatted(self, value): + try: + if value == None: + return '' + if self.format == Header.NONE: + return value + if self.format == Header.KMG: + return self.num(value) + if self.format == Header.YN: + if value: + return 'Y' + return 'N' + if self.format == Header.Y: + if value: + return 'Y' + return '' + if self.format == Header.TIME_LONG: + return TimeLong(value) + if self.format == Header.TIME_SHORT: + return TimeShort(value) + if self.format == Header.DURATION: + if value < 0: value = 0 + sec = value / 1000000000 + min = sec / 60 + hour = min / 60 + day = hour / 24 + result = "" + if day > 0: + result = "%dd " % day + if hour > 0 or result != "": + result += "%dh " % (hour % 24) + if min > 0 or result != "": + result += "%dm " % (min % 60) + result += "%ds" % (sec % 60) + return result + if self.format == Header.COMMAS: + return Commas(value) + except: + return "?" + + def numCell(self, value, tag): + fp = float(value) / 1000. + if fp < 10.0: + return "%1.2f%c" % (fp, tag) + if fp < 100.0: + return "%2.1f%c" % (fp, tag) + return "%4d%c" % (value / 1000, tag) + + def num(self, value): + if value < 1000: + return "%4d" % value + if value < 1000000: + return self.numCell(value, 'k') + value /= 1000 + if value < 1000000: + return self.numCell(value, 'm') + value /= 1000 + return self.numCell(value, 'g') + + +class Display: + """ Display formatting """ + + def __init__(self, spacing=2, prefix=" "): + self.tableSpacing = spacing + self.tablePrefix = prefix + self.timestampFormat = "%X" + + def formattedTable(self, title, heads, rows): + fRows = [] + for row in rows: + fRow = [] + col = 0 + for cell in row: + fRow.append(heads[col].formatted(cell)) + col += 1 + fRows.append(fRow) + headtext = [] + for head in heads: + headtext.append(head.text) + self.table(title, headtext, fRows) + + def table(self, title, heads, rows): + """ Print a table with autosized columns """ + + # Pad the rows to the number of heads + for row in rows: + diff = len(heads) - len(row) + for idx in range(diff): + row.append("") + + print title + if len (rows) == 0: + return + colWidth = [] + col = 0 + line = self.tablePrefix + for head in heads: + width = len (head) + for row in rows: + text = row[col] + if text.__class__ == str: + text = text.decode('utf-8') + cellWidth = len(unicode(text)) + if cellWidth > width: + width = cellWidth + colWidth.append (width + self.tableSpacing) + line = line + head + if col < len (heads) - 1: + for i in range (colWidth[col] - len (head)): + line = line + " " + col = col + 1 + print line + line = self.tablePrefix + for width in colWidth: + for i in range (width): + line = line + "=" + print line + + for row in rows: + line = self.tablePrefix + col = 0 + for width in colWidth: + text = row[col] + if text.__class__ == str: + text = text.decode('utf-8') + line = line + unicode(text) + if col < len (heads) - 1: + for i in range (width - len(unicode(text))): + line = line + " " + col = col + 1 + print line + + def do_setTimeFormat (self, fmt): + """ Select timestamp format """ + if fmt == "long": + self.timestampFormat = "%c" + elif fmt == "short": + self.timestampFormat = "%X" + + def timestamp (self, nsec): + """ Format a nanosecond-since-the-epoch timestamp for printing """ + return strftime (self.timestampFormat, gmtime (nsec / 1000000000)) + + def duration(self, nsec): + if nsec < 0: nsec = 0 + sec = nsec / 1000000000 + min = sec / 60 + hour = min / 60 + day = hour / 24 + result = "" + if day > 0: + result = "%dd " % day + if hour > 0 or result != "": + result += "%dh " % (hour % 24) + if min > 0 or result != "": + result += "%dm " % (min % 60) + result += "%ds" % (sec % 60) + return result + +class Sortable: + """ """ + def __init__(self, row, sortIndex): + self.row = row + self.sortIndex = sortIndex + if sortIndex >= len(row): + raise Exception("sort index exceeds row boundary") + + def __cmp__(self, other): + return cmp(self.row[self.sortIndex], other.row[self.sortIndex]) + + def getRow(self): + return self.row + +class Sorter: + """ """ + def __init__(self, heads, rows, sortCol, limit=0, inc=True): + col = 0 + for head in heads: + if head.text == sortCol: + break + col += 1 + if col == len(heads): + raise Exception("sortCol '%s', not found in headers" % sortCol) + + list = [] + for row in rows: + list.append(Sortable(row, col)) + list.sort() + if not inc: + list.reverse() + count = 0 + self.sorted = [] + for row in list: + self.sorted.append(row.getRow()) + count += 1 + if count == limit: + break + + def getSorted(self): + return self.sorted |