Cleanup utp_test and make the code more flexible to change amounts (#1972)

This commit is contained in:
Kim De Mey 2024-01-15 17:29:29 +01:00 committed by GitHub
parent fb2aa1b6f9
commit 2df9e2b7b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 114 deletions

View File

@ -1,9 +1,9 @@
# uTP testing infrastructure # uTP testing infrastructure
Testing infrastructure which enables to test uTP implementation over different Testing infrastructure which enables to test uTP implementation over different
network conditions on local machine. network conditions on a local machine.
Highly based on tools developed to test quic protocol: Uses following tools developed to test the QUIC protocol:
[quic-interop-runner](https://github.com/marten-seemann/quic-interop-runner) [quic-interop-runner](https://github.com/marten-seemann/quic-interop-runner)
@ -11,19 +11,20 @@ Highly based on tools developed to test quic protocol:
## Prerequisities ## Prerequisities
- Machine with docker installed - Machine with Docker installed
- nimbus-eth1 set-up to run `make utp_test` - nimbus-eth1 set-up to run `make utp_test`
## How it works ## How it works
Test setup uses docker compose to start 3 docker containers: Test setup uses Docker Compose to start 3 Docker containers:
- client - which is instance of uTP test app - uTP client - which is an instance of the `utp_test_app`
- server - which is instance of uTP test app - uTP server - which is an instance of the `utp_test_app`
- sim - which is instance with ns3 network simulator with several pre-compiled scenarios - sim - which is an instance of the ns3 network simulator with several pre-compiled scenarios
The networking is setup in such way that network traffic is routed from client to server The networking is set up in such way that network traffic is routed from client to server
and server to client thorugh sim which decideds what to do with flowing packets and server to client through the simulator.
The simulator decides what to do with packets passing through based on the selected scneario.
Explanation from [quic-network-simulator](https://github.com/marten-seemann/quic-network-simulator): Explanation from [quic-network-simulator](https://github.com/marten-seemann/quic-network-simulator):
@ -38,9 +39,9 @@ simulation sits in the middle and forwards packets between `leftnet` and
## Practicalities ## Practicalities
For now process is semi-manual (TODO automate this as much as possible) For now the process is semi-manual (TODO automate this as much as possible)
To run integration testing scenarios with different network conditions To run integration testing scenarios with different network conditions:
``` ```
1. cd nimbus-eth1/ 1. cd nimbus-eth1/

View File

@ -1,6 +1,6 @@
version: "3.5" version: "3.5"
# TODO Add possibility to configure ports by env vcariables # TODO Add possibility to configure ports by env variables
services: services:
sim: sim:
# TODO for now we are using simulator which is also used in quic, ultimatly # TODO for now we are using simulator which is also used in quic, ultimatly
@ -14,7 +14,7 @@ services:
- ./logs/sim:/logs - ./logs/sim:/logs
environment: environment:
- SCENARIO=$SCENARIO - SCENARIO=$SCENARIO
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
expose: expose:
- "57832" - "57832"
@ -30,8 +30,6 @@ services:
# TODO add building of docker images based on variable # TODO add building of docker images based on variable
# build: ./$SERVER # build: ./$SERVER
image: test-utp image: test-utp
# this arg will go to container entry point
command: "--udp-listen-address=193.167.100.100 --rpc-listen-address=0.0.0.0 --udp-port=9041 --rpc-port=9041"
container_name: server container_name: server
hostname: server hostname: server
stdin_open: true stdin_open: true
@ -43,7 +41,7 @@ services:
- CLIENT_PARAMS=--udp-listen-address=193.167.100.100 --rpc-listen-address=0.0.0.0 --udp-port=9041 --rpc-port=9041 - CLIENT_PARAMS=--udp-listen-address=193.167.100.100 --rpc-listen-address=0.0.0.0 --udp-port=9041 --rpc-port=9041
depends_on: depends_on:
- sim - sim
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- "9041:9041" - "9041:9041"
@ -60,7 +58,6 @@ services:
client: client:
# build: ./$CLIENT # build: ./$CLIENT
image: test-utp image: test-utp
command: "--udp-listen-address=193.167.0.100 --rpc-listen-address=0.0.0.0 --udp-port=9042 --rpc-port=9042"
container_name: client container_name: client
hostname: client hostname: client
stdin_open: true stdin_open: true
@ -72,7 +69,7 @@ services:
- CLIENT_PARAMS=--udp-listen-address=193.167.0.100 --rpc-listen-address=0.0.0.0 --udp-port=9042 --rpc-port=9042 - CLIENT_PARAMS=--udp-listen-address=193.167.0.100 --rpc-listen-address=0.0.0.0 --udp-port=9042 --rpc-port=9042
depends_on: depends_on:
- sim - sim
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- "9042:9042" - "9042:9042"

View File

@ -1,5 +1,5 @@
# Nimbus # Fluffy
# Copyright (c) 2022-2023 Status Research & Development GmbH # Copyright (c) 2022-2024 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -10,13 +10,13 @@ import
unittest2, testutils, chronos, unittest2, testutils, chronos,
json_rpc/rpcclient, stew/byteutils, json_rpc/rpcclient, stew/byteutils,
eth/keys, eth/keys,
./utp_test_client ./utp_test_rpc_client
proc generateBytesHex(rng: var HmacDrbgContext, length: int): string = proc generateBytesHex(rng: var HmacDrbgContext, length: int): string =
rng.generateBytes(length).toHex() rng.generateBytes(length).toHex()
# Before running the test suite, there need to be two instances of the # Before running this test suite, there need to be two instances of the
# utp_test_app running under provided ports (9042, 9041). # utp_test_app running under the tested ports: 9042, 9041.
# Those could be launched locally by running either # Those could be launched locally by running either
# ./utp_test_app --udp-listen-address=127.0.0.1 --rpc-listen-address=0.0.0.0 --udp-port=9041 --rpc-port=9041 # ./utp_test_app --udp-listen-address=127.0.0.1 --rpc-listen-address=0.0.0.0 --udp-port=9041 --rpc-port=9041
# ./utp_test_app --udp-listen-address=127.0.0.1 --rpc-listen-address=0.0.0.0 --udp-port=9042 --rpc-port=9042 # ./utp_test_app --udp-listen-address=127.0.0.1 --rpc-listen-address=0.0.0.0 --udp-port=9042 --rpc-port=9042
@ -24,13 +24,15 @@ proc generateBytesHex(rng: var HmacDrbgContext, length: int): string =
# running from docker dir: # running from docker dir:
# 1. docker build -t test-utp --no-cache --build-arg BRANCH_NAME=branch-name . # 1. docker build -t test-utp --no-cache --build-arg BRANCH_NAME=branch-name .
# 2. SCENARIO="scenario name and params " docker-compose up # 2. SCENARIO="scenario name and params " docker-compose up
procSuite "uTP integration tests":
let rng = newRng()
let clientContainerAddress = "127.0.0.1"
let clientContainerPort = Port(9042)
let serverContainerAddress = "127.0.0.1" procSuite "uTP network simulator tests":
let serverContainerPort = Port(9041) const
clientContainerAddress = "127.0.0.1"
clientContainerPort = Port(9042)
serverContainerAddress = "127.0.0.1"
serverContainerPort = Port(9041)
let rng = newRng()
type type
FutureCallback[A] = proc (): Future[A] {.gcsafe, raises: [].} FutureCallback[A] = proc (): Future[A] {.gcsafe, raises: [].}
@ -56,9 +58,9 @@ procSuite "uTP integration tests":
raise canc raise canc
proc findServerConnection( proc findServerConnection(
connections: openArray[SKey], connections: openArray[SKey],
clientId: NodeId, clientId: NodeId,
clientConnectionId: uint16): Option[Skey] = clientConnectionId: uint16): Option[Skey] =
let conns: seq[SKey] = let conns: seq[SKey] =
connections.filter((key:Skey) => key.id == (clientConnectionId + 1) and connections.filter((key:Skey) => key.id == (clientConnectionId + 1) and
key.nodeId == clientId) key.nodeId == clientId)
@ -75,20 +77,20 @@ procSuite "uTP integration tests":
await client.connect(clientContainerAddress, clientContainerPort, false) await client.connect(clientContainerAddress, clientContainerPort, false)
await server.connect(serverContainerAddress, serverContainerPort, false) await server.connect(serverContainerAddress, serverContainerPort, false)
# we may need to retry few times if the simm is not ready yet # we may need to retry few times if the sim is not ready yet
let clientInfo = await repeatTillSuccess(() => client.discv5_nodeInfo(), 10) let clientInfo = await repeatTillSuccess(() => client.discv5_nodeInfo(), 10)
let serverInfo = await repeatTillSuccess(() => server.discv5_nodeInfo(), 10) let serverInfo = await repeatTillSuccess(() => server.discv5_nodeInfo(), 10)
# nodes need to have established session before the utp try # nodes need to have an established discv5 session before the uTP test
discard await repeatTillSuccess(() => client.discv5_ping(serverInfo.enr)) discard await repeatTillSuccess(() => client.discv5_ping(serverInfo.enr))
return (client, clientInfo, server, serverInfo) return (client, clientInfo, server, serverInfo)
asyncTest "Transfer 100k bytes of data over utp stream from client to server": asyncTest "100kb transfer from client to server":
let (client, clientInfo, server, serverInfo) = await setupTest() const amountOfBytes = 100_000
let numOfBytes = 100000
let let
(client, clientInfo, server, serverInfo) = await setupTest()
clientConnectionKey = await repeatTillSuccess(() => clientConnectionKey = await repeatTillSuccess(() =>
client.utp_connect(serverInfo.enr)) client.utp_connect(serverInfo.enr))
serverConnections = await repeatTillSuccess(() => serverConnections = await repeatTillSuccess(() =>
@ -99,24 +101,24 @@ procSuite "uTP integration tests":
check: check:
maybeServerConnectionKey.isSome() maybeServerConnectionKey.isSome()
let serverConnectionKey = maybeServerConnectionKey.unsafeGet()
let let
bytesToWrite = generateBytesHex(rng[], numOfBytes) serverConnectionKey = maybeServerConnectionKey.unsafeGet()
bytesToWrite = generateBytesHex(rng[], amountOfBytes)
writeRes = await client.utp_write(clientConnectionKey, bytesToWrite) writeRes = await client.utp_write(clientConnectionKey, bytesToWrite)
readData = await server.utp_read(serverConnectionKey, numOfBytes) dataRead = await server.utp_read(serverConnectionKey, amountOfBytes)
check: check:
writeRes == true writeRes == true
readData == bytesToWrite dataRead == bytesToWrite
asyncTest "Transfer 100k bytes of data over utp stream from server to client": asyncTest "100kb transfer from server to client":
# In classic uTP this would not be possible, as when uTP works over UDP the # In classic uTP this would not be possible, as when uTP works over UDP the
# client needs to transfer first, but when working over discv5 it should be # client needs to transfer first, but when working over discv5 it should be
# possible to transfer data from server to client from the start. # possible to transfer data from server to client from the start.
let (client, clientInfo, server, serverInfo) = await setupTest() const amountOfBytes = 100_000
let numOfBytes = 100000
let let
(client, clientInfo, server, serverInfo) = await setupTest()
clientConnectionKey = await repeatTillSuccess(() => clientConnectionKey = await repeatTillSuccess(() =>
client.utp_connect(serverInfo.enr)) client.utp_connect(serverInfo.enr))
serverConnections = await repeatTillSuccess(() => serverConnections = await repeatTillSuccess(() =>
@ -127,21 +129,23 @@ procSuite "uTP integration tests":
check: check:
maybeServerConnectionKey.isSome() maybeServerConnectionKey.isSome()
let serverConnectionKey = maybeServerConnectionKey.unsafeGet()
let let
bytesToWrite = generateBytesHex(rng[], numOfBytes) serverConnectionKey = maybeServerConnectionKey.unsafeGet()
bytesToWrite = generateBytesHex(rng[], amountOfBytes)
writeRes = await server.utp_write(serverConnectionKey, bytesToWrite) writeRes = await server.utp_write(serverConnectionKey, bytesToWrite)
readData = await client.utp_read(clientConnectionKey, numOfBytes) dataRead = await client.utp_read(clientConnectionKey, amountOfBytes)
check: check:
writeRes == true writeRes == true
readData == bytesToWrite dataRead == bytesToWrite
asyncTest "Multiple 10kb transfers from client to server":
const
amountOfBytes = 10_000
amountOfTransfers = 3
asyncTest "Multiple 10k bytes transfers over utp stream":
let (client, clientInfo, server, serverInfo) = await setupTest()
let numOfBytes = 10000
let let
(client, clientInfo, server, serverInfo) = await setupTest()
clientConnectionKey = await repeatTillSuccess(() => clientConnectionKey = await repeatTillSuccess(() =>
client.utp_connect(serverInfo.enr)) client.utp_connect(serverInfo.enr))
serverConnections = await repeatTillSuccess(() => serverConnections = await repeatTillSuccess(() =>
@ -154,71 +158,47 @@ procSuite "uTP integration tests":
let serverConnectionKey = maybeServerConnectionKey.unsafeGet() let serverConnectionKey = maybeServerConnectionKey.unsafeGet()
let var totalBytesToWrite: string
bytesToWrite = generateBytesHex(rng[], numOfBytes) for i in 0..<amountOfTransfers:
bytesToWrite1 = generateBytesHex(rng[], numOfBytes) let
bytesToWrite2 = generateBytesHex(rng[], numOfBytes) bytesToWrite = generateBytesHex(rng[], amountOfBytes)
writeRes = await client.utp_write(clientConnectionKey, bytesToWrite) writeRes = await client.utp_write(clientConnectionKey, bytesToWrite)
writeRes1 = await client.utp_write(clientConnectionKey, bytesToWrite1)
writeRes2 = await client.utp_write(clientConnectionKey, bytesToWrite2)
readData = await server.utp_read(serverConnectionKey, numOfBytes * 3)
let writtenData = join(@[bytesToWrite, bytesToWrite1, bytesToWrite2]) check writeRes == true
totalBytesToWrite.add(bytesToWrite)
check: let dataRead = await server.utp_read(
writeRes == true serverConnectionKey, amountOfBytes * amountOfTransfers)
writeRes1 == true
writeRes2 == true check dataRead == totalBytesToWrite
readData == writtenData
asyncTest "Multiple 10kb transfers over multiple sockets from client to server":
const
amountOfBytes = 10_000
amountOfSockets = 3
asyncTest "Handle mulitplie sockets over one utp server instance ":
let (client, clientInfo, server, serverInfo) = await setupTest() let (client, clientInfo, server, serverInfo) = await setupTest()
let numOfBytes = 10000
let
clientConnectionKey1 = await repeatTillSuccess(() =>
client.utp_connect(serverInfo.enr))
clientConnectionKey2 = await repeatTillSuccess(() =>
client.utp_connect(serverInfo.enr))
clientConnectionKey3 = await repeatTillSuccess(() =>
client.utp_connect(serverInfo.enr))
serverConnections = await repeatTillSuccess(() =>
server.utp_get_connections())
maybeServerConnectionKey1 = serverConnections.findServerConnection( var connectionKeys: seq[(SKey, SKey)]
clientInfo.nodeId, clientConnectionKey1.id) for i in 0..<amountOfSockets:
maybeServerConnectionKey2 = serverConnections.findServerConnection( let
clientInfo.nodeId, clientConnectionKey2.id) clientConnectionKey = await repeatTillSuccess(() =>
maybeServerConnectionKey3 = serverConnections.findServerConnection( client.utp_connect(serverInfo.enr))
clientInfo.nodeId, clientConnectionKey3.id) serverConnections = await repeatTillSuccess(() =>
server.utp_get_connections())
serverConnectionKeyRes = serverConnections.findServerConnection(
clientInfo.nodeId, clientConnectionKey.id)
check: check serverConnectionKeyRes.isSome()
maybeServerConnectionKey1.isSome()
maybeServerConnectionKey2.isSome()
maybeServerConnectionKey3.isSome()
let serverConnectionKey1 = maybeServerConnectionKey1.unsafeGet() connectionKeys.add((clientConnectionKey, serverConnectionKeyRes.unsafeGet()))
let serverConnectionKey2 = maybeServerConnectionKey2.unsafeGet()
let serverConnectionKey3 = maybeServerConnectionKey3.unsafeGet()
let for (clientConnectionKey, serverConnectionKey) in connectionKeys:
bytesToWrite1 = generateBytesHex(rng[], numOfBytes) let
bytesToWrite2 = generateBytesHex(rng[], numOfBytes) bytesToWrite = generateBytesHex(rng[], amountOfBytes)
bytesToWrite3 = generateBytesHex(rng[], numOfBytes) writeRes = await client.utp_write(clientConnectionKey, bytesToWrite)
dataRead = await server.utp_read(serverConnectionKey, amountOfBytes)
writeRes1 = await client.utp_write(clientConnectionKey1, bytesToWrite1) check:
writeRes2 = await client.utp_write(clientConnectionKey2, bytesToWrite2) writeRes == true
writeRes3 = await client.utp_write(clientConnectionKey3, bytesToWrite3) dataRead == bytesToWrite
readData1 = await server.utp_read(serverConnectionKey1, numOfBytes)
readData2 = await server.utp_read(serverConnectionKey2, numOfBytes)
readData3 = await server.utp_read(serverConnectionKey3, numOfBytes)
check:
writeRes1 == true
writeRes2 == true
writeRes3 == true
# all data was delivered to correct sockets
readData1 == bytesToWrite1
readData2 == bytesToWrite2
readData3 == bytesToWrite3