Skip to content

Commit b57f931

Browse files
authored
Use kad-dht discovery for nimlibp2p regression mesh (#298)
Use kad-dht discovery for nimlibp2p regression mesh
1 parent 1350491 commit b57f931

2 files changed

Lines changed: 85 additions & 8 deletions

File tree

src/deployments/experiments/libp2p/nimlibp2p.py

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import traceback
55
from typing import Literal
66

7-
from kubernetes.client import V1StatefulSet
8-
from pydantic import BaseModel, ConfigDict, NonNegativeFloat, NonNegativeInt
7+
from kubernetes.client import V1Probe, V1ServicePort, V1StatefulSet, V1TCPSocketAction
8+
from pydantic import BaseModel, ConfigDict, NonNegativeFloat, NonNegativeInt, model_validator
99

10+
from src.deployments.core.builders import ServiceBuilder
1011
from src.deployments.core.configs.container import Image
1112
from src.deployments.experiments.base_experiment import BaseExperiment
1213
from src.deployments.libp2p.bridge import Bridge
@@ -22,21 +23,37 @@
2223
logger = logging.getLogger(__name__)
2324

2425
Muxer = Literal["yamux", "quic", "mplex"]
26+
Discovery = Literal["static", "kad-dht"]
27+
28+
BOOTSTRAP_NAME = "bootstrap"
2529

2630

2731
class ExpConfig(BaseModel):
28-
num_nodes: NonNegativeInt = 30
32+
num_relay_nodes: NonNegativeInt = 30
2933
num_messages: NonNegativeInt = 20
3034
message_size_bytes: NonNegativeInt = 1000
3135
delay_cold_start: NonNegativeFloat = 60
3236
delay_after_publish: NonNegativeFloat = 1
3337
muxer: Muxer = "yamux"
3438
image: Image = Image(repo="pearsonwhite/dst-nimlibp2p-logging", tag="wip-4.2-1.16.0-amd")
39+
# "kad-dht" discovers peers through a bootstrap node; "static" uses the CONNECTTO dial.
40+
discovery: Discovery = "static"
3541
connect_to: NonNegativeInt = 10
42+
bootstrap_nodes: NonNegativeInt = 1
3643
network_delay: NonNegativeInt = 0
3744
network_jitter: NonNegativeInt = 0
3845
node_start_delay: NonNegativeInt = 60
3946

47+
@model_validator(mode="after")
48+
def _check_bootstrap_nodes(self):
49+
if self.discovery == "kad-dht" and self.bootstrap_nodes < 1:
50+
raise ValueError("kad-dht discovery requires bootstrap_nodes >= 1")
51+
return self
52+
53+
54+
def bootstrap_dns(namespace: str) -> str:
55+
return f"{BOOTSTRAP_NAME}.{namespace}.svc.cluster.local"
56+
4057

4158
def build_nodes(
4259
namespace: str,
@@ -47,18 +64,26 @@ def build_nodes(
4764
.with_libp2p_config(
4865
name="pod",
4966
namespace=namespace,
50-
num_nodes=params.num_nodes,
67+
num_nodes=params.num_relay_nodes,
5168
dns_searches=["nimp2p-service"],
5269
)
53-
.with_option(NimLibp2p.peers, params.num_nodes)
70+
.with_option(NimLibp2p.peers, params.num_relay_nodes)
5471
.with_option(NimLibp2p.self_trigger, True)
55-
.with_option(NimLibp2p.service, "nimp2p-service")
5672
.with_option(NimLibp2p.muxer, params.muxer)
57-
.with_option(NimLibp2p.connect_to, params.connect_to)
5873
.with_option(NimLibp2p.cold_start_delay, params.node_start_delay)
5974
.with_readiness_probe(readiness_probe_metrics())
6075
.with_image(params.image)
6176
)
77+
if params.discovery == "kad-dht":
78+
builder = (
79+
builder.with_option(NimLibp2p.node_role, "RoleNormal")
80+
.with_option(NimLibp2p.discovery, "kad-dht")
81+
.with_option(NimLibp2p.service, bootstrap_dns(namespace))
82+
)
83+
else:
84+
builder = builder.with_option(NimLibp2p.service, "nimp2p-service").with_option(
85+
NimLibp2p.connect_to, params.connect_to
86+
)
6287
if params.network_delay or params.network_jitter:
6388
builder = builder.with_network_delay(
6489
delay=params.network_delay, jitter=params.network_jitter
@@ -67,6 +92,45 @@ def build_nodes(
6792
return builder.build()
6893

6994

95+
def build_bootstrap_service(namespace: str):
96+
return (
97+
ServiceBuilder()
98+
.with_name(BOOTSTRAP_NAME)
99+
.with_namespace(namespace)
100+
.with_cluster_ip("None")
101+
.with_selector("app", "zerotenkay")
102+
.with_selector("role", "bootstrap")
103+
.with_port(V1ServicePort(name="p2p", port=5000, target_port=5000))
104+
.build()
105+
)
106+
107+
108+
def build_bootstrap_nodes(namespace: str, params: ExpConfig) -> V1StatefulSet:
109+
# Every node holds a link to the anchor, so it must accept more than num_relay_nodes.
110+
# Probe the metrics port (always TCP, unlike the p2p port under quic).
111+
return (
112+
Libp2pStatefulSetBuilder()
113+
.with_libp2p_config(
114+
name=BOOTSTRAP_NAME, namespace=namespace, num_nodes=params.bootstrap_nodes
115+
)
116+
.with_label("role", "bootstrap")
117+
.with_option(NimLibp2p.node_role, "RoleBootstrap")
118+
.with_option(NimLibp2p.discovery, "kad-dht")
119+
.with_option(NimLibp2p.muxer, params.muxer)
120+
.with_option(NimLibp2p.max_connections, params.num_relay_nodes + 100)
121+
.with_readiness_probe(
122+
V1Probe(
123+
tcp_socket=V1TCPSocketAction(port=8008),
124+
initial_delay_seconds=5,
125+
period_seconds=2,
126+
failure_threshold=3,
127+
)
128+
)
129+
.with_image(params.image)
130+
.build()
131+
)
132+
133+
70134
async def publish(config, namespace, random_name):
71135
try:
72136
target = Target(
@@ -107,6 +171,17 @@ async def _run(self):
107171
)
108172
await self.deploy(deployment=publisher, wait_for_ready=True)
109173

174+
# Bootstrap (kad-dht only): anchor node + headless discovery service. Deployed
175+
# before the nodes so the mesh can form through it once the nodes wake up.
176+
if self.config.discovery == "kad-dht":
177+
bootstrap_service = build_bootstrap_service(self.namespace)
178+
self.dump_yaml(bootstrap_service, "bootstrap-service")
179+
await self.deploy(deployment=bootstrap_service)
180+
181+
bootstrap = build_bootstrap_nodes(namespace=self.namespace, params=self.config)
182+
self.dump_yaml(bootstrap, "bootstrap")
183+
await self.deploy(deployment=bootstrap, wait_for_ready=True)
184+
110185
# Nodes
111186
nodes = build_nodes(
112187
namespace=self.namespace,
@@ -125,7 +200,7 @@ async def _run(self):
125200

126201
tasks = []
127202
for msg_index in range(self.config.num_messages):
128-
index = random.randint(0, self.config.num_nodes - 1)
203+
index = random.randint(0, self.config.num_relay_nodes - 1)
129204
random_name = f"{name}-{index}"
130205
self.log_event({"event": "publish", "node": random_name, "index": msg_index})
131206
tasks.append(asyncio.create_task(publish(self.config, namespace, random_name)))

src/deployments/libp2p/builders/builders.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class Option:
2323
service = "SERVICE"
2424
max_connections = "MAXCONNECTIONS"
2525
cold_start_delay = "COLDSTARTDELAY"
26+
node_role = "NODE_ROLE"
27+
discovery = "DISCOVERY"
2628

2729

2830
class Libp2pStatefulSetBuilder(StatefulSetBuilder):

0 commit comments

Comments
 (0)