BIND 10 trac1427, updated. 7f573f432cfca90d2f9409829f14b3645083b9af [1427] Implement get_socket

BIND 10 source code commits bind10-changes at lists.isc.org
Sat Nov 26 17:08:41 UTC 2011


The branch, trac1427 has been updated
       via  7f573f432cfca90d2f9409829f14b3645083b9af (commit)
       via  b586771730eb1d22330e3a4f46c6c596d6ab57da (commit)
       via  137abb738558ae9602f834890f477a924b520001 (commit)
       via  14c51c664a98beb4867728d528190aff335e6f27 (commit)
      from  6a4afc2165e4e6e692e71cb6795201c9df5afee2 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 7f573f432cfca90d2f9409829f14b3645083b9af
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Sat Nov 26 18:08:25 2011 +0100

    [1427] Implement get_socket

commit b586771730eb1d22330e3a4f46c6c596d6ab57da
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Sat Nov 26 17:27:11 2011 +0100

    [1427] Finish get_token

commit 137abb738558ae9602f834890f477a924b520001
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Sat Nov 26 15:57:24 2011 +0100

    [1427] Getting cached tokens

commit 14c51c664a98beb4867728d528190aff335e6f27
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Sat Nov 26 15:32:22 2011 +0100

    [1427] Check compatibility of share mode

-----------------------------------------------------------------------

Summary of changes:
 src/lib/python/isc/bind10/socket_cache.py          |   85 +++++++++-
 .../python/isc/bind10/tests/socket_cache_test.py   |  176 ++++++++++++++++++++
 2 files changed, 256 insertions(+), 5 deletions(-)

-----------------------------------------------------------------------
diff --git a/src/lib/python/isc/bind10/socket_cache.py b/src/lib/python/isc/bind10/socket_cache.py
index dfe4074..689cefb 100644
--- a/src/lib/python/isc/bind10/socket_cache.py
+++ b/src/lib/python/isc/bind10/socket_cache.py
@@ -18,6 +18,8 @@ Here's the cache for sockets from socket creator.
 """
 
 import os
+import random
+import isc.bind10.sockcreator
 
 class SocketError(Exception):
     """
@@ -25,8 +27,12 @@ class SocketError(Exception):
     socket. Possible reasons might be the address it should be bound to
     is already taken, the permissions are insufficient, the address family
     is not supported on this computer and many more.
+
+    The errno, if not None, is passed from the socket creator.
     """
-    pass
+    def __init__(self, message, errno):
+        Exception.__init__(self, message)
+        self.errno = errno
 
 class ShareError(Exception):
     """
@@ -69,6 +75,31 @@ class Socket:
         """
         os.close(self.fileno)
 
+    def shareCompatible(self, mode, name):
+        """
+        Checks if the given share mode and name is compatible with the ones
+        already installed here.
+
+        The allowed values for mode are listed in the Cache.get_token
+        function.
+        """
+        if mode not in ['NO', 'SAMEAPP', 'ANY']:
+            raise ValueError("Mode " + mode + " is invalid")
+
+        # Go through the existing ones
+        for (emode, ename) in self.shares.values():
+            if emode == 'NO' or mode == 'NO':
+                # One of them can't live together with anything
+                return False
+            if (emode == 'SAMEAPP' or mode == 'SAMEAPP') and \
+                ename != name:
+                # One of them can't live together with someone of different
+                # name
+                return False
+            # else both are ANY or SAMEAPP with the same name, which is OK
+        # No problem found, so we consider it OK
+        return True
+
 class Cache:
     """
     This is the cache for sockets from socket creator. The purpose of cache
@@ -106,8 +137,8 @@ class Cache:
         # This is a dict from applications to set of tokens used by the
         # application, for the sockets already picked up by an application
         self._active_apps = {}
-        # The sockets live here to be indexed by address and subsequently
-        # by port
+        # The sockets live here to be indexed by protocol, address and
+        # subsequently by port
         self._sockets = {}
         # These are just the tokens actually in use, so we don't generate
         # dupes. If one is dropped, it can be potentially reclaimed.
@@ -148,7 +179,39 @@ class Cache:
         Note that it isn't guaranteed the tokens would be unique and they
         should be used as an opaque handle only.
         """
-        pass
+        addr_str = str(address)
+        try:
+            socket = self._sockets[protocol][addr_str][port]
+        except KeyError:
+            # Something in the dicts is not there, so socket is to be
+            # created
+            try:
+                fileno = self._creator.get_socket(address, port, protocol)
+            except isc.bind10.sockcreator.CreatorError as ce:
+                if ce.fatal:
+                    raise
+                else:
+                    raise SocketError(str(ce), ce.errno)
+            socket = Socket(protocol, address, port, fileno)
+            # And cache it
+            if protocol not in self._sockets:
+                self._sockets[protocol] = {}
+            if addr_str not in self._sockets[protocol]:
+                self._sockets[protocol][addr_str] = {}
+            self._sockets[protocol][addr_str][port] = socket
+        # Now we get the token, check it is compatible
+        if not socket.shareCompatible(share_mode, share_name):
+            raise ShareError("Cached socket not compatible with mode " +
+                             share_mode + " and name " + share_name)
+        # Grab yet unused token
+        token = 't' + str(random.randint(0, 2^32-1))
+        while token in self._live_tokens:
+            token = 't' + str(random.randint(0, 2^32-1))
+        self._waiting_tokens[token] = socket
+        self._live_tokens.add(token)
+        socket.shares[token] = (share_mode, share_name)
+        socket.waiting_tokens.add(token)
+        return token
 
     def get_socket(self, token, application):
         """
@@ -166,7 +229,19 @@ class Cache:
         get_token, it was already used, the socket wasn't picked up soon
         enough, ...), it raises ValueError.
         """
-        pass
+        try:
+            socket = self._waiting_tokens[token]
+        except KeyError:
+            raise ValueError("Token " + token +
+                             " isn't waiting to be picked up")
+        del self._waiting_tokens[token]
+        self._active_tokens[token] = socket
+        if application not in self._active_apps:
+            self._active_apps[application] = set()
+        self._active_apps[application].add(token)
+        socket.waiting_tokens.remove(token)
+        socket.active_tokens[token] = application
+        return socket.fileno
 
     def drop_socket(self, token):
         """
diff --git a/src/lib/python/isc/bind10/tests/socket_cache_test.py b/src/lib/python/isc/bind10/tests/socket_cache_test.py
index b0d1a49..93f99dd 100644
--- a/src/lib/python/isc/bind10/tests/socket_cache_test.py
+++ b/src/lib/python/isc/bind10/tests/socket_cache_test.py
@@ -16,6 +16,7 @@
 import unittest
 import isc.log
 import isc.bind10.socket_cache
+import isc.bind10.sockcreator
 from isc.net.addr import IPAddr
 import os
 
@@ -79,6 +80,51 @@ class SocketTest(Test):
         self.__socket = None
         self.assertEqual([42], self._closes)
 
+    def test_share_modes(self):
+        """
+        Test the share mode compatibility check function.
+        """
+        modes = ['NO', 'SAMEAPP', 'ANY']
+        # If there are no shares, it is compatible with everything.
+        for mode in modes:
+            self.assertTrue(self.__socket.shareCompatible(mode, 'anything'))
+
+        # There's an NO already, so it is incompatible with everything.
+        self.__socket.shares = {'token': ('NO', 'anything')}
+        for mode in modes:
+            self.assertFalse(self.__socket.shareCompatible(mode, 'anything'))
+
+        # If there's SAMEAPP, it is compatible with ANY and SAMEAPP with the
+        # same name.
+        self.__socket.shares = {'token': ('SAMEAPP', 'app')}
+        self.assertFalse(self.__socket.shareCompatible('NO', 'app'))
+        self.assertFalse(self.__socket.shareCompatible('SAMEAPP', 'something'))
+        self.assertTrue(self.__socket.shareCompatible('SAMEAPP', 'app'))
+        self.assertTrue(self.__socket.shareCompatible('ANY', 'app'))
+        self.assertFalse(self.__socket.shareCompatible('ANY', 'something'))
+
+        # If there's ANY, then ANY and SAMEAPP with the same name is compatible
+        self.__socket.shares = {'token': ('ANY', 'app')}
+        self.assertFalse(self.__socket.shareCompatible('NO', 'app'))
+        self.assertFalse(self.__socket.shareCompatible('SAMEAPP', 'something'))
+        self.assertTrue(self.__socket.shareCompatible('SAMEAPP', 'app'))
+        self.assertTrue(self.__socket.shareCompatible('ANY', 'something'))
+
+        # In case there are multiple already inside
+        self.__socket.shares = {
+            'token': ('ANY', 'app'),
+            'another': ('SAMEAPP', 'app')
+        }
+        self.assertFalse(self.__socket.shareCompatible('NO', 'app'))
+        self.assertFalse(self.__socket.shareCompatible('SAMEAPP', 'something'))
+        self.assertTrue(self.__socket.shareCompatible('SAMEAPP', 'app'))
+        self.assertFalse(self.__socket.shareCompatible('ANY', 'something'))
+        self.assertTrue(self.__socket.shareCompatible('ANY', 'app'))
+
+        # Invalid inputs are rejected
+        self.assertRaises(ValueError, self.__socket.shareCompatible, 'bad',
+                          'bad')
+
 class SocketCacheTest(Test):
     """
     Some tests for the isc.bind10.socket_cache.Cache.
@@ -89,9 +135,15 @@ class SocketCacheTest(Test):
     def setUp(self):
         """
         Creates the cache for tests with us being the socket creator.
+
+        Also creates some more variables for testing.
         """
         Test.setUp(self)
         self.__cache = isc.bind10.socket_cache.Cache(self)
+        self.__address = IPAddr("192.0.2.1")
+        self.__socket = isc.bind10.socket_cache.Socket('Test', self.__address,
+                                                       1024, 42)
+        self.__get_socket_called = False
 
     def test_init(self):
         """
@@ -104,6 +156,130 @@ class SocketCacheTest(Test):
         self.assertEqual({}, self.__cache._sockets)
         self.assertEqual(set(), self.__cache._live_tokens)
 
+    def get_socket(self, address, port, socktype):
+        """
+        Pretend to be a socket creator.
+
+        This expects to be called with the _address, port 1024 and 'UDP'.
+
+        Returns 42 and notes down it was called.
+        """
+        self.assertEqual(self.__address, address)
+        self.assertEqual(1024, port)
+        self.assertEqual('UDP', socktype)
+        self.__get_socket_called = True
+        return 42
+
+    def test_get_token_cached(self):
+        """
+        Check the behaviour of get_token when the requested socket is already
+        cached inside.
+        """
+        self.__cache._sockets = {
+            'UDP': {'192.0.2.1': {1024: self.__socket}}
+        }
+        token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY',
+                                       'test')
+        # It didn't call get_socket
+        self.assertFalse(self.__get_socket_called)
+        # It returned something
+        self.assertIsNotNone(token)
+        # The token is both in the waiting sockets and the live tokens
+        self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens)
+        self.assertEqual(set([token]), self.__cache._live_tokens)
+        # The token got the new share to block any relevant queries
+        self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares)
+        # The socket knows the token is waiting in it
+        self.assertEqual(set([token]), self.__socket.waiting_tokens)
+
+        # If we request one more, with incompatible share, it is rejected
+        self.assertRaises(isc.bind10.socket_cache.ShareError,
+                          self.__cache.get_token, 'UDP', self.__address, 1024,
+                          'NO', 'test')
+        # The internals are not changed, so the same checks
+        self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens)
+        self.assertEqual(set([token]), self.__cache._live_tokens)
+        self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares)
+        self.assertEqual(set([token]), self.__socket.waiting_tokens)
+
+    def test_get_token_uncached(self):
+        """
+        Check a new socket is created when a corresponding one is missing.
+        """
+        token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY',
+                                       'test')
+        # The get_socket was called
+        self.assertTrue(self.__get_socket_called)
+        # It returned something
+        self.assertIsNotNone(token)
+        # Get the socket and check it looks OK
+        socket = self.__cache._waiting_tokens[token]
+        self.assertEqual(self.__address, socket.address)
+        self.assertEqual(1024, socket.port)
+        self.assertEqual(42, socket.fileno)
+        self.assertEqual('UDP', socket.protocol)
+        # The socket is properly cached
+        self.assertEqual({
+            'UDP': {'192.0.2.1': {1024: socket}}
+        }, self.__cache._sockets)
+        # The token is both in the waiting sockets and the live tokens
+        self.assertEqual({token: socket}, self.__cache._waiting_tokens)
+        self.assertEqual(set([token]), self.__cache._live_tokens)
+        # The token got the new share to block any relevant queries
+        self.assertEqual({token: ('ANY', 'test')}, socket.shares)
+        # The socket knows the token is waiting in it
+        self.assertEqual(set([token]), socket.waiting_tokens)
+
+    def test_get_token_excs(self):
+        """
+        Test that it is handled properly if the socket creator raises
+        some exceptions.
+        """
+        def raiseCreatorError(fatal):
+            raise isc.bind10.sockcreator.CreatorError('test error', fatal)
+        # First, fatal socket creator errors are passed through
+        self.get_socket = lambda addr, port, proto: raiseCreatorError(True)
+        self.assertRaises(isc.bind10.sockcreator.CreatorError,
+                          self.__cache.get_token, 'UDP', self.__address, 1024,
+                          'NO', 'test')
+        # And nonfatal are converted to SocketError
+        self.get_socket = lambda addr, port, proto: raiseCreatorError(False)
+        self.assertRaises(isc.bind10.socket_cache.SocketError,
+                          self.__cache.get_token, 'UDP', self.__address, 1024,
+                          'NO', 'test')
+
+    def test_get_socket(self):
+        """
+        Test that we can pickup a socket if we know a token.
+        """
+        token = "token"
+        app = 13
+        # No socket prepared there
+        self.assertRaises(ValueError, self.__cache.get_socket, token, app)
+        # Not changed
+        self.assertEqual({}, self.__cache._active_tokens)
+        self.assertEqual({}, self.__cache._active_apps)
+        self.assertEqual({}, self.__cache._sockets)
+        self.assertEqual(set(), self.__cache._live_tokens)
+        # Prepare a token there
+        self.__socket.waiting_tokens = set([token])
+        self.__socket.shares = {token: ('ANY', 'app')}
+        self.__cache._waiting_tokens = {token: self.__socket}
+        self.__cache._sockets = {'UDP': {'192.0.2.1': {1024: self.__socket}}}
+        self.__cache._live_tokens = set([token])
+        socket = self.__cache.get_socket(token, app)
+        # Received the fileno
+        self.assertEqual(42, socket)
+        # It moved from waiting to active ones
+        self.assertEqual({}, self.__cache._waiting_tokens)
+        self.assertEqual({token: self.__socket}, self.__cache._active_tokens)
+        self.assertEqual({13: set([token])}, self.__cache._active_apps)
+        self.assertEqual(set([token]), self.__cache._live_tokens)
+        self.assertEqual(set(), self.__socket.waiting_tokens)
+        self.assertEqual({token: 13}, self.__socket.active_tokens)
+        # Trying to get it again fails
+        self.assertRaises(ValueError, self.__cache.get_socket, token, app)
+
 if __name__ == '__main__':
     isc.log.init("bind10")
     isc.log.resetUnitTestRootLogger()




More information about the bind10-changes mailing list