From c033521602d81ee225192d1d86182d278af45269 Mon Sep 17 00:00:00 2001 From: Vincent Pelletier <vincent@nexedi.com> Date: Wed, 1 Apr 2009 10:05:42 +0000 Subject: [PATCH] Initial import of ClientHandler class unit test. Note: there are 3 unimplemented tests (raising NotImplementedError). test_StopOperation: looks unimplemented in current code (no action besides logging). test_knownMasterNotifyNodeInformation & test_unknownMasterNotifyNodeInformation: it is yet unsure on how to test those cases. git-svn-id: https://svn.erp5.org/repos/neo/branches/prototype3@309 71dcc9de-d417-0410-9af5-da40c76e7ee4 --- neo/client/tests/testClientHandler.py | 1028 +++++++++++++++++++++++++ 1 file changed, 1028 insertions(+) create mode 100644 neo/client/tests/testClientHandler.py diff --git a/neo/client/tests/testClientHandler.py b/neo/client/tests/testClientHandler.py new file mode 100644 index 00000000..489f7eb8 --- /dev/null +++ b/neo/client/tests/testClientHandler.py @@ -0,0 +1,1028 @@ +# +# Copyright (C) 2009 Nexedi SA +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import os +import unittest +import logging +from mock import Mock, ReturnValues +from neo.protocol import Packet, INVALID_UUID +from neo.protocol import ERROR, REQUEST_NODE_IDENTIFICATION, ACCEPT_NODE_IDENTIFICATION, \ + PING, PONG, ASK_PRIMARY_MASTER, ANSWER_PRIMARY_MASTER, ANNOUNCE_PRIMARY_MASTER, \ + REELECT_PRIMARY_MASTER, NOTIFY_NODE_INFORMATION, START_OPERATION, \ + STOP_OPERATION, ASK_LAST_IDS, ANSWER_LAST_IDS, ASK_PARTITION_TABLE, \ + ANSWER_PARTITION_TABLE, SEND_PARTITION_TABLE, NOTIFY_PARTITION_CHANGES, \ + ASK_UNFINISHED_TRANSACTIONS, ANSWER_UNFINISHED_TRANSACTIONS, \ + ASK_OBJECT_PRESENT, ANSWER_OBJECT_PRESENT, \ + DELETE_TRANSACTION, COMMIT_TRANSACTION, ASK_NEW_TID, ANSWER_NEW_TID, \ + FINISH_TRANSACTION, NOTIFY_TRANSACTION_FINISHED, LOCK_INFORMATION, \ + NOTIFY_INFORMATION_LOCKED, INVALIDATE_OBJECTS, UNLOCK_INFORMATION, \ + ASK_NEW_OIDS, ANSWER_NEW_OIDS, ASK_STORE_OBJECT, ANSWER_STORE_OBJECT, \ + ABORT_TRANSACTION, ASK_STORE_TRANSACTION, ANSWER_STORE_TRANSACTION, \ + ASK_OBJECT, ANSWER_OBJECT, ASK_TIDS, ANSWER_TIDS, ASK_TRANSACTION_INFORMATION, \ + ANSWER_TRANSACTION_INFORMATION, ASK_OBJECT_HISTORY, ANSWER_OBJECT_HISTORY, \ + ASK_OIDS, ANSWER_OIDS, \ + NOT_READY_CODE, OID_NOT_FOUND_CODE, SERIAL_NOT_FOUND_CODE, TID_NOT_FOUND_CODE, \ + PROTOCOL_ERROR_CODE, TIMEOUT_ERROR_CODE, BROKEN_NODE_DISALLOWED_CODE, \ + INTERNAL_ERROR_CODE, \ + STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \ + RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \ + UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, DISCARDED_STATE +from neo.exception import ElectionFailure + +from neo.client.handler import ClientEventHandler +from neo.node import StorageNode + +MARKER = [] + +class ClientEventHandlerTest(unittest.TestCase): + + def setUp(self): + # Silence all log messages + logging.basicConfig(level=logging.CRITICAL + 1) + +# def tearDown(self): +# pass + + def getConnection(self, uuid=None, port=10010, next_id=None, ip='127.0.0.1'): + if uuid is None: + uuid = self.getUUID() + return Mock({'addPacket': None, + 'getUUID': uuid, + 'getAddress': (ip, port), + 'getNextId': next_id, + 'lock': None, + 'unlock': None}) + + def getUUID(self): + uuid = INVALID_UUID + while uuid == INVALID_UUID: + uuid = os.urandom(16) + self.uuid = uuid + return uuid + + def getDispatcher(self, queue=None): + return Mock({'getQueue': queue, 'connectToPrimaryMasterNode': None}) + + def test_ping(self): + """ + Simplest test: check that a PING packet is answered by a PONG + packet. + """ + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(None, dispatcher) + conn = self.getConnection() + client_handler.packetReceived(conn, Packet().ping(1)) + pong = conn.mockGetNamedCalls('addPacket')[0].getParam(0) + self.assertTrue(isinstance(pong, Packet)) + self.assertEquals(pong.getType(), PONG) + + def _testInitialMasterWithMethod(self, method): + class App: + primary_master_node = None + app = App() + method(self.getDispatcher(), app) + self.assertEqual(app.primary_master_node, -1) + + def _testMasterWithMethod(self, method): + uuid = self.getUUID() + class App: + primary_master_node = Mock({'getUUID': uuid}) + master_conn = Mock({'close': None, 'getUUID': uuid}) + app = App() + dispatcher = self.getDispatcher() + method(dispatcher, app, uuid=uuid) + # XXX: should connection closure be tested ? It's not implemented in all cases + #self.assertEquals(len(App.master_conn.mockGetNamedCalls('close')), 1) + #self.assertEquals(app.master_conn, None) + #self.assertEquals(app.primary_master_node, None) + self.assertEquals(len(dispatcher.mockGetNamedCalls('connectToPrimaryMasterNode')), 1) + + def _testStorageWithMethod(self, method, state=TEMPORARILY_DOWN_STATE): + storage_ip = '127.0.0.1' + storage_port = 10011 + fake_storage_node_uuid = self.getUUID() + # XXX: Must be replaced by a Mock object, but application uses isinstance... + fake_storage_node = StorageNode(uuid=fake_storage_node_uuid, server=(storage_ip, storage_port)) + master_node_next_packet_id = 1 + class App: + primary_master_node = Mock({'getUUID': self.getUUID()}) + nm = Mock({'getNodeByServer': fake_storage_node}) + cp = Mock({'removeConnection': None}) # TODO: Add expectation on parameter value (must be fake_storage_node) + master_conn = self.getConnection(next_id=ReturnValues(master_node_next_packet_id)) + app = App() + conn = self.getConnection(port=storage_port, ip=storage_ip) + key_1 = (id(conn), 0) + queue_1 = Mock({'put': None, '__hash__': 1}) # TODO: Add expectation on parameter value (must be key_1) + # Fake another Storage connection by adding 1 to id(conn) + key_2 = (id(conn) + 1, 0) + queue_2 = Mock({'put': None, '__hash__': 2}) + class Dispatcher: + message_table = {key_1: queue_1, + key_2: queue_2} + dispatcher = Dispatcher() + method(dispatcher, app, conn=conn) + # Check that master was notified of the failure + addPacket_call_list = app.master_conn.mockGetNamedCalls('addPacket') + # Test sanity check + self.assertEqual(len(addPacket_call_list), 1) + node_status_packet = addPacket_call_list[0].getParam(0) + self.assertTrue(isinstance(node_status_packet, Packet)) + # Test sanity check + self.assertEquals(node_status_packet.getId(), master_node_next_packet_id) + self.assertEquals(node_status_packet.getType(), NOTIFY_NODE_INFORMATION) + self.assertEquals(node_status_packet.decode()[0], + [(STORAGE_NODE_TYPE, storage_ip, storage_port, + fake_storage_node_uuid, state), ] + ) + # Check that failed connection got removed from connection pool + removeConnection_call_list = app.cp.mockGetNamedCalls('removeConnection') + # Test sanity check + self.assertEqual(len(removeConnection_call_list), 1) + self.assertTrue(removeConnection_call_list[0].getParam(0) is fake_storage_node) + # Check that fake packet was put into queue_1, and none in queue_2. + queue_1_put_call_list = queue_1.mockGetNamedCalls('put') + self.assertEqual(len(queue_1_put_call_list), 1) + self.assertEqual(queue_1_put_call_list[0].getParam(0), (conn, None)) + self.assertEqual(len(queue_2.mockGetNamedCalls('put')), 0) + + + def _testConnectionFailed(self, dispatcher, app, uuid=None, conn=None): + client_handler = ClientEventHandler(app, dispatcher) + if conn is None: + conn = self.getConnection(uuid=uuid) + client_handler.connectionFailed(conn) + + def test_initialMasterConnectionFailed(self): + self._testInitialMasterWithMethod(self._testConnectionFailed) + + def test_masterConnectionFailed(self): + self._testMasterWithMethod(self._testConnectionFailed) + + def test_storageConnectionFailed(self): + self._testStorageWithMethod(self._testConnectionFailed) + + def _testConnectionClosed(self, dispatcher, app, uuid=None, conn=None): + client_handler = ClientEventHandler(app, dispatcher) + if conn is None: + conn = self.getConnection(uuid=uuid) + client_handler.connectionClosed(conn) + + def test_initialMasterConnectionClosed(self): + self._testInitialMasterWithMethod(self._testConnectionClosed) + + def test_masterConnectionClosed(self): + self._testMasterWithMethod(self._testConnectionClosed) + + def test_storageConnectionClosed(self): + self._testStorageWithMethod(self._testConnectionClosed) + + def _testTimeoutExpired(self, dispatcher, app, uuid=None, conn=None): + client_handler = ClientEventHandler(app, dispatcher) + if conn is None: + conn = self.getConnection(uuid=uuid) + client_handler.timeoutExpired(conn) + + def test_initialMasterTimeoutExpired(self): + self._testInitialMasterWithMethod(self._testTimeoutExpired) + + def test_masterTimeoutExpired(self): + self._testMasterWithMethod(self._testTimeoutExpired) + + def test_storageTimeoutExpired(self): + self._testStorageWithMethod(self._testTimeoutExpired) + + def _testPeerBroken(self, dispatcher, app, uuid=None, conn=None): + client_handler = ClientEventHandler(app, dispatcher) + if conn is None: + conn = self.getConnection(uuid=uuid) + client_handler.peerBroken(conn) + + def test_initialMasterPeerBroken(self): + self._testInitialMasterWithMethod(self._testPeerBroken) + + def test_masterPeerBroken(self): + self._testMasterWithMethod(self._testPeerBroken) + + def test_storagePeerBroken(self): + self._testStorageWithMethod(self._testPeerBroken, state=BROKEN_STATE) + + def test_notReady(self): + class FakeLocal: + node_not_ready = None + class App: + local_var = FakeLocal() + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + client_handler.handleNotReady(conn, None, None) + self.assertEquals(app.local_var.node_not_ready, 1) + + + def test_clientAcceptNodeIdentification(self): + class App: + nm = Mock({'getNodeByServer': None}) + storage_node = None + pt = None + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + uuid = self.getUUID() + client_handler.handleAcceptNodeIdentification(conn, None, CLIENT_NODE_TYPE, + uuid, '127.0.0.1', 10010, + 0, 0) + self.assertEquals(len(conn.mockGetNamedCalls('close')), 1) + self.assertEquals(app.storage_node, None) + self.assertEquals(app.pt, None) + + def test_masterAcceptNodeIdentification(self): + node = Mock({'setUUID': None}) + class App: + nm = Mock({'getNodeByServer': node}) + storage_node = None + pt = None + + def getQueue(self): + return None + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + uuid = self.getUUID() + client_handler.handleAcceptNodeIdentification(conn, None, MASTER_NODE_TYPE, + uuid, '127.0.0.1', 10010, + 10, 2) + self.assertEquals(len(conn.mockGetNamedCalls('close')), 0) + self.assertEquals(len(conn.mockGetNamedCalls('setUUID')), 1) + setUUID_call_list = node.mockGetNamedCalls('setUUID') + self.assertEquals(len(setUUID_call_list), 1) + self.assertEquals(setUUID_call_list[0].getParam(0), uuid) + self.assertEquals(app.storage_node, None) + self.assertTrue(app.pt is not None) + + def test_storageAcceptNodeIdentification(self): + node = Mock({'setUUID': None}) + class App: + nm = Mock({'getNodeByServer': node}) + storage_node = None + pt = None + + def getQueue(self): + return None + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + uuid = self.getUUID() + client_handler.handleAcceptNodeIdentification(conn, None, STORAGE_NODE_TYPE, + uuid, '127.0.0.1', 10010, + 0, 0) + self.assertEquals(len(conn.mockGetNamedCalls('close')), 0) + self.assertEquals(len(conn.mockGetNamedCalls('setUUID')), 1) + setUUID_call_list = node.mockGetNamedCalls('setUUID') + self.assertEquals(len(setUUID_call_list), 1) + self.assertEquals(setUUID_call_list[0].getParam(0), uuid) + self.assertTrue(app.storage_node is node) + self.assertEquals(app.pt, None) + + def _testHandleUnexpectedPacketCalledWithMedhod(self, client_handler, method, args=(), kw=()): + # Monkey-patch handleUnexpectedPacket to check if it is called + call_list = [] + def ClientHandler_handleUnexpectedPacket(self, conn, packet): + call_list.append((conn, packet)) + original_handleUnexpectedPacket = client_handler.__class__.handleUnexpectedPacket + client_handler.__class__.handleUnexpectedPacket = ClientHandler_handleUnexpectedPacket + try: + method(*args, **dict(kw)) + finally: + # Restore original method + client_handler.__class__.handleUnexpectedPacket = original_handleUnexpectedPacket + # Check that packet was notified as unexpected. + self.assertEquals(len(call_list), 1) + # Return call_list in case caller wants to do extra checks on it. + return call_list + + # Master node handler + def test_initialAnswerPrimaryMaster(self): + client_handler = ClientEventHandler(None, self.getDispatcher()) + conn = Mock({'getUUID': None}) + call_list = self._testHandleUnexpectedPacketCalledWithMedhod( + client_handler, client_handler.handleAnswerPrimaryMaster, + args=(conn, None, 0, [])) + # Nothing else happened, or a raise would have happened (app is None, + # and it's where things would happen) + self.assertEquals(call_list[0], (conn, None)) + + def test_nonMasterAnswerPrimaryMaster(self): + for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE): + node = Mock({'getNodeType': node_type}) + class App: + nm = Mock({'getNodeByUUID': node, 'getNodeByServer': None, 'add': None}) + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + client_handler.handleAnswerPrimaryMaster(conn, None, 0, []) + # Check that nothing happened + self.assertEqual(len(app.nm.mockGetNamedCalls('getNodeByServer')), 0) + self.assertEqual(len(app.nm.mockGetNamedCalls('add')), 0) + + def test_unknownNodeAnswerPrimaryMaster(self): + node = Mock({'getNodeType': MASTER_NODE_TYPE}) + class App: + nm = Mock({'getNodeByUUID': node, 'getNodeByServer': None, 'add': None}) + # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID() + primary_master_node = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + test_master_list = [('127.0.0.1', 10010, self.getUUID())] + client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list) + # Check that yet-unknown master node got added + getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer') + add_call_list = app.nm.mockGetNamedCalls('add') + self.assertEqual(len(getNodeByServer_call_list), 1) + self.assertEqual(len(add_call_list), 1) + address, port, test_uuid = test_master_list[0] + getNodeByServer_call = getNodeByServer_call_list[0] + add_call = add_call_list[0] + self.assertEquals((address, port), getNodeByServer_call.getParam(0)) + node_instance = add_call.getParam(0) + self.assertEquals(test_uuid, node_instance.getUUID()) + # Check that primary master was not updated (it is not known yet, + # hence INVALID_UUID in call). + self.assertEquals(app.primary_master_node, None) + + def test_knownNodeUnknownUUIDNodeAnswerPrimaryMaster(self): + node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': None, 'setUUID': None}) + class App: + nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None}) + # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID() + # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port) + primary_master_node = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + test_node_uuid = self.getUUID() + test_master_list = [('127.0.0.1', 10010, test_node_uuid)] + client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list) + # Check that known master node did not get added + getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer') + add_call_list = app.nm.mockGetNamedCalls('add') + self.assertEqual(len(getNodeByServer_call_list), 1) + self.assertEqual(len(add_call_list), 0) + # Check that node UUID got updated + setUUID_call_list = node.mockGetNamedCalls('setUUID') + self.assertEqual(len(setUUID_call_list), 1) + self.assertEqual(setUUID_call_list[0].getParam(0), test_node_uuid) + # Check that primary master was not updated (it is not known yet, + # hence INVALID_UUID in call). + self.assertEquals(app.primary_master_node, None) + + def test_knownNodeKnownUUIDNodeAnswerPrimaryMaster(self): + test_node_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None}) + class App: + nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None}) + # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID() + # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port) + primary_master_node = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + test_master_list = [('127.0.0.1', 10010, test_node_uuid)] + client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list) + # Check that known master node did not get added + getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer') + add_call_list = app.nm.mockGetNamedCalls('add') + self.assertEqual(len(getNodeByServer_call_list), 1) + self.assertEqual(len(add_call_list), 0) + # Check that node UUID was untouched + # XXX: should we just check that there was either no call or a call + # with same uuid, or enforce no call ? Here we enforce no call just + # because it's what implementation does. + setUUID_call_list = node.mockGetNamedCalls('setUUID') + self.assertEqual(len(setUUID_call_list), 0) + # Check that primary master was not updated (it is not known yet, + # hence INVALID_UUID in call). + self.assertEquals(app.primary_master_node, None) + + # TODO: test known node, known but different uuid (not detected in code, + # desired behaviour unknown) + + def test_alreadyDifferentPrimaryAnswerPrimaryMaster(self): + test_node_uuid = self.getUUID() + test_primary_node_uuid = test_node_uuid + while test_primary_node_uuid == test_node_uuid: + test_primary_node_uuid = self.getUUID() + test_primary_master_node = Mock({'getUUID': test_primary_node_uuid}) + node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None}) + class App: + nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None}) + # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID() + # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port) + primary_master_node = test_primary_master_node + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + # If primary master is already set *and* is not given primary master + # handle call raises. + # XXX: is it acceptable for a handle call to raise without any proper fallback ? + self.assertRaises(ElectionFailure, client_handler.handleAnswerPrimaryMaster, + conn, None, test_node_uuid, []) + + def test_alreadySamePrimaryAnswerPrimaryMaster(self): + test_node_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None}) + class App: + nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None}) + primary_master_node = node + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + client_handler.handleAnswerPrimaryMaster(conn, None, test_node_uuid, []) + # Check that primary node is (still) node. + self.assertTrue(app.primary_master_node is node) + + def test_unknownNewPrimaryAnswerPrimaryMaster(self): + test_node_uuid = self.getUUID() + test_primary_node_uuid = test_node_uuid + while test_primary_node_uuid == test_node_uuid: + test_primary_node_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None}) + class App: + nm = Mock({'getNodeByUUID': ReturnValues(node, None), 'getNodeByServer': node, 'add': None}) + # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID(), then test_primary_node_uuid + primary_master_node = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + client_handler.handleAnswerPrimaryMaster(conn, None, test_primary_node_uuid, []) + # Check that primary node was not updated. + self.assertTrue(app.primary_master_node is None) + + def test_AnswerPrimaryMaster(self): + test_node_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None}) + class App: + nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None}) + # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID() + # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port) + primary_master_node = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + test_master_list = [('127.0.0.1', 10010, test_node_uuid)] + client_handler.handleAnswerPrimaryMaster(conn, None, test_node_uuid, test_master_list) + # Check that primary master was updated to known node + self.assertTrue(app.primary_master_node is node) + + def test_initialSendPartitionTable(self): + client_handler = ClientEventHandler(None, self.getDispatcher()) + conn = Mock({'getUUID': None}) + call_list = self._testHandleUnexpectedPacketCalledWithMedhod( + client_handler, client_handler.handleSendPartitionTable, + args=(conn, None, None, None)) + self.assertEquals(call_list[0], (conn, None)) + + def test_nonMasterSendPartitionTable(self): + for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE): + node = Mock({'getNodeType': node_type}) + class App: + nm = Mock({'getNodeByUUID': node}) + pt = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + client_handler.handleSendPartitionTable(conn, None, 0, []) + # Check that nothing happened + self.assertTrue(app.pt is None) + + def test_newSendPartitionTable(self): + node = Mock({'getNodeType': MASTER_NODE_TYPE}) + test_ptid = 0 + class App: + nm = Mock({'getNodeByUUID': node}) + pt = Mock({'clear': None}) + ptid = test_ptid + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + client_handler.handleSendPartitionTable(conn, None, test_ptid + 1, []) + # Check that partition table got cleared and ptid got updated + self.assertEquals(app.ptid, test_ptid + 1) + self.assertEquals(len(app.pt.mockGetNamedCalls('clear')), 1) + + def test_unknownNodeSendPartitionTable(self): + test_node = Mock({'getNodeType': MASTER_NODE_TYPE}) + test_ptid = 0 + class App: + nm = Mock({'getNodeByUUID': ReturnValues(test_node, None), 'add': None}) + pt = Mock({'setCell': None}) + ptid = test_ptid + test_storage_uuid = self.getUUID() + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + # TODO: use realistic values + test_row_list = [(0, [(test_storage_uuid, 0)])] + client_handler.handleSendPartitionTable(conn, None, test_ptid, test_row_list) + # Check that node got created + add_call_list = app.nm.mockGetNamedCalls('add') + self.assertEquals(len(add_call_list), 1) + created_node = add_call_list[0].getParam(0) + self.assertEqual(created_node.getUUID(), test_storage_uuid) + # Check that partition table cell got added + setCell_call_list = app.pt.mockGetNamedCalls('setCell') + self.assertEquals(len(setCell_call_list), 1) + offset = setCell_call_list[0].getParam(0) + node = setCell_call_list[0].getParam(1) + state = setCell_call_list[0].getParam(2) + self.assertEqual(offset, test_row_list[0][0]) + self.assertTrue(node is created_node) + self.assertEqual(state, test_row_list[0][1][0][1]) + + def test_knownNodeSendPartitionTable(self): + test_node = Mock({'getNodeType': MASTER_NODE_TYPE}) + test_ptid = 0 + class App: + nm = Mock({'getNodeByUUID': test_node, 'add': None}) + pt = Mock({'setCell': None}) + ptid = test_ptid + test_storage_uuid = self.getUUID() + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + # TODO: use realistic values + test_row_list = [(0, [(test_storage_uuid, 0)])] + client_handler.handleSendPartitionTable(conn, None, test_ptid, test_row_list) + # Check that node did not get created + self.assertEquals(len(app.nm.mockGetNamedCalls('add')), 0) + # Check that partition table cell got added + setCell_call_list = app.pt.mockGetNamedCalls('setCell') + self.assertEquals(len(setCell_call_list), 1) + offset = setCell_call_list[0].getParam(0) + node = setCell_call_list[0].getParam(1) + state = setCell_call_list[0].getParam(2) + self.assertEqual(offset, test_row_list[0][0]) + self.assertTrue(node is test_node) + self.assertEqual(state, test_row_list[0][1][0][1]) + + def test_initialNotifyNodeInformation(self): + client_handler = ClientEventHandler(None, self.getDispatcher()) + conn = Mock({'getUUID': None}) + call_list = self._testHandleUnexpectedPacketCalledWithMedhod( + client_handler, client_handler.handleNotifyNodeInformation, + args=(conn, None, None)) + self.assertEquals(call_list[0], (conn, None)) + + def test_nonMasterNotifyNodeInformation(self): + for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE): + test_master_uuid = self.getUUID() + node = Mock({'getNodeType': node_type}) + class App: + nm = Mock({'getNodeByUUID': node}) + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_master_uuid) + client_handler.handleNotifyNodeInformation(conn, None, None) + # If handleNotifyNodeInformation tries to read node_list + # parameter, it will fail (it is not a list). + # This test assumes that this failure will cause an exception to + # be raised, making it fail. + + def test_nonIterableParameterRaisesNotifyNodeInformation(self): + # XXX: this test is here for sanity self-check: it verifies the + # assumption described in test_nonMasterNotifyNodeInformation + # by making a valid call with a non-iterable parameter given as + # node_list value. + test_master_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE}) + class App: + nm = Mock({'getNodeByUUID': node}) + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_master_uuid) + self.assertRaises(TypeError, client_handler.handleNotifyNodeInformation, + conn, None, None) + + def _testNotifyNodeInformation(self, test_node, getNodeByServer=None, getNodeByUUID=MARKER): + invalid_uid_test_node = (test_node[0], test_node[1], test_node[2] + 1, + INVALID_UUID, test_node[4]) + test_node_list = [test_node, invalid_uid_test_node] + test_master_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE}) + if getNodeByUUID is not MARKER: + getNodeByUUID = ReturnValues(node, getNodeByUUID) + class App: + nm = Mock({'getNodeByUUID': getNodeByUUID, + 'getNodeByServer': getNodeByServer, + 'add': None, + 'remove': None}) + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_master_uuid) + client_handler.handleNotifyNodeInformation(conn, None, test_node_list) + # Return nm so caller can check handler actions. + return app.nm + + def test_unknownMasterNotifyNodeInformation(self): + raise NotImplementedError + + def test_knownMasterNotifyNodeInformation(self): + raise NotImplementedError + + def test_unknownStorageNotifyNodeInformation(self): + # TODO: use realistic value for node state + test_node = (STORAGE_NODE_TYPE, '127.0.0.1', 10010, self.getUUID(), + 0) + nm = self._testNotifyNodeInformation(test_node, getNodeByUUID=None) + # Check that node got added + add_call_list = nm.mockGetNamedCalls('add') + self.assertEqual(len(add_call_list), 1) + added_node = add_call_list[0].getParam(0) + # XXX: this test does not check that node state got updated. + # This is because there would be no way to tell the difference between + # an updated state and default state if they are the same value (we + # don't control node class/instance here) + # Likewise for server address and node uuid. + + def test_knownStorageNotifyNodeInformation(self): + # TODO: use realistic value for node state + node = Mock({'setState': None, 'setServer': None}) + test_node = (STORAGE_NODE_TYPE, '127.0.0.1', 10010, self.getUUID(), + 0) + nm = self._testNotifyNodeInformation(test_node, getNodeByUUID=node) + # Check that no node got added + self.assertEqual(len(nm.mockGetNamedCalls('add')), 0) + # Check that server address got set + setServer_call_list = node.mockGetNamedCalls('setServer') + self.assertEqual(len(setServer_call_list), 1) + self.assertEqual(setServer_call_list[0].getParam(0), + (test_node[1], test_node[2])) + # Check node state has been updated + setState_call_list = node.mockGetNamedCalls('setState') + self.assertEqual(len(setState_call_list), 1) + self.assertEqual(setState_call_list[0].getParam(0), test_node[4]) + + def test_initialNotifyPartitionChanges(self): + class App: + nm = None + pt = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = Mock({'getUUID': None}) + call_list = self._testHandleUnexpectedPacketCalledWithMedhod( + client_handler, client_handler.handleNotifyPartitionChanges, + args=(conn, None, None, None)) + self.assertEquals(call_list[0], (conn, None)) + + def test_nonMasterNotifyPartitionChanges(self): + for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE): + test_master_uuid = self.getUUID() + node = Mock({'getNodeType': node_type, 'getUUID': test_master_uuid}) + class App: + nm = Mock({'getNodeByUUID': node}) + pt = None + primary_master_node = node + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_master_uuid) + client_handler.handleNotifyPartitionChanges(conn, None, 0, []) + # Check that nothing happened + self.assertTrue(app.pt is None) + + def test_noPrimaryMasterNotifyPartitionChanges(self): + node = Mock({'getNodeType': MASTER_NODE_TYPE}) + class App: + nm = Mock({'getNodeByUUID': node}) + pt = None + primary_master_node = None + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection() + client_handler.handleNotifyPartitionChanges(conn, None, 0, []) + # Check that nothing happened + self.assertTrue(app.pt is None) + + def test_nonPrimaryMasterNotifyPartitionChanges(self): + test_master_uuid = self.getUUID() + test_sender_uuid = test_master_uuid + while test_sender_uuid == test_master_uuid: + test_sender_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE}) + test_master_node = Mock({'getUUID': test_master_uuid}) + class App: + nm = Mock({'getNodeByUUID': node}) + pt = None + primary_master_node = test_master_node + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_sender_uuid) + client_handler.handleNotifyPartitionChanges(conn, None, 0, []) + # Check that nothing happened + self.assertTrue(app.pt is None) + + def test_ignoreOutdatedPTIDNotifyPartitionChanges(self): + test_master_uuid = self.getUUID() + node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid}) + test_ptid = 1 + class App: + nm = Mock({'getNodeByUUID': node}) + pt = None + primary_master_node = node + ptid = test_ptid + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_master_uuid) + client_handler.handleNotifyPartitionChanges(conn, None, test_ptid, []) + # Check that nothing happened + self.assertTrue(app.pt is None) + self.assertEquals(app.ptid, test_ptid) + + def test_unknownNodeNotifyPartitionChanges(self): + test_master_uuid = self.getUUID() + test_node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid}) + test_ptid = 1 + class App: + nm = Mock({'getNodeByUUID': ReturnValues(test_node, None)}) + pt = Mock({'setCell': None}) + primary_master_node = test_node + ptid = test_ptid + uuid = None # XXX: Is it really needed ? + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_master_uuid) + test_storage_uuid = self.getUUID() + # TODO: use realistic values + test_cell_list = [(0, test_storage_uuid, 0)] + client_handler.handleNotifyPartitionChanges(conn, None, test_ptid + 1, test_cell_list) + # Check that a new node got added + add_call_list = app.nm.mockGetNamedCalls('add') + self.assertEqual(len(add_call_list), 1) + added_node = add_call_list[0].getParam(0) + # Check that partition got updated + self.assertEqual(app.ptid, test_ptid + 1) + setCell_call_list = app.pt.mockGetNamedCalls('setCell') + self.assertEqual(len(setCell_call_list), 1) + offset = setCell_call_list[0].getParam(0) + node = setCell_call_list[0].getParam(1) + state = setCell_call_list[0].getParam(2) + self.assertEqual(offset, test_cell_list[0][0]) + self.assertTrue(node is added_node) + self.assertEqual(state, test_cell_list[0][2]) + + # TODO: confirm condition under which an unknown node should be added with a TEMPORARILY_DOWN_STATE (implementation is unclear) + + def test_knownNodeNotifyPartitionChanges(self): + test_master_uuid = self.getUUID() + test_node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid}) + test_ptid = 1 + class App: + nm = Mock({'getNodeByUUID': test_node, 'add': None}) + pt = Mock({'setCell': None}) + primary_master_node = test_node + ptid = test_ptid + app = App() + client_handler = ClientEventHandler(app, self.getDispatcher()) + conn = self.getConnection(uuid=test_master_uuid) + test_storage_uuid = self.getUUID() + # TODO: use realistic values + test_cell_list = [(0, test_storage_uuid, 0)] + client_handler.handleNotifyPartitionChanges(conn, None, test_ptid + 1, test_cell_list) + # Check that no node got added + self.assertEqual(len(app.nm.mockGetNamedCalls('add')), 0) + # Check that partition got updated + self.assertEqual(app.ptid, test_ptid + 1) + setCell_call_list = app.pt.mockGetNamedCalls('setCell') + self.assertEqual(len(setCell_call_list), 1) + offset = setCell_call_list[0].getParam(0) + node = setCell_call_list[0].getParam(1) + state = setCell_call_list[0].getParam(2) + self.assertEqual(offset, test_cell_list[0][0]) + self.assertTrue(node is test_node) + self.assertEqual(state, test_cell_list[0][2]) + + def test_AnswerNewTID(self): + class App: + tid = None + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + test_tid = 1 + client_handler.handleAnswerNewTID(conn, None, test_tid) + self.assertEquals(app.tid, test_tid) + + def test_NotifyTransactionFinished(self): + class App: + tid = 1 + txn_finished = None + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + client_handler.handleNotifyTransactionFinished(conn, None, 1) + self.assertEquals(app.txn_finished, 1) + # TODO: decide what to do when non-current transaction is notified as finished, and test that behaviour + + def test_InvalidateObjects(self): + class App: + def _cache_lock_acquire(self): + pass + + def _cache_lock_release(self): + pass + + _db = Mock({'invalidate': None}) + mq_cache = Mock({'__delitem__': None}) + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + test_tid = 1 + test_oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02'] + client_handler.handleInvalidateObjects(conn, None, test_oid_list[:], test_tid) + # 'invalidate' is called just once + invalidate_call_list = app._db.mockGetNamedCalls('invalidate') + self.assertEquals(len(invalidate_call_list), 1) + invalidate_call = invalidate_call_list[0] + invalidate_tid = invalidate_call.getParam(0) + self.assertEquals(invalidate_tid, test_tid) + invalidate_oid_dict = invalidate_call.getParam(1) + self.assertEquals(len(invalidate_oid_dict), len(test_oid_list)) + self.assertEquals(set(invalidate_oid_dict), set(test_oid_list)) + self.assertEquals(set(invalidate_oid_dict.itervalues()), set([test_tid])) + # '__delitem__' is called once per invalidated object + delitem_call_list = app.mq_cache.mockGetNamedCalls('__delitem__') + self.assertEquals(len(delitem_call_list), len(test_oid_list)) + oid_list = [x.getParam(0) for x in delitem_call_list] + self.assertEquals(set(oid_list), set(test_oid_list)) + + def test_AnswerNewOIDs(self): + class App: + new_oid_list = [] + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + test_oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02'] + client_handler.handleAnswerNewOIDs(conn, None, test_oid_list[:]) + self.assertEquals(set(app.new_oid_list), set(test_oid_list)) + + def test_StopOperation(self): + raise NotImplementedError + + # Storage node handler + + def test_AnswerObject(self): + class FakeLocal: + asked_object = () + class App: + local_var = FakeLocal() + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + # TODO: use realistic values + test_object_data = ('\x00\x00\x00\x00\x00\x00\x00\x01', 0, 0, 0, 0, 'test') + client_handler.handleAnswerObject(conn, None, *test_object_data) + self.assertEquals(app.local_var.asked_object, test_object_data) + + def _testAnswerStoreObject(self, app, conflicting, oid, serial): + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + client_handler.handleAnswerStoreObject(conn, None, conflicting, oid, serial) + + def test_conflictingAnswerStoreObject(self): + class App: + txn_object_stored = (0, 0) + app = App() + test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01' + test_serial = 1 + self._testAnswerStoreObject(app, 1, test_oid, test_serial) + self.assertEqual(app.txn_object_stored, (-1, test_serial)) + + def test_AnswerStoreObject(self): + class App: + txn_object_stored = (0, 0) + app = App() + test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01' + test_serial = 1 + self._testAnswerStoreObject(app, 0, test_oid, test_serial) + self.assertEqual(app.txn_object_stored, (test_oid, test_serial)) + + def test_AnswerStoreTransaction(self): + class App: + tid = 10 + txn_voted = 0 + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + test_tid = 10 + client_handler.handleAnswerStoreTransaction(conn, None, test_tid) + self.assertEquals(app.txn_voted, 1) + # TODO: test handleAnswerObject with test_tid not matching app.tid (not handled in program) + + def test_AnswerTransactionInformation(self): + class FakeLocal: + txn_info = {} + class App: + local_var = FakeLocal() + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + tid = '\x00\x00\x00\x00\x00\x00\x00\x01' # TODO: use a more realistic tid + user = 'bar' + desc = 'foo' + ext = 0 # XXX: unused in implementation + oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02'] + client_handler.handleAnswerTransactionInformation(conn, None, tid, user, desc, ext, oid_list[:]) + stored_dict = app.local_var.txn_info + # TODO: test 'time' value ? + self.assertEquals(stored_dict['user_name'], user) + self.assertEquals(stored_dict['description'], desc) + self.assertEquals(stored_dict['id'], tid) + self.assertEquals(stored_dict['oids'], oid_list) + + def test_AnswerObjectHistory(self): + class FakeLocal: + history = (0, []) + class App: + local_var = FakeLocal() + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01' + # TODO: use realistic values + test_history_list = [(1, 2), (3, 4)] + client_handler.handleAnswerObjectHistory(conn, None, test_oid, test_history_list[:]) + oid, history = app.local_var.history + self.assertEquals(oid, test_oid) + self.assertEquals(len(history), len(test_history_list)) + self.assertEquals(set(history), set(test_history_list)) + + def test_OidNotFound(self): + class FakeLocal: + asked_object = 0 + history = 0 + class App: + local_var = FakeLocal() + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + client_handler.handleOidNotFound(conn, None, None) + self.assertEquals(app.local_var.asked_object, -1) + self.assertEquals(app.local_var.history, -1) + + def test_TidNotFound(self): + class FakeLocal: + txn_info = 0 + class App: + local_var = FakeLocal() + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + client_handler.handleTidNotFound(conn, None, None) + self.assertEquals(app.local_var.txn_info, -1) + + def test_AnswerTIDs(self): + class FakeLocal: + node_tids = {} + class App: + local_var = FakeLocal() + app = App() + dispatcher = self.getDispatcher() + client_handler = ClientEventHandler(app, dispatcher) + conn = self.getConnection() + test_tid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02'] + client_handler.handleAnswerTIDs(conn, None, test_tid_list[:]) + stored_tid_list = [] + for tid_list in app.local_var.node_tids.itervalues(): + stored_tid_list.extend(tid_list) + self.assertEquals(len(stored_tid_list), len(test_tid_list)) + self.assertEquals(set(stored_tid_list), set(test_tid_list)) + +if __name__ == '__main__': + unittest.main() + -- 2.30.9