Boris Stepanenko
authored
Before, we used to modify box.cfg.replication to reproduce network problems in our test. This worked fine in most situations, but doesn't work in others: when instance gets disconnected by modifying box.cfg.replication, it closes its connection immediately (in terms of realtime), and this is noticed almost immediately by its neighbours in replica set (because they receive EOF). This made it impossible to test some things, that rely on specific timeouts in our code (e.g. strict fencing). This commits adds helper, which acts as UNIX socket proxy, and can block connection transparently for tarantool instances. It makes it possible to write some tests, that were not possible before. It is also possible to inject arbitrary packets between instance, which are interconnected via proxy. Usage: +-------------------+ |tarantool server 1 | +-------------------+ | | | .-----------------. ( /tmp/test-out ) `-----------------' | | | +-------------------+ | proxy | +-------------------+ | | | .-----------------. +-------( /tmp/test-in )--------+ | `-----------------' | | | | | | | +-------------------+ +-------------------+ |tarantool server 2 | |tarantool server 3 | +-------------------+ +-------------------+ tarantool server 1 init.lua: box.cfg{listen = '/tmp/test-out'} box.once("schema", function() box.schema.user.grant('guest', 'super') end) tarantool server 2 and tarantool server 3 init.lua: box.cfg{replication = '/tmp/test-in'} proxy init.lua: -- Import proxy helper Proxy = require('test.luatest_helpers.proxy.proxy') -- Create proxy, which will (when started) listen on client_socket_path -- and accept connection when client tries to connect. The accepted -- socket connection is then passed to new Connection instance. proxy = Proxy:new({ -- Path to UNIX socket, where proxy will await new connections. client_socket_path = '/tmp/test-in', -- Path to UNIX socket where tarantool server is listening. server_socket_path = '/tmp/test-out', -- Table, describing how to process client socket. Optional. -- Defaults used and described: process_client = { -- function(connection) which, if not nil, will be called once -- before client socket processing loop. pre = nil, -- function(connection, data) which, if not nil, will be called -- in loop, when new data is received from client socket. -- Connection.forward_to_server(connection, data) will: -- 1) Connect server socket to server_socket_path, if server -- socket is not connected. -- 2) Write data to server socket, if connected and writable. func = Connection.forward_to_server, -- function(connection) which, if not nil, will be called once -- after client socket processing loop. -- Connection.close_client_socket(connection) will shutdown and -- close client socket, if it is connected. post = Connection.close_client_socket, }, -- Table, describing how to process server socket. Optional. -- Defaults used and described: process_server = { -- function(connection) which, if not nil, will be called once -- before server socket processing loop. pre = nil, -- function(connection, data) which, if not nil, will be called -- in loop, when new data is received from server socket. -- Connection.forward_to_client(connection, data) will write data -- to client socket, if it is connected and writable func = Connection.forward_to_client, -- function(connection) which, if not nil, will be called once -- after server socket processing loop. -- Connection.close_server_socket(connection) will shutdown and -- close server socket, if it is connected. post = Connection.close_server_socket, } }) -- Bind client socket (defined by proxy.client_socket_path) and start -- accepting connections on it in a new fiber. If opts.force is set to -- true, it will remove proxy.client_socket_path file before binding to -- it. After proxy is started it will accept client connections and -- create Connection instance for each connection. proxy:start({force = false}) -- Stop accepting new connetions on client socket and join the fiber, -- created by proxy:start(), and close client socket. Also stop all -- active connections (see Connection:stop()). proxy:stop() -- Pause accepting new connections and pause all active connections (see -- Connection:pause()). proxy:pause() -- Resume accepting new connections and resume all paused connections -- (see Connection:resume()) proxy:resume() -- Connection class: Connection:new({ { -- Socket which is already created (by Proxy class for example). -- Optional, may be nil. client_socket = '?table', -- Path to connect server socket to. Will try to connect on -- initialization, and in Connection.forward_to_server. -- Can connect manually by calling -- Connection:connect_server_socket(). server_socket_path = 'string', -- See Proxy:new() process_client = '?table', -- See Proxy:new() process_server = '?table', }, }) -- Start processing client socket, using functions from -- Connection:process_client. Connection:start() -- Connect server socket to Connection.server_socket_path (if not -- connected already). Start processing server socket, if successfully -- connected (using functions from Connection.process_server). Connection:connect_server_socket() -- Pause processing packets (both incoming from client socket and server -- socket). Connection:pause() -- Resume processing packets (both incoming from client socket and -- server socket). Connection:resume() -- Close server socket, if open. Connection:close_server_socket() -- Close client socket, if open. Connection:close_client_socket() -- Close client and server sockets, if open, and wait for processing -- fibers to die. Connection:stop() NO_DOC=test helpers NO_CHANGELOG=test helpers