Skip to content

Commit 259df04

Browse files
committed
support more complicated custom networks
1 parent 243a105 commit 259df04

File tree

4 files changed

+138
-91
lines changed

4 files changed

+138
-91
lines changed

src/warnet/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Constants used throughout the project
77
# Storing as constants for now but we might want a more sophisticated config management
88
# at some point.
9-
SUPPORTED_TAGS = ["27.0", "26.0", "25.1", "24.2", "23.2", "22.2"]
9+
SUPPORTED_TAGS = ["29.0", "28.1", "27.0", "26.0", "25.1", "24.2", "23.2", "22.2"]
1010
DEFAULT_TAG = SUPPORTED_TAGS[0]
1111
WEIGHTED_TAGS = [
1212
tag for index, tag in enumerate(reversed(SUPPORTED_TAGS)) for _ in range(index + 1)

src/warnet/graph.py

Lines changed: 124 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
import sys
55
from pathlib import Path
66

7+
from rich import print
8+
from rich.console import Console
9+
from rich.table import Table
10+
711
import click
812
import inquirer
913
import yaml
@@ -24,9 +28,7 @@ def graph():
2428

2529

2630
def custom_graph(
27-
num_nodes: int,
28-
num_connections: int,
29-
version: str,
31+
tanks: list,
3032
datadir: Path,
3133
fork_observer: bool,
3234
fork_obs_query_interval: int,
@@ -43,30 +45,39 @@ def custom_graph(
4345
# Generate network.yaml
4446
nodes = []
4547
connections = set()
48+
total_count = sum(int(entry["count"]) for entry in tanks)
49+
index = 0
4650

47-
for i in range(num_nodes):
48-
node = {"name": f"tank-{i:04d}", "addnode": []}
49-
50-
# Add round-robin connection
51-
next_node = (i + 1) % num_nodes
52-
node["addnode"].append(f"tank-{next_node:04d}")
53-
connections.add((i, next_node))
54-
55-
# Add random connections
56-
available_nodes = list(range(num_nodes))
57-
available_nodes.remove(i)
58-
if next_node in available_nodes:
59-
available_nodes.remove(next_node)
60-
61-
for _ in range(min(num_connections - 1, len(available_nodes))):
62-
random_node = random.choice(available_nodes)
63-
# Avoid circular loops of A -> B -> A
64-
if (random_node, i) not in connections:
65-
node["addnode"].append(f"tank-{random_node:04d}")
66-
connections.add((i, random_node))
67-
available_nodes.remove(random_node)
68-
69-
nodes.append(node)
51+
for entry in tanks:
52+
for i in range(int(entry["count"])):
53+
if ":" in entry["version"] and "/" in entry["version"]:
54+
repo, tag = entry["version"].split(":")
55+
image = {"repository": repo, "tag": tag}
56+
else:
57+
image = {"tag": entry["version"]}
58+
node = {"name": f"tank-{index:04d}", "addnode": [], "image": image}
59+
60+
# Add round-robin connection
61+
next_node = (index + 1) % total_count
62+
node["addnode"].append(f"tank-{next_node:04d}")
63+
connections.add((index, next_node))
64+
65+
# Add random connections
66+
available_nodes = list(range(total_count))
67+
available_nodes.remove(index)
68+
if next_node in available_nodes:
69+
available_nodes.remove(next_node)
70+
71+
for _ in range(min(int(entry["connections"]) - 1, len(available_nodes))):
72+
random_node = random.choice(available_nodes)
73+
# Avoid circular loops of A -> B -> A
74+
if (random_node, index) not in connections:
75+
node["addnode"].append(f"tank-{random_node:04d}")
76+
connections.add((index, random_node))
77+
available_nodes.remove(random_node)
78+
79+
nodes.append(node)
80+
index += 1
7081

7182
network_yaml_data = {"nodes": nodes}
7283
network_yaml_data["fork_observer"] = {
@@ -86,7 +97,6 @@ def custom_graph(
8697
"image": {
8798
"repository": DEFAULT_IMAGE_REPO,
8899
"pullPolicy": "IfNotPresent",
89-
"tag": version
90100
},
91101
"defaultConfig":
92102
f"rpcauth={FORK_OBSERVER_RPCAUTH}\n" +
@@ -108,73 +118,104 @@ def custom_graph(
108118

109119

110120
def inquirer_create_network(project_path: Path):
111-
# Custom network configuration
112-
questions = [
121+
network_name_prompt = inquirer.prompt([
113122
inquirer.Text(
114123
"network_name",
115124
message=click.style("Enter your network name", fg="blue", bold=True),
116125
validate=lambda _, x: len(x) > 0,
117-
),
118-
inquirer.List(
119-
"nodes",
120-
message=click.style("How many nodes would you like?", fg="blue", bold=True),
121-
choices=["8", "12", "20", "50", "other"],
122-
default="12",
123-
),
124-
inquirer.List(
125-
"connections",
126-
message=click.style(
127-
"How many connections would you like each node to have?",
128-
fg="blue",
129-
bold=True,
130-
),
131-
choices=["0", "1", "2", "8", "12", "other"],
132-
default="8",
133-
),
134-
inquirer.List(
135-
"version",
136-
message=click.style(
137-
"Which version would you like nodes to run by default?", fg="blue", bold=True
138-
),
139-
choices=SUPPORTED_TAGS,
140-
default=DEFAULT_TAG,
141-
),
142-
]
143-
144-
net_answers = inquirer.prompt(questions)
145-
if net_answers is None:
126+
)])
127+
if not network_name_prompt:
146128
click.secho("Setup cancelled by user.", fg="yellow")
147129
return False
148130

149-
if net_answers["nodes"] == "other":
150-
custom_nodes = inquirer.prompt(
151-
[
152-
inquirer.Text(
153-
"nodes",
154-
message=click.style("Enter the number of nodes", fg="blue", bold=True),
155-
validate=lambda _, x: int(x) > 0,
156-
)
157-
]
158-
)
159-
if custom_nodes is None:
131+
tanks = []
132+
while True:
133+
table = Table(title="Current Network Population", show_header=True, header_style="magenta")
134+
table.add_column("Version", style="cyan")
135+
table.add_column("Count", style="green")
136+
table.add_column("Connections", style="green")
137+
138+
for entry in tanks:
139+
table.add_row(entry["version"], entry["count"], entry["connections"])
140+
141+
Console().print(table)
142+
143+
add_more_prompt = inquirer.prompt([
144+
inquirer.List(
145+
"add_more",
146+
message=click.style(f"How many nodes to add? (0 = done)", fg="blue", bold=True),
147+
choices=["0", "4", "8", "12", "20", "50", "other"],
148+
default="12")])
149+
if not add_more_prompt:
160150
click.secho("Setup cancelled by user.", fg="yellow")
161151
return False
162-
net_answers["nodes"] = custom_nodes["nodes"]
152+
if add_more_prompt["add_more"].startswith("0"):
153+
break
163154

164-
if net_answers["connections"] == "other":
165-
custom_connections = inquirer.prompt(
166-
[
155+
if add_more_prompt["add_more"] == "other":
156+
how_many_prompt = inquirer.prompt([
167157
inquirer.Text(
168-
"connections",
169-
message=click.style("Enter the number of connections", fg="blue", bold=True),
170-
validate=lambda _, x: int(x) >= 0,
171-
)
172-
]
173-
)
174-
if custom_connections is None:
158+
"how_many",
159+
message=click.style("Enter the number of nodes", fg="blue", bold=True),
160+
validate=lambda _, x: int(x) > 0)])
161+
if not how_many_prompt:
162+
click.secho("Setup cancelled by user.", fg="yellow")
163+
return False
164+
how_many = how_many_prompt["how_many"]
165+
else:
166+
how_many = add_more_prompt["add_more"]
167+
168+
tank_details_prompt = inquirer.prompt([
169+
inquirer.List(
170+
"version",
171+
message=click.style("Which version would you like to add to network?", fg="blue", bold=True),
172+
choices=["other"] + SUPPORTED_TAGS,
173+
default=DEFAULT_TAG,
174+
),
175+
inquirer.List(
176+
"connections",
177+
message=click.style(
178+
"How many connections would you like each of these nodes to have?",
179+
fg="blue",
180+
bold=True,
181+
),
182+
choices=["0", "1", "2", "8", "12", "other"],
183+
default="8",
184+
)])
185+
if not tank_details_prompt:
175186
click.secho("Setup cancelled by user.", fg="yellow")
176187
return False
177-
net_answers["connections"] = custom_connections["connections"]
188+
break
189+
if tank_details_prompt["version"] == "other":
190+
custom_version_prompt = inquirer.prompt([
191+
inquirer.Text(
192+
"version",
193+
message=click.style("Provide dockerhub repository/image:tag", fg="blue", bold=True),
194+
validate=lambda _, x: "/" in x and ":" in x)])
195+
if not custom_version_prompt:
196+
click.secho("Setup cancelled by user.", fg="yellow")
197+
return False
198+
tank_details_prompt["version"] = custom_version_prompt["version"]
199+
200+
if tank_details_prompt["connections"] == "other":
201+
how_many_conn_prompt = inquirer.prompt([
202+
inquirer.Text(
203+
"how_many_conn",
204+
message=click.style("Enter the number of connections", fg="blue", bold=True),
205+
validate=lambda _, x: int(x) > 0)])
206+
if not how_many_conn_prompt:
207+
click.secho("Setup cancelled by user.", fg="yellow")
208+
return False
209+
how_many_conn = how_many_conn_prompt["how_many_conn"]
210+
else:
211+
how_many_conn = tank_details_prompt["connections"]
212+
213+
tanks.append({
214+
"version": tank_details_prompt["version"],
215+
"count": how_many,
216+
"connections": how_many_conn
217+
})
218+
178219
fork_observer = click.prompt(
179220
click.style(
180221
"\nWould you like to enable fork-observer on the network?", fg="blue", bold=True
@@ -202,12 +243,10 @@ def inquirer_create_network(project_path: Path):
202243
default=False,
203244
)
204245
caddy = fork_observer | logging
205-
custom_network_path = project_path / "networks" / net_answers["network_name"]
246+
custom_network_path = project_path / "networks" / network_name_prompt["network_name"]
206247
click.secho("\nGenerating custom network...", fg="yellow", bold=True)
207248
custom_graph(
208-
int(net_answers["nodes"]),
209-
int(net_answers["connections"]),
210-
net_answers["version"],
249+
tanks,
211250
custom_network_path,
212251
fork_observer,
213252
fork_observer_query_interval,

test/graph_test.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
import json
44
import os
5+
import sys
56

67
import pexpect
78
from test_base import TestBase
89

10+
from warnet.process import stream_command
11+
912
NETWORKS_DIR = "networks"
1013

1114

@@ -36,15 +39,20 @@ def directory_not_exist(self):
3639
def directory_exists(self):
3740
try:
3841
self.log.info("testing warnet create, dir does exist")
39-
self.sut = pexpect.spawn("warnet create")
42+
self.sut = pexpect.spawn("warnet create", encoding="utf-8")
43+
self.sut.logfile = sys.stdout
4044
self.sut.expect("name", timeout=30)
4145
self.sut.sendline("ANewNetwork")
4246
self.sut.expect("many", timeout=30)
4347
self.sut.sendline("")
44-
self.sut.expect("connections", timeout=30)
45-
self.sut.sendline("")
4648
self.sut.expect("version", timeout=30)
4749
self.sut.sendline("")
50+
self.sut.expect("connections", timeout=30)
51+
self.sut.sendline("")
52+
self.sut.expect("many", timeout=30)
53+
# Up arrow three times: [12] -> 8 -> 4 -> 0 (done)
54+
self.sut.sendline("\x1b[A" * 3)
55+
self.sut.sendline("\r")
4856
self.sut.expect("enable fork-observer", timeout=30)
4957
self.sut.sendline("")
5058
self.sut.expect("seconds", timeout=30)
@@ -65,7 +73,7 @@ def run_created_network(self):
6573
f.write(s)
6674

6775
self.log.info("deploying new network")
68-
self.warnet("deploy networks/ANewNetwork")
76+
stream_command("warnet deploy networks/ANewNetwork")
6977
self.wait_for_all_tanks_status(target="running")
7078
debugs = json.loads(self.warnet("bitcoin rpc tank-0000 logging"))
7179
# set in defaultConfig

test/plugin_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(self):
1818
Path(os.path.dirname(__file__)) / "data" / "network_with_plugins"
1919
)
2020
self.plugins_dir = Path(os.path.dirname(__file__)).parent / "resources" / "plugins"
21-
self.simln_exec = "plugins/simln/plugin.py"
21+
self.simln_exec = self.plugins_dir / "simln" / "plugin.py"
2222

2323
def run_test(self):
2424
try:

0 commit comments

Comments
 (0)