You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by tr...@apache.org on 2013/06/28 15:42:12 UTC
svn commit: r1497770 - in /qpid/trunk/qpid/extras/dispatch: src/
src/py/router/ tests/
Author: tross
Date: Fri Jun 28 13:42:12 2013
New Revision: 1497770
URL: http://svn.apache.org/r1497770
Log:
QPID-4967 - Added the Python routing engine and integrated its tests into ctest
Added:
qpid/trunk/qpid/extras/dispatch/src/py/router/
qpid/trunk/qpid/extras/dispatch/src/py/router/__init__.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/adapter.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/binding.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/configuration.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/data.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/link.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/mobile.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/neighbor.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/path.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/router_engine.py (with props)
qpid/trunk/qpid/extras/dispatch/src/py/router/routing.py (with props)
qpid/trunk/qpid/extras/dispatch/tests/router_engine_test.py (with props)
Modified:
qpid/trunk/qpid/extras/dispatch/src/agent.c
qpid/trunk/qpid/extras/dispatch/src/message.c
qpid/trunk/qpid/extras/dispatch/tests/CMakeLists.txt
Modified: qpid/trunk/qpid/extras/dispatch/src/agent.c
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/agent.c?rev=1497770&r1=1497769&r2=1497770&view=diff
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/agent.c (original)
+++ qpid/trunk/qpid/extras/dispatch/src/agent.c Fri Jun 28 13:42:12 2013
@@ -168,7 +168,7 @@ static void dx_agent_process_request(dx_
return;
//
- // Try to get a map-view of the application-properties. Exit if the it is not a map-value.
+ // Try to get a map-view of the application-properties. Exit if it is not a map-value.
//
dx_field_map_t *map = dx_field_map(ap, 1);
if (map == 0) {
Modified: qpid/trunk/qpid/extras/dispatch/src/message.c
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/message.c?rev=1497770&r1=1497769&r2=1497770&view=diff
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/message.c (original)
+++ qpid/trunk/qpid/extras/dispatch/src/message.c Fri Jun 28 13:42:12 2013
@@ -95,7 +95,7 @@ static int traverse_field(unsigned char
break;
}
- if (field) {
+ if (field && !field->parsed) {
field->buffer = *buffer;
field->offset = *cursor - dx_buffer_base(*buffer);
field->length = consume;
@@ -280,7 +280,7 @@ static dx_field_location_t *dx_message_f
result = traverse_field(&cursor, &buffer, 0); // message_id
if (!result) return 0;
- result = traverse_field(&cursor, &buffer, 0); // user_id
+ result = traverse_field(&cursor, &buffer, &content->field_user_id); // user_id
if (!result) return 0;
result = traverse_field(&cursor, &buffer, &content->field_to); // to
if (!result) return 0;
@@ -301,14 +301,14 @@ static dx_field_location_t *dx_message_f
int count = start_list(&cursor, &buffer);
int result;
- if (count < 3)
+ if (count < 5)
break;
result = traverse_field(&cursor, &buffer, 0); // message_id
if (!result) return 0;
- result = traverse_field(&cursor, &buffer, 0); // user_id
+ result = traverse_field(&cursor, &buffer, &content->field_user_id); // user_id
if (!result) return 0;
- result = traverse_field(&cursor, &buffer, 0); // to
+ result = traverse_field(&cursor, &buffer, &content->field_to); // to
if (!result) return 0;
result = traverse_field(&cursor, &buffer, 0); // subject
if (!result) return 0;
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/__init__.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/__init__.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/__init__.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/__init__.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,20 @@
+#
+# 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 router.router_engine import *
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/__init__.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/adapter.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/adapter.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/adapter.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/adapter.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,102 @@
+#
+# 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.
+#
+
+TRACE = 0
+DEBUG = 1
+INFO = 2
+NOTICE = 3
+WARNING = 4
+ERROR = 5
+CRITICAL = 6
+
+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.adapter.remote_unbind(a, b)
+ for a,b in to_add:
+ self.container.adapter.remote_bind(a, b)
+
+ self.container.log(INFO, "New Routing Table (class=%s):" % key_class)
+ for a,b in new_table:
+ self.container.log(INFO, " %s => %s" % (a, b))
+
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/adapter.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/binding.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/binding.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/binding.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/binding.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,138 @@
+#
+# 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.
+#
+
+TRACE = 0
+DEBUG = 1
+INFO = 2
+NOTICE = 3
+WARNING = 4
+ERROR = 5
+CRITICAL = 6
+
+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)
+
+
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/binding.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/configuration.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/configuration.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/configuration.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/configuration.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,47 @@
+#
+# 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.
+#
+
+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 }
+
+ ##
+ ## 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 __repr__(self):
+ return "%r" % self.values
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/configuration.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/data.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/data.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/data.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/data.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,269 @@
+#
+# 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.
+#
+
+
+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)
+
+
+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
+
+
+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
+
+
+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
+
+
+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}
+
+
+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()}
+
+
+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 get_opcode(self):
+ return 'LSR'
+
+ def __repr__(self):
+ return "LSR(id=%s area=%s)" % (self.id, 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
+
+
+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}
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/data.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/link.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/link.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/link.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/link.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,143 @@
+#
+# 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 data import MessageRA, MessageLSU, MessageLSR
+from time import time
+
+TRACE = 0
+DEBUG = 1
+INFO = 2
+NOTICE = 3
+WARNING = 4
+ERROR = 5
+CRITICAL = 6
+
+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(INFO, "New Link-State Collection:")
+ for a,b in self.collection.items():
+ self.container.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.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.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))
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/link.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/mobile.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/mobile.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/mobile.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/mobile.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,183 @@
+#
+# 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 data import MessageRA, MessageMAR, MessageMAU
+
+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):
+ """
+ """
+ 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
+ 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('_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 _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('_topo.%s.%s' % (_area, _id), MessageMAR(None, self.id, self.area, _seq))
+ self.needed_mars = {}
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/mobile.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/neighbor.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/neighbor.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/neighbor.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/neighbor.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,85 @@
+#
+# 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 data import LinkState, MessageHELLO
+from time import time
+
+TRACE = 0
+DEBUG = 1
+INFO = 2
+NOTICE = 3
+WARNING = 4
+ERROR = 5
+CRITICAL = 6
+
+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, [])
+
+
+ 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('_peer', 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)
+
+
+ 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.log(INFO, "New neighbor established: %s" % msg.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.log(INFO, "Neighbor lost: %s" % key)
+
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/neighbor.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/path.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/path.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/path.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/path.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,205 @@
+#
+# 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.
+#
+
+TRACE = 0
+DEBUG = 1
+INFO = 2
+NOTICE = 3
+WARNING = 4
+ERROR = 5
+CRITICAL = 6
+
+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)
+
+
+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:
+ ##
+ ## 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))
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/path.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/router_engine.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/router_engine.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/router_engine.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/router_engine.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,246 @@
+#
+# 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 time
+from uuid import uuid4
+
+from configuration import Configuration
+from data import *
+from neighbor import NeighborEngine
+from link import LinkStateEngine
+from path import PathEngine
+from mobile import MobileAddressEngine
+from routing import RoutingTableEngine
+from binding import BindingEngine
+from adapter import AdapterEngine
+
+TRACE = 0
+DEBUG = 1
+INFO = 2
+NOTICE = 3
+WARNING = 4
+ERROR = 5
+CRITICAL = 6
+
+class RouterEngine:
+ """
+ """
+
+ def __init__(self, adapter, domain, router_id=None, area='area', config_override={}):
+ """
+ Initialize an instance of a router for a domain.
+ """
+ ##
+ ## Record important information about this router instance
+ ##
+ self.adapter = adapter
+ self.domain = domain
+ if router_id:
+ self.id = router_id
+ else:
+ self.id = str(uuid4())
+ self.area = area
+ self.log(NOTICE, "Router Engine Instantiated: area=%s id=%s" % (self.area, self.id))
+
+ ##
+ ## Setup configuration
+ ##
+ self.config = Configuration(config_override)
+ self.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)
+
+ ##
+ ## Establish the local bindings so that this router instance can receive
+ ## traffic addressed to it
+ ##
+ self.adapter.local_bind('router')
+ self.adapter.local_bind('_topo/%s/%s' % (self.area, self.id))
+ self.adapter.local_bind('_topo/%s/all' % self.area)
+
+
+ ##========================================================================================
+ ## 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(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(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)
+ except Exception, e:
+ self.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(TRACE, "RCVD: %r" % msg)
+ self.neighbor_engine.handle_hello(msg, now)
+
+ elif opcode == 'RA':
+ msg = MessageRA(body)
+ self.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(TRACE, "RCVD: %r" % msg)
+ self.link_state_engine.handle_lsu(msg, now)
+
+ elif opcode == 'LSR':
+ msg = MessageLSR(body)
+ self.log(TRACE, "RCVD: %r" % msg)
+ self.link_state_engine.handle_lsr(msg, now)
+
+ elif opcode == 'MAU':
+ msg = MessageMAU(body)
+ self.log(TRACE, "RCVD: %r" % msg)
+ self.mobile_address_engine.handle_mau(msg, now)
+
+ elif opcode == 'MAR':
+ msg = MessageMAR(body)
+ self.log(TRACE, "RCVD: %r" % msg)
+ self.mobile_address_engine.handle_mar(msg, now)
+
+ except Exception, e:
+ self.log(ERROR, "Exception in message processing: opcode=%s body=%r exception=%r" % (opcode, 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 the adapter
+ ##========================================================================================
+ def log(self, level, text):
+ """
+ Emit a log message to the host's event log
+ """
+ self.adapter.log(level, text)
+
+
+ def send(self, dest, msg):
+ """
+ Send a control message to another router.
+ """
+ self.adapter.send(dest, msg.get_opcode(), msg.to_dict())
+ self.log(TRACE, "SENT: %r dest=%s" % (msg, dest))
+
+
+ ##========================================================================================
+ ## Interconnect between the Sub-Modules
+ ##========================================================================================
+ def local_link_state_changed(self, link_state):
+ self.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(DEBUG, "Event: ls_collection_changed: %r" % collection)
+ self.path_engine.ls_collection_changed(collection)
+
+ def next_hops_changed(self, next_hop_table):
+ self.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(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(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(DEBUG, "Event: remote_routes_changed: class=%s routes=%r" % (key_class, routes))
+ self.adapter_engine.remote_routes_changed(key_class, routes)
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/router_engine.py
------------------------------------------------------------------------------
svn:eol-style = native
Added: qpid/trunk/qpid/extras/dispatch/src/py/router/routing.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/src/py/router/routing.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/src/py/router/routing.py (added)
+++ qpid/trunk/qpid/extras/dispatch/src/py/router/routing.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,59 @@
+#
+# 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.
+#
+
+TRACE = 0
+DEBUG = 1
+INFO = 2
+NOTICE = 3
+WARNING = 4
+ERROR = 5
+CRITICAL = 6
+
+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
+
Propchange: qpid/trunk/qpid/extras/dispatch/src/py/router/routing.py
------------------------------------------------------------------------------
svn:eol-style = native
Modified: qpid/trunk/qpid/extras/dispatch/tests/CMakeLists.txt
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/tests/CMakeLists.txt?rev=1497770&r1=1497769&r2=1497770&view=diff
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/tests/CMakeLists.txt (original)
+++ qpid/trunk/qpid/extras/dispatch/tests/CMakeLists.txt Fri Jun 28 13:42:12 2013
@@ -49,3 +49,4 @@ add_test(unit_tests_size_3 unit_test
add_test(unit_tests_size_2 unit_tests_size 2)
add_test(unit_tests_size_1 unit_tests_size 1)
add_test(unit_tests unit_tests ${CMAKE_CURRENT_SOURCE_DIR}/threads4.conf)
+add_test(router_tests python ${CMAKE_CURRENT_SOURCE_DIR}/router_engine_test.py -v)
Added: qpid/trunk/qpid/extras/dispatch/tests/router_engine_test.py
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/extras/dispatch/tests/router_engine_test.py?rev=1497770&view=auto
==============================================================================
--- qpid/trunk/qpid/extras/dispatch/tests/router_engine_test.py (added)
+++ qpid/trunk/qpid/extras/dispatch/tests/router_engine_test.py Fri Jun 28 13:42:12 2013
@@ -0,0 +1,410 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import unittest
+from router.router_engine import NeighborEngine, PathEngine, Configuration
+from router.data import LinkState, MessageHELLO
+
+class Adapter(object):
+ 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 send(self, dest, opcode, body):
+ print "Adapter.send: domain=%s, dest=%s, opcode=%s, body=%s" % (self._domain, dest, opcode, body)
+
+ def local_bind(self, key):
+ print "Adapter.local_bind: key=%s" % key
+
+ 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 remote_rebind(self, subject, old_peer, new_peer):
+ print "Adapter.remote_rebind: subject=%s, old_peer=%s, new_peer=%s" % (subject, old_peer, new_peer)
+
+
+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 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 setUp(self):
+ self.sent = []
+ self.local_link_state = None
+ self.id = "R1"
+ self.area = "area"
+ self.config = Configuration()
+
+ 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, "_peer")
+ 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, [])
+
+
+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')
+
+
+if __name__ == '__main__':
+ unittest.main()
Propchange: qpid/trunk/qpid/extras/dispatch/tests/router_engine_test.py
------------------------------------------------------------------------------
svn:eol-style = native
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org