wingo - in flumotion/trunk: . flumotion/common flumotion/test
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Tue Feb 13 19:06:26 CET 2007
Author: wingo
Date: Tue Feb 13 19:06:22 2007
New Revision: 4483
Added:
flumotion/trunk/flumotion/common/avltree.py
Modified:
flumotion/trunk/ChangeLog
flumotion/trunk/flumotion/common/Makefile.am
flumotion/trunk/flumotion/common/netutils.py
flumotion/trunk/flumotion/test/test_common_netutils.py
Log:
2007-02-13 Andy Wingo <wingo at pobox.com>
* flumotion/test/test_common_netutils.py
(TestRoutingTable.testRoute): Update tests.
* flumotion/common/netutils.py (RoutingTable): Change "Network" to
RoutingTable, and make it use an AVL tree.
* flumotion/common/avltree.py: New file, an AVL tree
implementation.
Modified: flumotion/trunk/ChangeLog
==============================================================================
--- flumotion/trunk/ChangeLog (original)
+++ flumotion/trunk/ChangeLog Tue Feb 13 19:06:22 2007
@@ -1,3 +1,14 @@
+2007-02-13 Andy Wingo <wingo at pobox.com>
+
+ * flumotion/test/test_common_netutils.py
+ (TestRoutingTable.testRoute): Update tests.
+
+ * flumotion/common/netutils.py (RoutingTable): Change "Network" to
+ RoutingTable, and make it use an AVL tree.
+
+ * flumotion/common/avltree.py: New file, an AVL tree
+ implementation.
+
2007-02-13 Michael Smith <msmith at fluendo.com>
* flumotion/component/bouncers/icalbouncer.py:
Modified: flumotion/trunk/flumotion/common/Makefile.am
==============================================================================
--- flumotion/trunk/flumotion/common/Makefile.am (original)
+++ flumotion/trunk/flumotion/common/Makefile.am Tue Feb 13 19:06:22 2007
@@ -4,6 +4,7 @@
flumotion_PYTHON = \
__init__.py \
+ avltree.py \
boot.py \
bundle.py \
bundleclient.py \
Added: flumotion/trunk/flumotion/common/avltree.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/common/avltree.py Tue Feb 13 19:06:22 2007
@@ -0,0 +1,244 @@
+# -*- Mode: Python; test-case-name: flumotion.test.test_common_messages -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+"""
+A pure python functional-style self-balancing binary search tree
+implementation, with an object-oriented wrapper. Useful for maintaining
+sorted sets, traversing sets in order, and closest-match lookups.
+"""
+
+
+def node(l, v, r, b):
+ """Make an AVL tree node, consisting of a left tree, a value, a
+ right tree, and the "balance factor": the difference in lengths
+ between the right and left sides, respectively."""
+ return (l, v, r, b)
+
+def height(tree):
+ """Return the height of an AVL tree. Relies on the balance factors
+ being consistent."""
+ if tree is None:
+ return 0
+ else:
+ l, v, r, b = tree
+ if b <= 0:
+ return height(l) + 1
+ else:
+ return height(l) + 1
+
+def debug(tree, level=0):
+ """Print out a debugging representation of an AVL tree."""
+ if tree is None:
+ return
+ l, v, r, b = tree
+ debug(l, level+1)
+ bchr = {-2:'--',-1:'-',0:'0',1:'+',2:'++'}.get(b,'?')
+ print '%s%s: %r' % (' '*level, bchr, v)
+ debug(r, level+1)
+
+def fromseq(seq):
+ """Populate and return an AVL tree from an iterable sequence."""
+ t = None
+ for x in seq:
+ _, t = insert(t, x)
+ return t
+
+def _balance(hdiff, l, v, r, b):
+ """Internal method to rebalance an AVL tree, called as needed."""
+ # if we have to rebalance, in the end the node has balance 0;
+ # for details see GNU libavl docs
+ if b < -1:
+ # rotate right
+ ll, lv, lr, lb = l
+ if lb <= 0:
+ # easy case, lv is new root; 0 can happen in deletion
+ new = node(ll, lv, node(lr, v, r, 0), 0)
+ if hdiff < 0:
+ # deletion; maybe we decreased in height
+ old = node(l, v, r, b)
+ hdiff = height(new) - height(old)
+ else:
+ # we know that for insertion we don't increase in height
+ hdiff = 0
+ return hdiff, new
+ else: # lb == +1
+ # lrv will be the new root
+ lrl, lrv, lrr, lrb = lr
+ if lrb == 0: # lr is the new node
+ newleftb = newrightb = 0
+ elif lrb == -1:
+ newleftb = 0
+ newrightb = +1
+ else: # lrb == +1
+ newleftb = -1
+ newrightb = 0
+ return new, node(node(ll, lv, lrl, newleftb),
+ lrv,
+ node(lrr, v, r, newrightb),
+ 0)
+ elif b > 1:
+ # rotate left
+ rl, rv, rr, rb = r
+ if rb >= 0:
+ # easy case, rv is new root; 0 can happen in deletion
+ new = node(node(l, v, rl, 0), rv, rr, 0)
+ if hdiff < 0:
+ # deletion; maybe we decreased in height
+ old = node(l, v, r, b)
+ hdiff = height(new) - height(old)
+ else:
+ # we know that for insertion we don't increase in height
+ hdiff = 0
+ return hdiff, new
+ else: # rb == -1
+ # rlv will be the new root
+ rll, rlv, rlr, rlb = rl
+ if rlb == 0: # rl is the new node
+ newleftb = newrightb = 0
+ elif rlb == +1:
+ newleftb = -1
+ newrightb = 0
+ else: # rlb == -1
+ newleftb = 0
+ newrightb = +1
+ return 0, node(node(l, v, rll, newleftb),
+ rlv,
+ node(rlr, rv, rr, newrightb),
+ 0)
+ else:
+ return hdiff, node(l, v, r, b)
+
+def insert(tree, value):
+ """Insert a value into an AVL tree. Returns a tuple of
+ (heightdifference, tree). The original tree is unmodified."""
+ if tree is None:
+ return 1, (None, value, None, 0)
+ else:
+ l, v, r, b = tree
+ if value < v:
+ hdiff, newl = insert(l, value)
+ return _balance(hdiff, newl, v, r, b - hdiff)
+ elif value > v:
+ hdiff, newr = insert(r, value)
+ return _balance(hdiff, l, v, newr, b + hdiff)
+ else:
+ raise ValueError('tree already has value %r' % value)
+
+def delete(tree, value):
+ """Delete a value from an AVL tree. Like L{insert}, returns a tuple
+ of (heightdifference, tree). The original tree is unmodified."""
+ def popmin((l, v, r, b)):
+ if l is None:
+ minv = v
+ return minv, -1, r
+ else:
+ minv, hdiff, newl = popmin(l)
+ if hdiff != 0:
+ if b >= 0:
+ # overall height only changes if left was taller before
+ hdiff = 0
+ b += 1
+
+ return (minv,) + _balance(hdiff, newl, v, r, b)
+
+ if tree is None:
+ raise ValueError('tree has no value %r' % value)
+ else:
+ l, v, r, b = tree
+ if value < v:
+ hdiff, newl = delete(l, value)
+ if hdiff != 0:
+ if b >= 0:
+ # overall height only changes if left was
+ # taller before
+ hdiff = 0
+ b += 1
+ return _balance(hdiff, newl, v, r, b)
+ elif value > v:
+ hdiff, newr = delete(r, value)
+ if hdiff != 0:
+ if b <= 0:
+ # overall height only changes if right was
+ # taller before
+ hdiff = 0
+ b -= 1
+ return _balance(hdiff, l, v, newr, b)
+ else:
+ # we have found the node!
+ if r is None:
+ # no right link, just replace with left
+ return -1, l
+ else:
+ newv, hdiff, newr = popmin(r)
+ if hdiff != 0:
+ if b <= 0:
+ # overall height only changes if right was
+ # taller before
+ hdiff = 0
+ b -= 1
+ return _balance(hdiff, l, newv, newr, b)
+
+def lookup(tree, value):
+ """Look up a node in an AVL tree. Returns a node tuple or False if
+ the value was not found."""
+ if tree is None:
+ return False
+ else:
+ l, v, r, b = tree
+ if value < v:
+ return lookup(l, v)
+ elif value > v:
+ return lookup(r, v)
+ else:
+ return tree
+
+def iterate(tree):
+ """Iterate over an AVL tree, starting with the lowest-ordered
+ value."""
+ if tree is not None:
+ l, v, r, b = tree
+ for x in iterate(l):
+ yield x
+ yield v
+ for x in iterate(r):
+ yield x
+
+class AVLTree(object):
+ def __init__(self, seq=()):
+ self._len = len(seq)
+ self.tree = fromseq(seq)
+
+ def insert(self, value):
+ _, self.tree = insert(self.tree, value)
+ self._len += 1
+
+ def delete(self, value):
+ _, self.tree = delete(self.tree, value)
+ self._len -= 1
+
+ def __contains__(self, value):
+ return bool(lookup(self.tree, value))
+
+ def __len__(self):
+ return self._len
+
+ def __iter__(self):
+ return iterate(self.tree)
Modified: flumotion/trunk/flumotion/common/netutils.py
==============================================================================
--- flumotion/trunk/flumotion/common/netutils.py (original)
+++ flumotion/trunk/flumotion/common/netutils.py Tue Feb 13 19:06:22 2007
@@ -27,6 +27,7 @@
import fcntl
import struct
import array
+import avltree
# Thanks to Paul Cannon, see
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/439093
@@ -103,25 +104,44 @@
l.reverse()
return '.'.join(map(str, l))
-class Network(set):
- def __init__(self, name=None):
- self.name = name
+def countTrailingZeroes32(n):
+ tz = 0
+ if n == 0:
+ # max of 32 bits
+ tz = 32
+ else:
+ while not (n & (1<<tz)):
+ tz += 1
+ return tz
+
+class RoutingTable(object):
+ def __init__(self):
+ self.avltree = avltree.AVLTree()
def _parseSubnet(self, ipv4String, maskBits):
return (ipv4StringToInt(ipv4String),
~((1 << (32 - maskBits)) - 1))
- def addSubnet(self, ipv4String, maskBits=32):
- self.add(self._parseSubnet(ipv4String, maskBits))
+ def addSubnet(self, route, ipv4String, maskBits=32):
+ ipv4Int, mask = self._parseSubnet(ipv4String, maskBits)
+ self.avltree.insert((mask, ipv4Int, route))
- def removeSubnet(self, ipv4String, maskBits=32):
- self.remove(self._parseSubnet(ipv4String, maskBits))
+ def removeSubnet(self, route, ipv4String, maskBits=32):
+ ipv4Int, mask = self._parseSubnet(ipv4String, maskBits)
+ self.avltree.delete((mask, ipv4Int, route))
- def match(self, ipv4String):
- ip = ipv4StringToInt(ipv4String)
+ def __iter__(self):
+ return iter(self.avltree)
- for net, netmask in self:
+ def __len__(self):
+ return len(self.avltree)
+
+ def route(self, ip):
+ if isinstance(ip, str):
+ ip = ipv4StringToInt(ip)
+
+ for netmask, net, route in self:
if ip & netmask == net:
- return True
+ return route
- return False
+ return None
Modified: flumotion/trunk/flumotion/test/test_common_netutils.py
==============================================================================
--- flumotion/trunk/flumotion/test/test_common_netutils.py (original)
+++ flumotion/trunk/flumotion/test/test_common_netutils.py Tue Feb 13 19:06:22 2007
@@ -24,7 +24,7 @@
from twisted.trial import unittest
from flumotion.common.netutils import ipv4StringToInt, ipv4IntToString
-from flumotion.common.netutils import Network
+from flumotion.common.netutils import RoutingTable
class TestIpv4Parse(unittest.TestCase):
@@ -48,46 +48,44 @@
self.assertEquals(ipv4StringToInt('0.1.0.0'), 1<<16)
self.assertEquals(ipv4StringToInt('1.0.0.0'), 1<<24)
-class TestNetwork(unittest.TestCase):
- def testName(self):
- self.assertEquals(Network().name, None)
- self.assertEquals(Network('foo').name, 'foo')
-
+class TestRoutingTable(unittest.TestCase):
def testAddRemove(self):
- net = Network()
- net.addSubnet('192.168.0.0', 24)
- net.addSubnet('192.168.1.0', 24)
+ net = RoutingTable()
+ net.addSubnet('foo', '192.168.0.0', 24)
+ net.addSubnet('foo', '192.168.1.0', 24)
self.assertEquals(len(net), 2)
- net.removeSubnet('192.168.0.0', 24)
- net.removeSubnet('192.168.1.0', 24)
+ net.removeSubnet('foo', '192.168.0.0', 24)
+ net.removeSubnet('foo', '192.168.1.0', 24)
self.assertEquals(len(net), 0)
- def testMatch(self):
- net = Network()
+ def testRoute(self):
+ net = RoutingTable()
- self.failIf(net.match('192.168.1.0'))
-
- net.addSubnet('192.168.1.0', 24)
+ def ar(ip, route):
+ self.assertEquals(net.route(ip), route)
- self.failUnless(net.match('192.168.1.0'))
- self.failUnless(net.match('192.168.1.10'))
- self.failUnless(net.match('192.168.1.255'))
+ ar('192.168.1.0', None)
- self.failIf(net.match('192.168.0.255'))
- self.failIf(net.match('192.168.2.0'))
+ net.addSubnet('foo', '192.168.1.0', 24)
- net.addSubnet('192.168.2.0', 24)
+ ar('192.168.1.0', 'foo')
+ ar('192.168.1.10', 'foo')
+ ar('192.168.1.255', 'foo')
- self.failIf(net.match('192.168.0.255'))
- self.failUnless(net.match('192.168.1.255'))
- self.failUnless(net.match('192.168.2.0'))
+ ar('192.168.0.255', None)
+ ar('192.168.2.0', None)
- net.removeSubnet('192.168.1.0', 24)
- net.removeSubnet('192.168.2.0', 24)
+ net.addSubnet('foo', '192.168.2.0', 24)
- self.failIf(net.match('192.168.1.0'))
- self.failIf(net.match('192.168.1.10'))
- self.failIf(net.match('192.168.1.255'))
-
- self.failIf(net.match('192.168.0.255'))
- self.failIf(net.match('192.168.2.0'))
+ ar('192.168.0.255', None)
+ ar('192.168.1.255', 'foo')
+ ar('192.168.2.0', 'foo')
+
+ net.removeSubnet('foo', '192.168.1.0', 24)
+ net.removeSubnet('foo', '192.168.2.0', 24)
+
+ ar('192.168.1.0', None)
+ ar('192.168.1.10', None)
+ ar('192.168.1.255', None)
+ ar('192.168.0.255', None)
+ ar('192.168.2.0', None)
More information about the flumotion-commit
mailing list