Esempio n. 1
0
 def test_open_socket_default(self):
     env_var = None
     orig_socket_file = None
     if "BIND10_MSGQ_SOCKET_FILE" in os.environ:
         env_var = os.environ["BIND10_MSGQ_SOCKET_FILE"]
         del os.environ["BIND10_MSGQ_SOCKET_FILE"]
     # temporarily replace the class "default" not to be disrupted by
     # any running BIND 10 instance.
     if "BIND10_TEST_SOCKET_FILE" in os.environ:
         MsgQ.SOCKET_FILE = os.environ["BIND10_TEST_SOCKET_FILE"]
     socket_file = MsgQ.SOCKET_FILE
     self.assertFalse(os.path.exists(socket_file))
     msgq = MsgQ();
     try:
         msgq.setup()
         self.assertTrue(os.path.exists(socket_file))
         msgq.shutdown();
         self.assertFalse(os.path.exists(socket_file))
     except socket.error:
         # ok, the install path doesn't exist at all,
         # so we can't check any further
         pass
     if env_var is not None:
         os.environ["BIND10_MSGQ_SOCKET_FILE"] = env_var
     if orig_socket_file is not None:
         MsgQ.SOCKET_FILE = orig_socket_file
Esempio n. 2
0
 def test_open_socket_parameter(self):
     self.assertFalse(os.path.exists("./my_socket_file"))
     msgq = MsgQ("./my_socket_file");
     msgq.setup()
     self.assertTrue(os.path.exists("./my_socket_file"))
     msgq.shutdown();
     self.assertFalse(os.path.exists("./my_socket_file"))
Esempio n. 3
0
 def test_open_socket_environment_variable(self):
     self.assertFalse(os.path.exists("my_socket_file"))
     os.environ["BIND10_MSGQ_SOCKET_FILE"] = "./my_socket_file"
     msgq = MsgQ();
     msgq.setup()
     self.assertTrue(os.path.exists("./my_socket_file"))
     msgq.shutdown();
     self.assertFalse(os.path.exists("./my_socket_file"))
Esempio n. 4
0
 def setUp(self):
     self.__msgq = MsgQ()
     self.__abort_wait = False
     self.__result = None
     self.__notify_thread = threading.Thread(target=self.__notify)
     self.__wait_thread = threading.Thread(target=self.__wait)
     # Make sure the threads are killed if left behind by the test.
     self.__notify_thread.daemon = True
     self.__wait_thread.daemon = True
Esempio n. 5
0
 def setUp(self):
     self.__msgq = MsgQ()
     self.__msgq.kill_socket = self.mock_kill_socket
     self.__sock = self.MockSocket()
     self.__data = b'dummy'
     self.__msgq.sockets[42] = self.__sock
     self.__msgq.sendbuffs[42] = (None, b'testdata')
     self.__sock_error = socket.error()
     self.__killed_socket = None
     self.__logger = self.LoggerWrapper(msgq.logger)
     msgq.logger = self.__logger
     self.__orig_select = msgq.select.select
Esempio n. 6
0
 def test_open_socket_parameter(self):
     self.assertFalse(os.path.exists("./my_socket_file"))
     msgq = MsgQ("./my_socket_file")
     msgq.setup()
     self.assertTrue(os.path.exists("./my_socket_file"))
     msgq.shutdown()
     self.assertFalse(os.path.exists("./my_socket_file"))
Esempio n. 7
0
 def test_open_socket_default(self):
     env_var = None
     orig_socket_file = None
     if "BUNDY_MSGQ_SOCKET_FILE" in os.environ:
         env_var = os.environ["BUNDY_MSGQ_SOCKET_FILE"]
         del os.environ["BUNDY_MSGQ_SOCKET_FILE"]
     # temporarily replace the class "default" not to be disrupted by
     # any running BUNDY instance.
     if "BUNDY_TEST_SOCKET_FILE" in os.environ:
         MsgQ.SOCKET_FILE = os.environ["BUNDY_TEST_SOCKET_FILE"]
     socket_file = MsgQ.SOCKET_FILE
     self.assertFalse(os.path.exists(socket_file))
     msgq = MsgQ()
     try:
         msgq.setup()
         self.assertTrue(os.path.exists(socket_file))
         msgq.shutdown()
         self.assertFalse(os.path.exists(socket_file))
     except socket.error:
         # ok, the install path doesn't exist at all,
         # so we can't check any further
         pass
     if env_var is not None:
         os.environ["BUNDY_MSGQ_SOCKET_FILE"] = env_var
     if orig_socket_file is not None:
         MsgQ.SOCKET_FILE = orig_socket_file
Esempio n. 8
0
    def send_many(self, data):
        """
        Tries that sending a command many times and getting an answer works.
        """
        msgq = MsgQ()
        # msgq.run needs to compare with the listen_socket, so we provide
        # a replacement
        msgq.listen_socket = DummySocket
        (queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)

        def run():
            length = len(data)
            queue_pid = os.fork()
            if queue_pid == 0:
                signal.alarm(120)
                msgq.setup_signalsock()
                msgq.register_socket(queue)
                msgq.run()
                msgq.cleanup_signalsock()
            else:
                try:

                    def killall(signum, frame):
                        os.kill(queue_pid, signal.SIGTERM)
                        os._exit(1)

                    signal.signal(signal.SIGALRM, killall)
                    msg = msgq.preparemsg({"type": "ping"}, data)
                    now = time.clock()
                    while time.clock() - now < 0.2:
                        out.sendall(msg)
                        # Check the answer
                        (routing,
                         received) = msgq.read_packet(out.fileno(), out)
                        self.assertEqual({"type": "pong"},
                                         bundy.cc.message.from_wire(routing))
                        self.assertEqual(data, received)
                finally:
                    os.kill(queue_pid, signal.SIGTERM)

        self.terminate_check(run)

        # Explicitly close temporary socket pair as the Python
        # interpreter expects it.  It may not be 100% exception safe,
        # but since this is only for tests we prefer brevity.
        queue.close()
        out.close()
Esempio n. 9
0
 def test_open_socket_environment_variable(self):
     self.assertFalse(os.path.exists("my_socket_file"))
     os.environ["BUNDY_MSGQ_SOCKET_FILE"] = "./my_socket_file"
     msgq = MsgQ()
     msgq.setup()
     self.assertTrue(os.path.exists("./my_socket_file"))
     msgq.shutdown()
     self.assertFalse(os.path.exists("./my_socket_file"))
Esempio n. 10
0
    def send_many(self, data):
        """
        Tries that sending a command many times and getting an answer works.
        """
        msgq = MsgQ()
        # msgq.run needs to compare with the listen_socket, so we provide
        # a replacement
        class DummySocket:
            def fileno():
                return -1
        msgq.listen_socket = DummySocket
        (queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
        def run():
            length = len(data)
            queue_pid = os.fork()
            if queue_pid == 0:
                signal.alarm(120)
                msgq.setup_poller()
                msgq.register_socket(queue)
                msgq.run()
            else:
                try:
                    def killall(signum, frame):
                        os.kill(queue_pid, signal.SIGTERM)
                        os._exit(1)
                    signal.signal(signal.SIGALRM, killall)
                    msg = msgq.preparemsg({"type" : "ping"}, data)
                    now = time.clock()
                    while time.clock() - now < 0.2:
                        out.sendall(msg)
                        # Check the answer
                        (routing, received) = msgq.read_packet(out.fileno(),
                            out)
                        self.assertEqual({"type" : "pong"},
                            isc.cc.message.from_wire(routing))
                        self.assertEqual(data, received)
                finally:
                    os.kill(queue_pid, signal.SIGTERM)
        self.terminate_check(run)

        # Explicitly close temporary socket pair as the Python
        # interpreter expects it.  It may not be 100% exception safe,
        # but since this is only for tests we prefer brevity.
        queue.close()
        out.close()
Esempio n. 11
0
 def setUp(self):
     self.__msgq = MsgQ()
     self.__abort_wait = False
     self.__result = None
     self.__notify_thread = threading.Thread(target=self.__notify)
     self.__wait_thread = threading.Thread(target=self.__wait)
     # Make sure the threads are killed if left behind by the test.
     self.__notify_thread.daemon = True
     self.__wait_thread.daemon = True
Esempio n. 12
0
 def setUp(self):
     self.__msgq = MsgQ()
     self.__msgq.kill_socket = self.mock_kill_socket
     self.__sock = self.MockSocket()
     self.__data = b'dummy'
     self.__msgq.sockets[42] = self.__sock
     self.__msgq.sendbuffs[42] = (None, b'testdata')
     self.__sock_error = socket.error()
     self.__killed_socket = None
     self.__logger = self.LoggerWrapper(msgq.logger)
     msgq.logger = self.__logger
Esempio n. 13
0
class TestOpenOrderFilter(unittest.TestCase):
  
  def setUp(self):
    self.orderQ = MsgQ()
    self.receiptQ = MsgQ()
    self.broker = Broker(None, self.orderQ, self.receiptQ)

  def test_filter(self):
    # 3
    oo1 = OpenOrder('s&p500', 'buy', 1)
    oo2 = OpenOrder('s&p500', 'sell', 2)
    oo3 = OpenOrder('s&p500', 'buy', 3)
    # 2
    co1 = CloseOrder()
    co2 = CloseOrder()
    
    orders = [oo1, oo2, oo3, co1, co2]
    for o in orders:
      self.orderQ.put(o)
      
    count = len(self.orderQ.extract_matching(lambda x: (isinstance(x, OpenOrder) == True)))
    self.assertEqual(count, 3)      
Esempio n. 14
0
    def infinite_sender(self, sender):
        """
        Sends data until an exception happens. socket.error is caught,
        as it means the socket got closed. Sender is called to actually
        send the data.
        """
        msgq = MsgQ()
        # We do only partial setup, so we don't create the listening socket
        msgq.setup_poller()
        (read, write) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
        msgq.register_socket(write)
        # Keep sending while it is not closed by the msgq
        try:
            while True:
                sender(msgq, write)
        except socket.error:
            pass

        # Explicitly close temporary socket pair as the Python
        # interpreter expects it.  It may not be 100% exception safe,
        # but since this is only for tests we prefer brevity.
        read.close()
        write.close()
Esempio n. 15
0
    def get_msgq_with_sockets(self):
        '''
        Create a message queue and prepare it for use with a socket pair.
        The write end is put into the message queue, so we can check it.
        It returns (msgq, read_end, write_end). It is expected the sockets
        are closed by the caller afterwards.

        Also check the sockets are registered correctly (eg. internal data
        structures are there for them).
        '''
        msgq = MsgQ()
        # We do only partial setup, so we don't create the listening socket
        (read, write) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
        msgq.register_socket(write)
        self.assertEqual(1, len(msgq.lnames))
        self.assertEqual(write, msgq.lnames[msgq.fd_to_lname[write.fileno()]])
        return (msgq, read, write)
Esempio n. 16
0
class SocketTests(unittest.TestCase):
    '''Test cases for micro behaviors related to socket operations.

    Some cases are covered as part of other tests, but in this fixture
    we check more details of specific method related to socket operation,
    with the help of mock classes to avoid expensive overhead.

    '''
    class MockSocket():
        '''A mock socket used instead of standard socket objects.'''
        def __init__(self):
            self.ex_on_send = None # raised from send() if not None
            self.recv_result = b'test' # dummy data or exception
            self.blockings = [] # history of setblocking() params
        def setblocking(self, on):
            self.blockings.append(on)
        def send(self, data):
            if self.ex_on_send is not None:
                raise self.ex_on_send
            return 10           # arbitrary choice
        def recv(self, len):
            if isinstance(self.recv_result, Exception):
                raise self.recv_result
            ret = self.recv_result
            self.recv_result = b'' # if called again, return empty data
            return ret
        def fileno(self):
            return 42           # arbitrary choice

    class LoggerWrapper():
        '''A simple wrapper of logger to inspect log messages.'''
        def __init__(self, logger):
            self.error_called = 0
            self.warn_called = 0
            self.debug_called = 0
            self.orig_logger = logger
        def error(self, *args):
            self.error_called += 1
            self.orig_logger.error(*args)
        def warn(self, *args):
            self.warn_called += 1
            self.orig_logger.warn(*args)
        def debug(self, *args):
            self.debug_called += 1
            self.orig_logger.debug(*args)

    def mock_kill_socket(self, fileno, sock):
        '''A replacement of MsgQ.kill_socket method for inspection.'''
        self.__killed_socket = (fileno, sock)
        if fileno in self.__msgq.sockets:
            del self.__msgq.sockets[fileno]

    def setUp(self):
        self.__msgq = MsgQ()
        self.__msgq.kill_socket = self.mock_kill_socket
        self.__sock = self.MockSocket()
        self.__data = b'dummy'
        self.__msgq.sockets[42] = self.__sock
        self.__msgq.sendbuffs[42] = (None, b'testdata')
        self.__sock_error = socket.error()
        self.__killed_socket = None
        self.__logger = self.LoggerWrapper(msgq.logger)
        msgq.logger = self.__logger
        self.__orig_select = msgq.select.select

    def tearDown(self):
        msgq.logger = self.__logger.orig_logger
        msgq.select.select = self.__orig_select

    def test_send_data(self):
        # Successful case: _send_data() returns the hardcoded value, and
        # setblocking() is called twice with the expected parameters
        self.assertEqual(10, self.__msgq._send_data(self.__sock, self.__data))
        self.assertEqual([0, 1], self.__sock.blockings)
        self.assertIsNone(self.__killed_socket)

    def test_send_data_interrupt(self):
        '''send() is interrupted. send_data() returns 0, sock isn't killed.'''
        expected_blockings = []
        for eno in [errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR]:
            self.__sock_error.errno = eno
            self.__sock.ex_on_send = self.__sock_error
            self.assertEqual(0, self.__msgq._send_data(self.__sock,
                                                       self.__data))
            expected_blockings.extend([0, 1])
            self.assertEqual(expected_blockings, self.__sock.blockings)
            self.assertIsNone(self.__killed_socket)

    def test_send_data_error(self):
        '''Unexpected error happens on send().  The socket is killed.

        If the error is EPIPE, it's logged at the warn level; otherwise
        an error message is logged.

        '''
        expected_blockings = []
        expected_errors = 0
        expected_warns = 0
        for eno in [errno.EPIPE, errno.ECONNRESET, errno.ENOBUFS]:
            self.__sock_error.errno = eno
            self.__sock.ex_on_send = self.__sock_error
            self.__killed_socket = None # clear any previuos value
            self.assertEqual(None, self.__msgq._send_data(self.__sock,
                                                          self.__data))
            self.assertEqual((42, self.__sock), self.__killed_socket)
            expected_blockings.extend([0, 1])
            self.assertEqual(expected_blockings, self.__sock.blockings)

            if eno == errno.EPIPE:
                expected_warns += 1
            else:
                expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_warns, self.__logger.warn_called)

    def test_process_packet(self):
        '''Check some failure cases in handling an incoming message.'''
        expected_errors = 0
        expected_debugs = 0

        # if socket.recv() fails due to socket.error, it will be logged
        # as error and the socket will be killed regardless of errno.
        for eno in [errno.ENOBUFS, errno.ECONNRESET]:
            self.__sock_error.errno = eno
            self.__sock.recv_result = self.__sock_error
            self.__killed_socket = None # clear any previuos value
            self.__msgq.process_packet(42, self.__sock)
            self.assertEqual((42, self.__sock), self.__killed_socket)
            expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_debugs, self.__logger.debug_called)

        # if socket.recv() returns empty data, the result depends on whether
        # there's any preceding data; in the second case below, at least
        # 6 bytes of data will be expected, and the second call to our faked
        # recv() returns empty data.  In that case it will be logged as error.
        for recv_data in [b'', b'short']:
            self.__sock.recv_result = recv_data
            self.__killed_socket = None
            self.__msgq.process_packet(42, self.__sock)
            self.assertEqual((42, self.__sock), self.__killed_socket)
            if len(recv_data) == 0:
                expected_debugs += 1
            else:
                expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_debugs, self.__logger.debug_called)

    def test_do_select(self):
        """
        Check the behaviour of the run_select method.

        In particular, check that we skip writing to the sockets we read,
        because a read may have side effects (like closing the socket) and
        we want to prevent strange behavior.
        """
        self.__read_called = []
        self.__write_called = []
        self.__reads = None
        self.__writes = None
        def do_read(fd, socket):
            self.__read_called.append(fd)
            self.__msgq.running = False
        def do_write(fd):
            self.__write_called.append(fd)
            self.__msgq.running = False
        self.__msgq.process_packet = do_read
        self.__msgq._process_write = do_write
        self.__msgq.fd_to_lname = {42: 'lname', 44: 'other', 45: 'unused'}
        # The do_select does index it, but just passes the value. So reuse
        # the dict to safe typing in the test.
        self.__msgq.sockets = self.__msgq.fd_to_lname
        self.__msgq.sendbuffs = {42: 'data', 43: 'data'}
        def my_select(reads, writes, errors):
            self.__reads = reads
            self.__writes = writes
            self.assertEqual([], errors)
            return ([42, 44], [42, 43], [])
        msgq.select.select = my_select
        self.__msgq.listen_socket = DummySocket

        self.__msgq.running = True
        self.__msgq.run_select()

        self.assertEqual([42, 44], self.__read_called)
        self.assertEqual([43], self.__write_called)
        self.assertEqual({42, 44, 45}, set(self.__reads))
        self.assertEqual({42, 43}, set(self.__writes))
Esempio n. 17
0
class ThreadTests(unittest.TestCase):
    """Test various things around thread synchronization."""

    def setUp(self):
        self.__msgq = MsgQ()
        self.__abort_wait = False
        self.__result = None
        self.__notify_thread = threading.Thread(target=self.__notify)
        self.__wait_thread = threading.Thread(target=self.__wait)
        # Make sure the threads are killed if left behind by the test.
        self.__notify_thread.daemon = True
        self.__wait_thread.daemon = True

    def __notify(self):
        """Call the cfgmgr_ready."""
        if self.__abort_wait:
            self.__msgq.cfgmgr_ready(False)
        else:
            self.__msgq.cfgmgr_ready()

    def __wait(self):
        """Wait for config manager and store the result."""
        self.__result = self.__msgq.wait_cfgmgr()

    def test_wait_cfgmgr(self):
        """One thread signals the config manager subscribed, the other
           waits for it. We then check it terminated correctly.
        """
        self.__notify_thread.start()
        self.__wait_thread.start()
        # Timeout to ensure the test terminates even on failure
        self.__wait_thread.join(60)
        self.assertTrue(self.__result)

    def test_wait_cfgmgr_2(self):
        """Same as test_wait_cfgmgr, but starting the threads in reverse order
           (the result should be the same).
        """
        self.__wait_thread.start()
        self.__notify_thread.start()
        # Timeout to ensure the test terminates even on failure
        self.__wait_thread.join(60)
        self.assertTrue(self.__result)

    def test_wait_abort(self):
        """Similar to test_wait_cfgmgr, but the config manager is never
           subscribed and it is aborted.
        """
        self.__abort_wait = True
        self.__wait_thread.start()
        self.__notify_thread.start()
        # Timeout to ensure the test terminates even on failure
        self.__wait_thread.join(60)
        self.assertIsNotNone(self.__result)
        self.assertFalse(self.__result)

    def __check_ready_and_abort(self):
        """Check that when we first say the config manager is ready and then
           try to abort, it uses the first result.
        """
        self.__msgq.cfgmgr_ready()
        self.__msgq.cfgmgr_ready(False)
        self.__result = self.__msgq.wait_cfgmgr()

    def test_ready_and_abort(self):
        """Perform the __check_ready_and_abort test, but in a separate thread,
           so in case something goes wrong with the synchronisation and it
           deadlocks, the test will terminate anyway.
        """
        test_thread = threading.Thread(target=self.__check_ready_and_abort)
        test_thread.daemon = True
        test_thread.start()
        test_thread.join(60)
        self.assertTrue(self.__result)
Esempio n. 18
0
class MsgQTest(unittest.TestCase):
    """
    Tests for the behaviour of MsgQ. This is for the core of MsgQ, other
    subsystems are in separate test fixtures.
    """
    def setUp(self):
        self.__msgq = MsgQ()

    def parse_msg(self, msg):
        """
        Parse a binary representation of message to the routing header and the
        data payload. It assumes the message is correctly encoded and the
        payload is not omitted. It'd probably throw in other cases, but we
        don't use it in such situations in this test.
        """
        (length, header_len) = struct.unpack('>IH', msg[:6])
        header = json.loads(msg[6:6 + header_len].decode('utf-8'))
        data = json.loads(msg[6 + header_len:].decode('utf-8'))
        return (header, data)

    def test_undeliverable_errors(self):
        """
        Send several packets through the MsgQ and check it generates
        undeliverable notifications under the correct circumstances.

        The test is not exhaustive as it doesn't test all combination
        of existence of the recipient, addressing schemes, want_answer
        header and the reply header. It is not needed, these should
        be mostly independent. That means, for example, if the message
        is a reply and there's no recipient to send it to, the error
        would not be generated no matter if we addressed the recipient
        by lname or group. If we included everything, the test would
        have too many scenarios with little benefit.
        """
        self.__sent_messages = []
        def fake_send_prepared_msg(socket, msg):
            self.__sent_messages.append((socket, msg))
            return True
        self.__msgq.send_prepared_msg = fake_send_prepared_msg
        # These would be real sockets in the MsgQ, but we pass them as
        # parameters only, so we don't need them to be. We use simple
        # integers to tell one from another.
        sender = 1
        recipient = 2
        another_recipiet = 3
        # The routing headers and data to test with.
        routing = {
            'to': '*',
            'from': 'sender',
            'group': 'group',
            'instance': '*',
            'seq': 42
        }
        data = {
            "data": "Just some data"
        }

        # Some common checking patterns
        def check_error():
            self.assertEqual(1, len(self.__sent_messages))
            self.assertEqual(1, self.__sent_messages[0][0])
            self.assertEqual(({
                                  'group': 'group',
                                  'instance': '*',
                                  'reply': 42,
                                  'seq': 42,
                                  'from': 'msgq',
                                  'to': 'sender',
                                  'want_answer': True
                              }, {'result': [-1, "No such recipient"]}),
                              self.parse_msg(self.__sent_messages[0][1]))
            self.__sent_messages = []

        def check_no_message():
            self.assertEqual([], self.__sent_messages)

        def check_delivered(rcpt_socket=recipient):
            self.assertEqual(1, len(self.__sent_messages))
            self.assertEqual(rcpt_socket, self.__sent_messages[0][0])
            self.assertEqual((routing, data),
                             self.parse_msg(self.__sent_messages[0][1]))
            self.__sent_messages = []

        # Send the message. No recipient, but errors are not requested,
        # so none is generated.
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # It should act the same if we explicitly say we do not want replies.
        routing["want_answer"] = False
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # Ask for errors if it can't be delivered.
        routing["want_answer"] = True
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # If the message is a reply itself, we never generate the errors
        routing["reply"] = 3
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # If there are recipients (but no "reply" header), the error should not
        # be sent and the message should get delivered.
        del routing["reply"]
        self.__msgq.subs.find = lambda group, instance: [recipient]
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered()

        # When we send a direct message and the recipient is not there, we get
        # the error too
        routing["to"] = "lname"
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # But when the recipient is there, it is delivered and no error is
        # generated.
        self.__msgq.lnames["lname"] = recipient
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered()

        # If an attempt to send fails, consider it no recipient.
        def fail_send_prepared_msg(socket, msg):
            '''
            Pretend sending a message failed. After one call, return to the
            usual mock, so the errors or other messages can be sent.
            '''
            self.__msgq.send_prepared_msg = fake_send_prepared_msg
            return False

        self.__msgq.send_prepared_msg = fail_send_prepared_msg
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # But if there are more recipients and only one fails, it should
        # be delivered to the other and not considered an error
        self.__msgq.send_prepared_msg = fail_send_prepared_msg
        routing["to"] = '*'
        self.__msgq.subs.find = lambda group, instance: [recipient,
                                                         another_recipiet]
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered(rcpt_socket=another_recipiet)
Esempio n. 19
0
 def setUp(self):
     self.__msgq = MsgQ()
Esempio n. 20
0
    def do_send(self, write, read, control_write, control_read,
                expect_arrive=True, expect_send_exception=None):
        """
        Makes a msgq object that is talking to itself,
        run it in a separate thread so we can use and
        test run().
        It is given two sets of connected sockets; write/read, and
        control_write/control_read. The former may be throwing errors
        and mangle data to test msgq. The second is mainly used to
        send msgq the stop command.
        (Note that the terms 'read' and 'write' are from the msgq
        point of view, so the test itself writes to 'control_read')
        Parameters:
        write: a socket that is used to send the data to
        read: a socket that is used to read the data from
        control_write: a second socket for communication with msgq
        control_read: a second socket for communication with msgq
        expect_arrive: if True, the read socket is read from, and the data
                       that is read is expected to be the same as the data
                       that has been sent to the write socket.
        expect_send_exception: if not None, this is the exception that is
                               expected to be raised by msgq
        """

        # Some message and envelope data to send and check
        env = b'{"env": "foo"}'
        msg = b'{"msg": "bar"}'

        msgq = MsgQ()
        # Don't need a listen_socket
        msgq.listen_socket = DummySocket
        msgq.setup_signalsock()
        msgq.register_socket(write)
        msgq.register_socket(control_write)
        # Queue the message for sending
        msgq.sendmsg(write, env, msg)

        # Run it in a thread
        msgq_thread = MsgQThread(msgq)
        # If we're done, just kill it
        msgq_thread.start()

        if expect_arrive:
            (recv_env, recv_msg) = msgq.read_packet(read.fileno(),
                read)
            self.assertEqual(env, recv_env)
            self.assertEqual(msg, recv_msg)

        # Tell msgq to stop
        msg = msgq.preparemsg({"type" : "stop"})
        control_read.sendall(msg)

        # Wait for thread to stop if it hasn't already.
        # Put in a (long) timeout; the thread *should* stop, but if it
        # does not, we don't want the test to hang forever
        msgq_thread.join(60)
        # Fail the test if it didn't stop
        self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")

        # Clean up some internals of msgq (usually called as part of
        # shutdown, but we skip that one here)
        msgq.cleanup_signalsock()

        # Check the exception from the thread, if any
        # First, if we didn't expect it; reraise it (to make test fail and
        # show the stacktrace for debugging)
        if expect_send_exception is None:
            if msgq_thread.caught_exception is not None:
                raise msgq_thread.caught_exception
        else:
            # If we *did* expect it, fail it there was none
            self.assertIsNotNone(msgq_thread.caught_exception)
Esempio n. 21
0
 def setUp(self):
   self.orderQ = MsgQ()
   self.receiptQ = MsgQ()
   self.broker = Broker(None, self.orderQ, self.receiptQ)
Esempio n. 22
0
class MsgQTest(unittest.TestCase):
    """
    Tests for the behaviour of MsgQ. This is for the core of MsgQ, other
    subsystems are in separate test fixtures.
    """
    def setUp(self):
        self.__msgq = MsgQ()

    def parse_msg(self, msg):
        """
        Parse a binary representation of message to the routing header and the
        data payload. It assumes the message is correctly encoded and the
        payload is not omitted. It'd probably throw in other cases, but we
        don't use it in such situations in this test.
        """
        (length, header_len) = struct.unpack('>IH', msg[:6])
        header = json.loads(msg[6:6 + header_len].decode('utf-8'))
        data = json.loads(msg[6 + header_len:].decode('utf-8'))
        return (header, data)

    def test_unknown_command(self):
        """
        Test the command handler returns error when the command is unknown.
        """
        # Fake we are running, to disable test workarounds
        self.__msgq.running = True
        self.assertEqual({'result': [1, "unknown command: unknown"]},
                         self.__msgq.command_handler('unknown', {}))

    def test_get_members(self):
        """
        Test getting members of a group or of all connected clients.
        """
        # Push two dummy "clients" into msgq (the ugly way, by directly
        # tweaking relevant data structures).
        class Sock:
            def __init__(self, fileno):
                self.fileno = lambda: fileno
        self.__msgq.lnames['first'] = Sock(1)
        self.__msgq.lnames['second'] = Sock(2)
        self.__msgq.fd_to_lname[1] = 'first'
        self.__msgq.fd_to_lname[2] = 'second'
        # Subscribe them to some groups
        self.__msgq.process_command_subscribe(self.__msgq.lnames['first'],
                                              {'group': 'G1', 'instance': '*'},
                                              None)
        self.__msgq.process_command_subscribe(self.__msgq.lnames['second'],
                                              {'group': 'G1', 'instance': '*'},
                                              None)
        self.__msgq.process_command_subscribe(self.__msgq.lnames['second'],
                                              {'group': 'G2', 'instance': '*'},
                                              None)
        # Now query content of some groups through the command handler.
        self.__msgq.running = True # Enable the command handler
        def check_both(result):
            """
            Check the result is successful one and it contains both lnames (in
            any order).
            """
            array = result['result'][1]
            self.assertEqual(set(['first', 'second']), set(array))
            self.assertEqual({'result': [0, array]}, result)
            # Make sure the result can be encoded as JSON
            # (there seems to be types that look like a list but JSON choks
            # on them)
            json.dumps(result)
        # Members of the G1 and G2
        self.assertEqual({'result': [0, ['second']]},
                         self.__msgq.command_handler('members',
                                                     {'group': 'G2'}))
        check_both(self.__msgq.command_handler('members', {'group': 'G1'}))
        # We pretend that all the possible groups exist, just that most
        # of them are empty. So requesting for Empty is request for an empty
        # group and should not fail.
        self.assertEqual({'result': [0, []]},
                         self.__msgq.command_handler('members',
                                                     {'group': 'Empty'}))
        # Without the name of the group, we just get all the clients.
        check_both(self.__msgq.command_handler('members', {}))
        # Omitting the parameters completely in such case is OK
        check_both(self.__msgq.command_handler('members', None))

    def notifications_setup(self):
        """
        Common setup of some notifications tests. Mock several things.
        """
        # Mock the method to send notifications (we don't really want
        # to send them now, just see they'd be sent).
        # Mock the poller, as we don't need it at all (and we don't have
        # real socket to give it now).
        notifications = []
        def send_notification(event, params):
            notifications.append((event, params))
        class FakePoller:
            def register(self, socket, mode):
                pass
            def unregister(self, sock):
                pass
        self.__msgq.members_notify = send_notification
        self.__msgq.poller = FakePoller()

        # Create a socket
        class Sock:
            def __init__(self, fileno):
                self.fileno = lambda: fileno
            def close(self):
                pass
        sock = Sock(1)
        return notifications, sock

    def test_notifies(self):
        """
        Test the message queue sends notifications about connecting,
        disconnecting and subscription changes.
        """
        notifications, sock = self.notifications_setup()

        # We should notify about new cliend when we register it
        self.__msgq.register_socket(sock)
        lname = self.__msgq.fd_to_lname[1] # Steal the lname
        self.assertEqual([('connected', {'client': lname})], notifications)
        del notifications[:]

        # A notification should happen for a subscription to a group
        self.__msgq.process_command_subscribe(sock, {'group': 'G',
                                                     'instance': '*'},
                                              None)
        self.assertEqual([('subscribed', {'client': lname, 'group': 'G'})],
                         notifications)
        del notifications[:]

        # As well for unsubscription
        self.__msgq.process_command_unsubscribe(sock, {'group': 'G',
                                                       'instance': '*'},
                                                None)
        self.assertEqual([('unsubscribed', {'client': lname, 'group': 'G'})],
                         notifications)
        del notifications[:]

        # Unsubscription from a group it isn't subscribed to
        self.__msgq.process_command_unsubscribe(sock, {'group': 'H',
                                                       'instance': '*'},
                                                None)
        self.assertEqual([], notifications)

        # And, finally, for removal of client
        self.__msgq.kill_socket(sock.fileno(), sock)
        self.assertEqual([('disconnected', {'client': lname})], notifications)

    def test_notifies_implicit_kill(self):
        """
        Test that the unsubscription notifications are sent before the socket
        is dropped, even in case it does not unsubscribe explicitly.
        """
        notifications, sock = self.notifications_setup()

        # Register and subscribe. Notifications for these are in above test.
        self.__msgq.register_socket(sock)
        lname = self.__msgq.fd_to_lname[1] # Steal the lname
        self.__msgq.process_command_subscribe(sock, {'group': 'G',
                                                     'instance': '*'},
                                              None)
        del notifications[:]

        self.__msgq.kill_socket(sock.fileno(), sock)
        # Now, the notification for unsubscribe should be first, second for
        # the disconnection.
        self.assertEqual([('unsubscribed', {'client': lname, 'group': 'G'}),
                          ('disconnected', {'client': lname})
                         ], notifications)

    def test_undeliverable_errors(self):
        """
        Send several packets through the MsgQ and check it generates
        undeliverable notifications under the correct circumstances.

        The test is not exhaustive as it doesn't test all combination
        of existence of the recipient, addressing schemes, want_answer
        header and the reply header. It is not needed, these should
        be mostly independent. That means, for example, if the message
        is a reply and there's no recipient to send it to, the error
        would not be generated no matter if we addressed the recipient
        by lname or group. If we included everything, the test would
        have too many scenarios with little benefit.
        """
        self.__sent_messages = []
        def fake_send_prepared_msg(socket, msg):
            self.__sent_messages.append((socket, msg))
            return True
        self.__msgq.send_prepared_msg = fake_send_prepared_msg
        # These would be real sockets in the MsgQ, but we pass them as
        # parameters only, so we don't need them to be. We use simple
        # integers to tell one from another.
        sender = 1
        recipient = 2
        another_recipiet = 3
        # The routing headers and data to test with.
        routing = {
            'to': '*',
            'from': 'sender',
            'group': 'group',
            'instance': '*',
            'seq': 42
        }
        data = {
            "data": "Just some data"
        }

        # Some common checking patterns
        def check_error():
            self.assertEqual(1, len(self.__sent_messages))
            self.assertEqual(1, self.__sent_messages[0][0])
            self.assertEqual(({
                                  'group': 'group',
                                  'instance': '*',
                                  'reply': 42,
                                  'seq': 42,
                                  'from': 'msgq',
                                  'to': 'sender',
                                  'want_answer': True
                              }, {'result': [-1, "No such recipient"]}),
                              self.parse_msg(self.__sent_messages[0][1]))
            self.__sent_messages = []

        def check_no_message():
            self.assertEqual([], self.__sent_messages)

        def check_delivered(rcpt_socket=recipient):
            self.assertEqual(1, len(self.__sent_messages))
            self.assertEqual(rcpt_socket, self.__sent_messages[0][0])
            self.assertEqual((routing, data),
                             self.parse_msg(self.__sent_messages[0][1]))
            self.__sent_messages = []

        # Send the message. No recipient, but errors are not requested,
        # so none is generated.
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # It should act the same if we explicitly say we do not want replies.
        routing["want_answer"] = False
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # Ask for errors if it can't be delivered.
        routing["want_answer"] = True
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # If the message is a reply itself, we never generate the errors
        routing["reply"] = 3
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # If there are recipients (but no "reply" header), the error should not
        # be sent and the message should get delivered.
        del routing["reply"]
        self.__msgq.subs.find = lambda group, instance: [recipient]
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered()

        # When we send a direct message and the recipient is not there, we get
        # the error too
        routing["to"] = "lname"
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # But when the recipient is there, it is delivered and no error is
        # generated.
        self.__msgq.lnames["lname"] = recipient
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered()

        # If an attempt to send fails, consider it no recipient.
        def fail_send_prepared_msg(socket, msg):
            '''
            Pretend sending a message failed. After one call, return to the
            usual mock, so the errors or other messages can be sent.
            '''
            self.__msgq.send_prepared_msg = fake_send_prepared_msg
            return False

        self.__msgq.send_prepared_msg = fail_send_prepared_msg
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # But if there are more recipients and only one fails, it should
        # be delivered to the other and not considered an error
        self.__msgq.send_prepared_msg = fail_send_prepared_msg
        routing["to"] = '*'
        self.__msgq.subs.find = lambda group, instance: [recipient,
                                                         another_recipiet]
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered(rcpt_socket=another_recipiet)
Esempio n. 23
0
 def test_open_socket_bad(self):
     msgq = MsgQ("/does/not/exist")
     self.assertRaises(socket.error, msgq.setup)
     # But we can clean up after that.
     msgq.shutdown()
Esempio n. 24
0
class SocketTests(unittest.TestCase):
    '''Test cases for micro behaviors related to socket operations.

    Some cases are covered as part of other tests, but in this fixture
    we check more details of specific method related to socket operation,
    with the help of mock classes to avoid expensive overhead.

    '''
    class MockSocket():
        '''A mock socket used instead of standard socket objects.'''
        def __init__(self):
            self.ex_on_send = None # raised from send() if not None
            self.recv_result = b'test' # dummy data or exception
            self.blockings = [] # history of setblocking() params
        def setblocking(self, on):
            self.blockings.append(on)
        def send(self, data):
            if self.ex_on_send is not None:
                raise self.ex_on_send
            return 10           # arbitrary choice
        def recv(self, len):
            if isinstance(self.recv_result, Exception):
                raise self.recv_result
            ret = self.recv_result
            self.recv_result = b'' # if called again, return empty data
            return ret
        def fileno(self):
            return 42           # arbitrary choice

    class LoggerWrapper():
        '''A simple wrapper of logger to inspect log messages.'''
        def __init__(self, logger):
            self.error_called = 0
            self.warn_called = 0
            self.debug_called = 0
            self.orig_logger = logger
        def error(self, *args):
            self.error_called += 1
            self.orig_logger.error(*args)
        def warn(self, *args):
            self.warn_called += 1
            self.orig_logger.warn(*args)
        def debug(self, *args):
            self.debug_called += 1
            self.orig_logger.debug(*args)

    def mock_kill_socket(self, fileno, sock):
        '''A replacement of MsgQ.kill_socket method for inspection.'''
        self.__killed_socket = (fileno, sock)
        if fileno in self.__msgq.sockets:
            del self.__msgq.sockets[fileno]

    def setUp(self):
        self.__msgq = MsgQ()
        self.__msgq.kill_socket = self.mock_kill_socket
        self.__sock = self.MockSocket()
        self.__data = b'dummy'
        self.__msgq.sockets[42] = self.__sock
        self.__msgq.sendbuffs[42] = (None, b'testdata')
        self.__sock_error = socket.error()
        self.__killed_socket = None
        self.__logger = self.LoggerWrapper(msgq.logger)
        msgq.logger = self.__logger

    def tearDown(self):
        msgq.logger = self.__logger.orig_logger

    def test_send_data(self):
        # Successful case: _send_data() returns the hardcoded value, and
        # setblocking() is called twice with the expected parameters
        self.assertEqual(10, self.__msgq._send_data(self.__sock, self.__data))
        self.assertEqual([0, 1], self.__sock.blockings)
        self.assertIsNone(self.__killed_socket)

    def test_send_data_interrupt(self):
        '''send() is interrupted. send_data() returns 0, sock isn't killed.'''
        expected_blockings = []
        for eno in [errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR]:
            self.__sock_error.errno = eno
            self.__sock.ex_on_send = self.__sock_error
            self.assertEqual(0, self.__msgq._send_data(self.__sock,
                                                       self.__data))
            expected_blockings.extend([0, 1])
            self.assertEqual(expected_blockings, self.__sock.blockings)
            self.assertIsNone(self.__killed_socket)

    def test_send_data_error(self):
        '''Unexpected error happens on send().  The socket is killed.

        If the error is EPIPE, it's logged at the warn level; otherwise
        an error message is logged.

        '''
        expected_blockings = []
        expected_errors = 0
        expected_warns = 0
        for eno in [errno.EPIPE, errno.ECONNRESET, errno.ENOBUFS]:
            self.__sock_error.errno = eno
            self.__sock.ex_on_send = self.__sock_error
            self.__killed_socket = None # clear any previuos value
            self.assertEqual(None, self.__msgq._send_data(self.__sock,
                                                          self.__data))
            self.assertEqual((42, self.__sock), self.__killed_socket)
            expected_blockings.extend([0, 1])
            self.assertEqual(expected_blockings, self.__sock.blockings)

            if eno == errno.EPIPE:
                expected_warns += 1
            else:
                expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_warns, self.__logger.warn_called)

    def test_process_fd_read_after_bad_write(self):
        '''Check the specific case of write fail followed by read attempt.

        The write failure results in kill_socket, then read shouldn't tried.

        '''
        self.__sock_error.errno = errno.EPIPE
        self.__sock.ex_on_send = self.__sock_error
        self.__msgq.process_socket = None # if called, trigger an exception
        self.__msgq._process_fd(42, True, True, False) # shouldn't crash

        # check the socket is deleted from the fileno=>sock dictionary
        self.assertEqual({}, self.__msgq.sockets)

    def test_process_fd_close_after_bad_write(self):
        '''Similar to the previous, but for checking dup'ed kill attempt'''
        self.__sock_error.errno = errno.EPIPE
        self.__sock.ex_on_send = self.__sock_error
        self.__msgq._process_fd(42, True, False, True) # shouldn't crash
        self.assertEqual({}, self.__msgq.sockets)

    def test_process_fd_writer_after_close(self):
        '''Emulate a "writable" socket has been already closed and killed.'''
        # This just shouldn't crash
        self.__msgq._process_fd(4200, True, False, False)

    def test_process_packet(self):
        '''Check some failure cases in handling an incoming message.'''
        expected_errors = 0
        expected_debugs = 0

        # if socket.recv() fails due to socket.error, it will be logged
        # as error and the socket will be killed regardless of errno.
        for eno in [errno.ENOBUFS, errno.ECONNRESET]:
            self.__sock_error.errno = eno
            self.__sock.recv_result = self.__sock_error
            self.__killed_socket = None # clear any previuos value
            self.__msgq.process_packet(42, self.__sock)
            self.assertEqual((42, self.__sock), self.__killed_socket)
            expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_debugs, self.__logger.debug_called)

        # if socket.recv() returns empty data, the result depends on whether
        # there's any preceding data; in the second case below, at least
        # 6 bytes of data will be expected, and the second call to our faked
        # recv() returns empty data.  In that case it will be logged as error.
        for recv_data in [b'', b'short']:
            self.__sock.recv_result = recv_data
            self.__killed_socket = None
            self.__msgq.process_packet(42, self.__sock)
            self.assertEqual((42, self.__sock), self.__killed_socket)
            if len(recv_data) == 0:
                expected_debugs += 1
            else:
                expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_debugs, self.__logger.debug_called)
Esempio n. 25
0
class SocketTests(unittest.TestCase):
    '''Test cases for micro behaviors related to socket operations.

    Some cases are covered as part of other tests, but in this fixture
    we check more details of specific method related to socket operation,
    with the help of mock classes to avoid expensive overhead.

    '''
    class MockSocket():
        '''A mock socket used instead of standard socket objects.'''
        def __init__(self):
            self.ex_on_send = None  # raised from send() if not None
            self.recv_result = b'test'  # dummy data or exception
            self.blockings = []  # history of setblocking() params

        def setblocking(self, on):
            self.blockings.append(on)

        def send(self, data):
            if self.ex_on_send is not None:
                raise self.ex_on_send
            return 10  # arbitrary choice

        def recv(self, len):
            if isinstance(self.recv_result, Exception):
                raise self.recv_result
            ret = self.recv_result
            self.recv_result = b''  # if called again, return empty data
            return ret

        def fileno(self):
            return 42  # arbitrary choice

    class LoggerWrapper():
        '''A simple wrapper of logger to inspect log messages.'''
        def __init__(self, logger):
            self.error_called = 0
            self.warn_called = 0
            self.debug_called = 0
            self.orig_logger = logger

        def error(self, *args):
            self.error_called += 1
            self.orig_logger.error(*args)

        def warn(self, *args):
            self.warn_called += 1
            self.orig_logger.warn(*args)

        def debug(self, *args):
            self.debug_called += 1
            self.orig_logger.debug(*args)

    def mock_kill_socket(self, fileno, sock):
        '''A replacement of MsgQ.kill_socket method for inspection.'''
        self.__killed_socket = (fileno, sock)
        if fileno in self.__msgq.sockets:
            del self.__msgq.sockets[fileno]

    def setUp(self):
        self.__msgq = MsgQ()
        self.__msgq.kill_socket = self.mock_kill_socket
        self.__sock = self.MockSocket()
        self.__data = b'dummy'
        self.__msgq.sockets[42] = self.__sock
        self.__msgq.sendbuffs[42] = (None, b'testdata')
        self.__sock_error = socket.error()
        self.__killed_socket = None
        self.__logger = self.LoggerWrapper(msgq.logger)
        msgq.logger = self.__logger
        self.__orig_select = msgq.select.select

    def tearDown(self):
        msgq.logger = self.__logger.orig_logger
        msgq.select.select = self.__orig_select

    def test_send_data(self):
        # Successful case: _send_data() returns the hardcoded value, and
        # setblocking() is called twice with the expected parameters
        self.assertEqual(10, self.__msgq._send_data(self.__sock, self.__data))
        self.assertEqual([0, 1], self.__sock.blockings)
        self.assertIsNone(self.__killed_socket)

    def test_send_data_interrupt(self):
        '''send() is interrupted. send_data() returns 0, sock isn't killed.'''
        expected_blockings = []
        for eno in [errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR]:
            self.__sock_error.errno = eno
            self.__sock.ex_on_send = self.__sock_error
            self.assertEqual(0, self.__msgq._send_data(self.__sock,
                                                       self.__data))
            expected_blockings.extend([0, 1])
            self.assertEqual(expected_blockings, self.__sock.blockings)
            self.assertIsNone(self.__killed_socket)

    def test_send_data_error(self):
        '''Unexpected error happens on send().  The socket is killed.

        If the error is EPIPE, it's logged at the warn level; otherwise
        an error message is logged.

        '''
        expected_blockings = []
        expected_errors = 0
        expected_warns = 0
        for eno in [errno.EPIPE, errno.ECONNRESET, errno.ENOBUFS]:
            self.__sock_error.errno = eno
            self.__sock.ex_on_send = self.__sock_error
            self.__killed_socket = None  # clear any previuos value
            self.assertEqual(None,
                             self.__msgq._send_data(self.__sock, self.__data))
            self.assertEqual((42, self.__sock), self.__killed_socket)
            expected_blockings.extend([0, 1])
            self.assertEqual(expected_blockings, self.__sock.blockings)

            if eno == errno.EPIPE:
                expected_warns += 1
            else:
                expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_warns, self.__logger.warn_called)

    def test_process_packet(self):
        '''Check some failure cases in handling an incoming message.'''
        expected_errors = 0
        expected_debugs = 0

        # if socket.recv() fails due to socket.error, it will be logged
        # as error and the socket will be killed regardless of errno.
        for eno in [errno.ENOBUFS, errno.ECONNRESET]:
            self.__sock_error.errno = eno
            self.__sock.recv_result = self.__sock_error
            self.__killed_socket = None  # clear any previuos value
            self.__msgq.process_packet(42, self.__sock)
            self.assertEqual((42, self.__sock), self.__killed_socket)
            expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_debugs, self.__logger.debug_called)

        # if socket.recv() returns empty data, the result depends on whether
        # there's any preceding data; in the second case below, at least
        # 6 bytes of data will be expected, and the second call to our faked
        # recv() returns empty data.  In that case it will be logged as error.
        for recv_data in [b'', b'short']:
            self.__sock.recv_result = recv_data
            self.__killed_socket = None
            self.__msgq.process_packet(42, self.__sock)
            self.assertEqual((42, self.__sock), self.__killed_socket)
            if len(recv_data) == 0:
                expected_debugs += 1
            else:
                expected_errors += 1
            self.assertEqual(expected_errors, self.__logger.error_called)
            self.assertEqual(expected_debugs, self.__logger.debug_called)

    def test_do_select(self):
        """
        Check the behaviour of the run_select method.

        In particular, check that we skip writing to the sockets we read,
        because a read may have side effects (like closing the socket) and
        we want to prevent strange behavior.
        """
        self.__read_called = []
        self.__write_called = []
        self.__reads = None
        self.__writes = None

        def do_read(fd, socket):
            self.__read_called.append(fd)
            self.__msgq.running = False

        def do_write(fd):
            self.__write_called.append(fd)
            self.__msgq.running = False

        self.__msgq.process_packet = do_read
        self.__msgq._process_write = do_write
        self.__msgq.fd_to_lname = {42: 'lname', 44: 'other', 45: 'unused'}
        # The do_select does index it, but just passes the value. So reuse
        # the dict to safe typing in the test.
        self.__msgq.sockets = self.__msgq.fd_to_lname
        self.__msgq.sendbuffs = {42: 'data', 43: 'data'}

        def my_select(reads, writes, errors):
            self.__reads = reads
            self.__writes = writes
            self.assertEqual([], errors)
            return ([42, 44], [42, 43], [])

        msgq.select.select = my_select
        self.__msgq.listen_socket = DummySocket

        self.__msgq.running = True
        self.__msgq.run_select()

        self.assertEqual([42, 44], self.__read_called)
        self.assertEqual([43], self.__write_called)
        self.assertEqual({42, 44, 45}, set(self.__reads))
        self.assertEqual({42, 43}, set(self.__writes))
Esempio n. 26
0
class MsgQTest(unittest.TestCase):
    """
    Tests for the behaviour of MsgQ. This is for the core of MsgQ, other
    subsystems are in separate test fixtures.
    """
    def setUp(self):
        self.__msgq = MsgQ()

    def parse_msg(self, msg):
        """
        Parse a binary representation of message to the routing header and the
        data payload. It assumes the message is correctly encoded and the
        payload is not omitted. It'd probably throw in other cases, but we
        don't use it in such situations in this test.
        """
        (length, header_len) = struct.unpack('>IH', msg[:6])
        header = json.loads(msg[6:6 + header_len].decode('utf-8'))
        data = json.loads(msg[6 + header_len:].decode('utf-8'))
        return (header, data)

    def test_unknown_command(self):
        """
        Test the command handler returns error when the command is unknown.
        """
        # Fake we are running, to disable test workarounds
        self.__msgq.running = True
        self.assertEqual({'result': [1, "unknown command: unknown"]},
                         self.__msgq.command_handler('unknown', {}))

    def test_get_members(self):
        """
        Test getting members of a group or of all connected clients.
        """

        # Push two dummy "clients" into msgq (the ugly way, by directly
        # tweaking relevant data structures).
        class Sock:
            def __init__(self, fileno):
                self.fileno = lambda: fileno

        self.__msgq.lnames['first'] = Sock(1)
        self.__msgq.lnames['second'] = Sock(2)
        self.__msgq.fd_to_lname[1] = 'first'
        self.__msgq.fd_to_lname[2] = 'second'
        # Subscribe them to some groups
        self.__msgq.process_command_subscribe(self.__msgq.lnames['first'], {
            'group': 'G1',
            'instance': '*'
        }, None)
        self.__msgq.process_command_subscribe(self.__msgq.lnames['second'], {
            'group': 'G1',
            'instance': '*'
        }, None)
        self.__msgq.process_command_subscribe(self.__msgq.lnames['second'], {
            'group': 'G2',
            'instance': '*'
        }, None)
        # Now query content of some groups through the command handler.
        self.__msgq.running = True  # Enable the command handler

        def check_both(result):
            """
            Check the result is successful one and it contains both lnames (in
            any order).
            """
            array = result['result'][1]
            self.assertEqual(set(['first', 'second']), set(array))
            self.assertEqual({'result': [0, array]}, result)
            # Make sure the result can be encoded as JSON
            # (there seems to be types that look like a list but JSON choks
            # on them)
            json.dumps(result)

        # Members of the G1 and G2
        self.assertEqual({'result': [0, ['second']]},
                         self.__msgq.command_handler('members',
                                                     {'group': 'G2'}))
        check_both(self.__msgq.command_handler('members', {'group': 'G1'}))
        # We pretend that all the possible groups exist, just that most
        # of them are empty. So requesting for Empty is request for an empty
        # group and should not fail.
        self.assertEqual({'result': [0, []]},
                         self.__msgq.command_handler('members',
                                                     {'group': 'Empty'}))
        # Without the name of the group, we just get all the clients.
        check_both(self.__msgq.command_handler('members', {}))
        # Omitting the parameters completely in such case is OK
        check_both(self.__msgq.command_handler('members', None))

    def notifications_setup(self):
        """
        Common setup of some notifications tests. Mock several things.
        """
        # Mock the method to send notifications (we don't really want
        # to send them now, just see they'd be sent).
        # Mock the poller, as we don't need it at all (and we don't have
        # real socket to give it now).
        notifications = []

        def send_notification(event, params):
            notifications.append((event, params))

        class FakePoller:
            def register(self, socket, mode):
                pass

            def unregister(self, sock):
                pass

        self.__msgq.members_notify = send_notification
        self.__msgq.poller = FakePoller()

        # Create a socket
        class Sock:
            def __init__(self, fileno):
                self.fileno = lambda: fileno

            def close(self):
                pass

        sock = Sock(1)
        return notifications, sock

    def test_notifies(self):
        """
        Test the message queue sends notifications about connecting,
        disconnecting and subscription changes.
        """
        notifications, sock = self.notifications_setup()

        # We should notify about new cliend when we register it
        self.__msgq.register_socket(sock)
        lname = self.__msgq.fd_to_lname[1]  # Steal the lname
        self.assertEqual([('connected', {'client': lname})], notifications)
        del notifications[:]

        # A notification should happen for a subscription to a group
        self.__msgq.process_command_subscribe(sock, {
            'group': 'G',
            'instance': '*'
        }, None)
        self.assertEqual([('subscribed', {
            'client': lname,
            'group': 'G'
        })], notifications)
        del notifications[:]

        # As well for unsubscription
        self.__msgq.process_command_unsubscribe(sock, {
            'group': 'G',
            'instance': '*'
        }, None)
        self.assertEqual([('unsubscribed', {
            'client': lname,
            'group': 'G'
        })], notifications)
        del notifications[:]

        # Unsubscription from a group it isn't subscribed to
        self.__msgq.process_command_unsubscribe(sock, {
            'group': 'H',
            'instance': '*'
        }, None)
        self.assertEqual([], notifications)

        # And, finally, for removal of client
        self.__msgq.kill_socket(sock.fileno(), sock)
        self.assertEqual([('disconnected', {'client': lname})], notifications)

    def test_notifies_implicit_kill(self):
        """
        Test that the unsubscription notifications are sent before the socket
        is dropped, even in case it does not unsubscribe explicitly.
        """
        notifications, sock = self.notifications_setup()

        # Register and subscribe. Notifications for these are in above test.
        self.__msgq.register_socket(sock)
        lname = self.__msgq.fd_to_lname[1]  # Steal the lname
        self.__msgq.process_command_subscribe(sock, {
            'group': 'G',
            'instance': '*'
        }, None)
        del notifications[:]

        self.__msgq.kill_socket(sock.fileno(), sock)
        # Now, the notification for unsubscribe should be first, second for
        # the disconnection.
        self.assertEqual([('unsubscribed', {
            'client': lname,
            'group': 'G'
        }), ('disconnected', {
            'client': lname
        })], notifications)

    def test_undeliverable_errors(self):
        """
        Send several packets through the MsgQ and check it generates
        undeliverable notifications under the correct circumstances.

        The test is not exhaustive as it doesn't test all combination
        of existence of the recipient, addressing schemes, want_answer
        header and the reply header. It is not needed, these should
        be mostly independent. That means, for example, if the message
        is a reply and there's no recipient to send it to, the error
        would not be generated no matter if we addressed the recipient
        by lname or group. If we included everything, the test would
        have too many scenarios with little benefit.
        """
        self.__sent_messages = []

        def fake_send_prepared_msg(socket, msg):
            self.__sent_messages.append((socket, msg))
            return True

        self.__msgq.send_prepared_msg = fake_send_prepared_msg
        # These would be real sockets in the MsgQ, but we pass them as
        # parameters only, so we don't need them to be. We use simple
        # integers to tell one from another.
        sender = 1
        recipient = 2
        another_recipiet = 3
        # The routing headers and data to test with.
        routing = {
            'to': '*',
            'from': 'sender',
            'group': 'group',
            'instance': '*',
            'seq': 42
        }
        data = {"data": "Just some data"}

        # Some common checking patterns
        def check_error():
            self.assertEqual(1, len(self.__sent_messages))
            self.assertEqual(1, self.__sent_messages[0][0])
            self.assertEqual(({
                'group': 'group',
                'instance': '*',
                'reply': 42,
                'seq': 42,
                'from': 'msgq',
                'to': 'sender',
                'want_answer': True
            }, {
                'result': [-1, "No such recipient"]
            }), self.parse_msg(self.__sent_messages[0][1]))
            self.__sent_messages = []

        def check_no_message():
            self.assertEqual([], self.__sent_messages)

        def check_delivered(rcpt_socket=recipient):
            self.assertEqual(1, len(self.__sent_messages))
            self.assertEqual(rcpt_socket, self.__sent_messages[0][0])
            self.assertEqual((routing, data),
                             self.parse_msg(self.__sent_messages[0][1]))
            self.__sent_messages = []

        # Send the message. No recipient, but errors are not requested,
        # so none is generated.
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # It should act the same if we explicitly say we do not want replies.
        routing["want_answer"] = False
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # Ask for errors if it can't be delivered.
        routing["want_answer"] = True
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # If the message is a reply itself, we never generate the errors
        routing["reply"] = 3
        self.__msgq.process_command_send(sender, routing, data)
        check_no_message()

        # If there are recipients (but no "reply" header), the error should not
        # be sent and the message should get delivered.
        del routing["reply"]
        self.__msgq.subs.find = lambda group, instance: [recipient]
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered()

        # When we send a direct message and the recipient is not there, we get
        # the error too
        routing["to"] = "lname"
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # But when the recipient is there, it is delivered and no error is
        # generated.
        self.__msgq.lnames["lname"] = recipient
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered()

        # If an attempt to send fails, consider it no recipient.
        def fail_send_prepared_msg(socket, msg):
            '''
            Pretend sending a message failed. After one call, return to the
            usual mock, so the errors or other messages can be sent.
            '''
            self.__msgq.send_prepared_msg = fake_send_prepared_msg
            return False

        self.__msgq.send_prepared_msg = fail_send_prepared_msg
        self.__msgq.process_command_send(sender, routing, data)
        check_error()

        # But if there are more recipients and only one fails, it should
        # be delivered to the other and not considered an error
        self.__msgq.send_prepared_msg = fail_send_prepared_msg
        routing["to"] = '*'
        self.__msgq.subs.find = lambda group, instance: [
            recipient, another_recipiet
        ]
        self.__msgq.process_command_send(sender, routing, data)
        check_delivered(rcpt_socket=another_recipiet)
Esempio n. 27
0
class ThreadTests(unittest.TestCase):
    """Test various things around thread synchronization."""
    def setUp(self):
        self.__msgq = MsgQ()
        self.__abort_wait = False
        self.__result = None
        self.__notify_thread = threading.Thread(target=self.__notify)
        self.__wait_thread = threading.Thread(target=self.__wait)
        # Make sure the threads are killed if left behind by the test.
        self.__notify_thread.daemon = True
        self.__wait_thread.daemon = True

    def __notify(self):
        """Call the cfgmgr_ready."""
        if self.__abort_wait:
            self.__msgq.cfgmgr_ready(False)
        else:
            self.__msgq.cfgmgr_ready()

    def __wait(self):
        """Wait for config manager and store the result."""
        self.__result = self.__msgq.wait_cfgmgr()

    def test_wait_cfgmgr(self):
        """One thread signals the config manager subscribed, the other
           waits for it. We then check it terminated correctly.
        """
        self.__notify_thread.start()
        self.__wait_thread.start()
        # Timeout to ensure the test terminates even on failure
        self.__wait_thread.join(60)
        self.assertTrue(self.__result)

    def test_wait_cfgmgr_2(self):
        """Same as test_wait_cfgmgr, but starting the threads in reverse order
           (the result should be the same).
        """
        self.__wait_thread.start()
        self.__notify_thread.start()
        # Timeout to ensure the test terminates even on failure
        self.__wait_thread.join(60)
        self.assertTrue(self.__result)

    def test_wait_abort(self):
        """Similar to test_wait_cfgmgr, but the config manager is never
           subscribed and it is aborted.
        """
        self.__abort_wait = True
        self.__wait_thread.start()
        self.__notify_thread.start()
        # Timeout to ensure the test terminates even on failure
        self.__wait_thread.join(60)
        self.assertIsNotNone(self.__result)
        self.assertFalse(self.__result)

    def __check_ready_and_abort(self):
        """Check that when we first say the config manager is ready and then
           try to abort, it uses the first result.
        """
        self.__msgq.cfgmgr_ready()
        self.__msgq.cfgmgr_ready(False)
        self.__result = self.__msgq.wait_cfgmgr()

    def test_ready_and_abort(self):
        """Perform the __check_ready_and_abort test, but in a separate thread,
           so in case something goes wrong with the synchronisation and it
           deadlocks, the test will terminate anyway.
        """
        test_thread = threading.Thread(target=self.__check_ready_and_abort)
        test_thread.daemon = True
        test_thread.start()
        test_thread.join(60)
        self.assertTrue(self.__result)
Esempio n. 28
0
    def do_send(self,
                write,
                read,
                control_write,
                control_read,
                expect_arrive=True,
                expect_send_exception=None):
        """
        Makes a msgq object that is talking to itself,
        run it in a separate thread so we can use and
        test run().
        It is given two sets of connected sockets; write/read, and
        control_write/control_read. The former may be throwing errors
        and mangle data to test msgq. The second is mainly used to
        send msgq the stop command.
        (Note that the terms 'read' and 'write' are from the msgq
        point of view, so the test itself writes to 'control_read')
        Parameters:
        write: a socket that is used to send the data to
        read: a socket that is used to read the data from
        control_write: a second socket for communication with msgq
        control_read: a second socket for communication with msgq
        expect_arrive: if True, the read socket is read from, and the data
                       that is read is expected to be the same as the data
                       that has been sent to the write socket.
        expect_send_exception: if not None, this is the exception that is
                               expected to be raised by msgq
        """

        # Some message and envelope data to send and check
        env = b'{"env": "foo"}'
        msg = b'{"msg": "bar"}'

        msgq = MsgQ()
        # Don't need a listen_socket
        msgq.listen_socket = DummySocket
        msgq.setup_signalsock()
        msgq.register_socket(write)
        msgq.register_socket(control_write)
        # Queue the message for sending
        msgq.sendmsg(write, env, msg)

        # Run it in a thread
        msgq_thread = MsgQThread(msgq)
        # If we're done, just kill it
        msgq_thread.start()

        if expect_arrive:
            (recv_env, recv_msg) = msgq.read_packet(read.fileno(), read)
            self.assertEqual(env, recv_env)
            self.assertEqual(msg, recv_msg)

        # Tell msgq to stop
        msg = msgq.preparemsg({"type": "stop"})
        control_read.sendall(msg)

        # Wait for thread to stop if it hasn't already.
        # Put in a (long) timeout; the thread *should* stop, but if it
        # does not, we don't want the test to hang forever
        msgq_thread.join(60)
        # Fail the test if it didn't stop
        self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")

        # Clean up some internals of msgq (usually called as part of
        # shutdown, but we skip that one here)
        msgq.cleanup_signalsock()

        # Check the exception from the thread, if any
        # First, if we didn't expect it; reraise it (to make test fail and
        # show the stacktrace for debugging)
        if expect_send_exception is None:
            if msgq_thread.caught_exception is not None:
                raise msgq_thread.caught_exception
        else:
            # If we *did* expect it, fail it there was none
            self.assertIsNotNone(msgq_thread.caught_exception)
Esempio n. 29
0
 def setUp(self):
     self.__msgq = MsgQ()