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