Skip to content

Commit 5c32b19

Browse files
committed
simln: export data inside docker compose network
1 parent e9a6c0a commit 5c32b19

File tree

7 files changed

+66
-41
lines changed

7 files changed

+66
-41
lines changed

src/cli/network.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ def connected(network: str):
143143

144144
@network.command()
145145
@click.option("--network", default="warnet", show_default=True)
146-
def export(network):
146+
def export(network: str):
147147
"""
148-
Export all [network] data for sim-ln to subdirectory
148+
Export all [network] data for a "simln" service running in a container
149+
on the network. Returns True on success.
149150
"""
150151
print(rpc_call("network_export", {"network": network}))

src/warnet/lnnode.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import os
1+
import io
2+
import tarfile
23

34
from backends import BackendInterface, ServiceType
45
from warnet.utils import exponential_backoff, generate_ipv4_addr, handle_json
@@ -115,30 +116,38 @@ def generate_cli_command(self, command: list[str]):
115116
raise Exception(f"Unsupported LN implementation: {self.impl}")
116117
return cmd
117118

118-
def export(self, config, subdir):
119-
container_name = self.backend.get_container_name(self.tank.index, ServiceType.LIGHTNING)
120-
macaroon_filename = f"{container_name}_admin.macaroon"
121-
cert_filename = f"{container_name}_tls.cert"
122-
macaroon_path = os.path.join(subdir, macaroon_filename)
123-
cert_path = os.path.join(subdir, cert_filename)
119+
def export(self, config: object, tar_file):
120+
# Retrieve the credentials
124121
macaroon = self.backend.get_file(
125122
self.tank.index,
126123
ServiceType.LIGHTNING,
127124
"/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon",
128125
)
129-
cert = self.backend.get_file(self.tank.index, ServiceType.LIGHTNING, "/root/.lnd/tls.cert")
126+
cert = self.backend.get_file(
127+
self.tank.index,
128+
ServiceType.LIGHTNING,
129+
"/root/.lnd/tls.cert"
130+
)
130131

131-
with open(macaroon_path, "wb") as f:
132-
f.write(macaroon)
132+
# Add the files to the in-memory tar archive
133+
container_name = self.backend.get_container_name(self.tank.index, ServiceType.LIGHTNING)
134+
macaroon_filename = f"{container_name}_admin.macaroon"
135+
cert_filename = f"{container_name}_tls.cert"
133136

134-
with open(cert_path, "wb") as f:
135-
f.write(cert)
137+
tarinfo1 = tarfile.TarInfo(name=macaroon_filename)
138+
tarinfo1.size = len(macaroon)
139+
fileobj1 = io.BytesIO(macaroon)
140+
tar_file.addfile(tarinfo=tarinfo1, fileobj=fileobj1)
141+
tarinfo2 = tarfile.TarInfo(name=cert_filename)
142+
tarinfo2.size = len(cert)
143+
fileobj2 = io.BytesIO(cert)
144+
tar_file.addfile(tarinfo=tarinfo2, fileobj=fileobj2)
136145

137146
config["nodes"].append(
138147
{
139148
"id": container_name,
140-
"address": f"https://{self.ipv4}:{self.rpc_port}",
141-
"macaroon": macaroon_path,
142-
"cert": cert_path,
149+
"address": f"https://{container_name}:{self.rpc_port}",
150+
"macaroon": f"/simln/{macaroon_filename}",
151+
"cert": f"/simln/{cert_filename}",
143152
}
144153
)

src/warnet/server.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import argparse
22
import base64
3+
import io
34
import json
45
import logging
56
import logging.config
@@ -10,6 +11,7 @@
1011
import signal
1112
import subprocess
1213
import sys
14+
import tarfile
1315
import tempfile
1416
import threading
1517
import time
@@ -268,20 +270,32 @@ def tank_messages(self, network: str, node_a: int, node_b: int) -> str:
268270
self.logger.error(msg)
269271
raise ServerError(message=msg) from e
270272

271-
def network_export(self, network: str) -> str:
273+
def network_export(self, network: str) -> bool:
272274
"""
273-
Export all data for sim-ln to subdirectory
275+
Export all data for a simln container running on the network
274276
"""
275-
try:
276-
wn = self.get_warnet(network)
277-
subdir = os.path.join(wn.config_dir, "simln")
278-
os.makedirs(subdir, exist_ok=True)
279-
wn.export(subdir)
280-
return subdir
281-
except Exception as e:
282-
msg = f"Error exporting network: {e}"
283-
self.logger.error(msg)
284-
raise ServerError(message=msg) from e
277+
wn = self.get_warnet(network)
278+
279+
# JSON object that will eventually be written to simln config file
280+
config = {"nodes": []}
281+
# In-memory file to build tar archive
282+
tar_buffer = io.BytesIO()
283+
with tarfile.open(fileobj=tar_buffer, mode="w") as tar_file:
284+
# tank LN nodes add their credentials to tar archive
285+
wn.export(config, tar_file)
286+
# write config file
287+
config_bytes = json.dumps(config).encode('utf-8')
288+
config_stream = io.BytesIO(config_bytes)
289+
tarinfo = tarfile.TarInfo(name="sim.json")
290+
tarinfo.size = len(config_bytes)
291+
tar_file.addfile(tarinfo=tarinfo, fileobj=config_stream)
292+
293+
with open(wn.config_dir / "simln.tar", "wb") as output:
294+
tar_buffer.seek(0)
295+
output.write(tar_buffer.read())
296+
297+
subprocess.run(["tar", "-xf", wn.config_dir / 'simln.tar', "-C", wn.config_dir / 'simln'])
298+
return True
285299

286300
def scenarios_available(self) -> list[tuple]:
287301
"""

src/warnet/services.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
"backends": ["compose", "k8s"],
8484
"image": "bitcoindevproject/simln:0.2.0",
8585
"container_name_suffix": "simln",
86-
"args": ["--log-level=debug"]
86+
"environment": [
87+
"LOG_LEVEL=debug",
88+
"SIMFILE_PATH=/simln/sim.json"
89+
],
90+
"config_files": ["/simln:/simln"]
8791
},
8892
}

src/warnet/tank.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,6 @@ def apply_network_conditions(self):
169169
f"Error applying network conditions to tank {self.index}: `{self.netem}` ({e})"
170170
)
171171

172-
def export(self, config, subdir):
172+
def export(self, config: object, tar_file):
173173
if self.lnnode is not None:
174-
self.lnnode.export(config, subdir)
174+
self.lnnode.export(config, tar_file)

src/warnet/warnet.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import base64
66
import json
77
import logging
8-
import os
98
import shutil
109
from pathlib import Path
1110

@@ -136,6 +135,8 @@ def from_network(cls, network_name, backend="compose"):
136135
self.graph = networkx.read_graphml(Path(self.config_dir / self.graph_name), node_type=int, force_multigraph=True)
137136
validate_graph_schema(self.graph)
138137
self.tanks_from_graph()
138+
if "services" in self.graph.graph:
139+
self.services = self.graph.graph["services"].split()
139140
for tank in self.tanks:
140141
tank._ipv4 = self.container_interface.get_tank_ipv4(tank.index)
141142
return self
@@ -237,15 +238,9 @@ def write_prometheus_config(self):
237238
except Exception as e:
238239
logger.error(f"An error occurred while writing to {prometheus_path}: {e}")
239240

240-
def export(self, subdir):
241-
if self.backend != "compose":
242-
raise NotImplementedError("Export is only supported for compose backend")
243-
config = {"nodes": []}
241+
def export(self, config: object, tar_file):
244242
for tank in self.tanks:
245-
tank.export(config, subdir)
246-
config_path = os.path.join(subdir, "sim.json")
247-
with open(config_path, "a") as f:
248-
json.dump(config, f)
243+
tank.export(config, tar_file)
249244

250245
def wait_for_health(self):
251246
self.container_interface.wait_for_healthy_tanks(self)

test/data/ln.graphml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?><graphml xmlns="http://graphml.graphdrawing.org/xmlns">
22
<key id="version" attr.name="version" attr.type="string" for="node" />
3+
<key id="services" attr.name="services" attr.type="string" for="graph" />
34
<key id="image" attr.name="image" attr.type="string" for="node" />
45
<key id="bitcoin_config" attr.name="bitcoin_config" attr.type="string" for="node" />
56
<key id="tc_netem" attr.name="tc_netem" attr.type="string" for="node" />
@@ -14,6 +15,7 @@
1415
<key id="source_policy" attr.name="source_policy" attr.type="string" for="edge" />
1516
<key id="target_policy" attr.name="target_policy" attr.type="string" for="edge" />
1617
<graph edgedefault="directed">
18+
<data key="services">simln</data>
1719
<node id="0">
1820
<data key="version">26.0</data>
1921
<data key="bitcoin_config">-uacomment=w0</data>

0 commit comments

Comments
 (0)