Skip to content

Commit 1b53dd0

Browse files
committed
launch activity in one line
1 parent 17725b8 commit 1b53dd0

File tree

3 files changed

+41
-220
lines changed

3 files changed

+41
-220
lines changed

resources/plugins/simln/simln.py

100644100755
Lines changed: 17 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
#!/usr/bin/env python3
2+
13
import json
24
import logging
35
import time
46
from pathlib import Path
5-
from subprocess import run
6-
from time import sleep
77
from typing import Optional
88

99
import click
@@ -18,12 +18,10 @@
1818
get_mission,
1919
get_static_client,
2020
wait_for_init,
21-
wait_for_pod,
2221
write_file_to_container,
2322
)
2423
from warnet.plugins import get_plugins_directory_or
2524
from warnet.process import run_command
26-
from warnet.status import _get_tank_status as network_status
2725

2826
# To make a "mission" tag for your plugin, declare it using the variable name MISSION. This will
2927
# be read by the warnet log system and status system.
@@ -54,8 +52,17 @@ class SimLNError(Exception):
5452
# Each plugin must declare a click "group" by decorating a function named after the plugin.
5553
# This makes your plugin available in the plugin section of Warnet.
5654
@click.group()
57-
def simln():
55+
@click.pass_context
56+
def simln(ctx):
5857
"""Commands for the SimLN plugin"""
58+
try:
59+
# check if we have set a user directory
60+
ctx.obj.get(USER_DIR_TAG)
61+
except Exception:
62+
# if not, set the great-grandparent of this file as the user dir
63+
ctx.ensure_object(dict)
64+
user_dir = Path(__file__).resolve().parent.parent.parent
65+
ctx.obj[USER_DIR_TAG] = Path(user_dir)
5966
pass
6067

6168

@@ -66,22 +73,6 @@ def warnet_register_plugin(register_command):
6673

6774
# The group function name is then used in decorators to create commands. These commands are
6875
# available to users when they access your plugin from the command line in Warnet.
69-
@simln.command()
70-
def run_demo():
71-
"""Run the SimLN Plugin demo"""
72-
_init_network()
73-
_fund_wallets()
74-
_wait_for_all_ln_nodes_to_have_a_host()
75-
log.info(warnet("bitcoin rpc tank-0000 -generate 7"))
76-
manual_open_channels()
77-
log.info(warnet("bitcoin rpc tank-0000 -generate 7"))
78-
wait_for_gossip_sync(2)
79-
log.info("done waiting")
80-
pod_name = prepare_and_launch_activity()
81-
log.info(pod_name)
82-
wait_for_pod(pod_name, 60)
83-
84-
8576
@simln.command()
8677
def list_simln_podnames():
8778
"""Get a list of simln pod names"""
@@ -96,14 +87,6 @@ def download_results(pod_name: str):
9687
print(f"Downloaded results to: {dest}")
9788

9889

99-
def prepare_and_launch_activity() -> str:
100-
sample_activity = _get_example_activity()
101-
log.info(f"Activity: {sample_activity}")
102-
pod_name = _launch_activity(sample_activity)
103-
log.info("Sent command. Done.")
104-
return pod_name
105-
106-
10790
# When we want to use a command inside our plugin and also provide that command to the user, we like
10891
# to create a private function whose name starts with an underscore. We also make a public function
10992
# with the same name except that we leave off the underscore, decorate it with the command
@@ -168,141 +151,6 @@ def launch_activity(ctx, activity: str):
168151
print(_launch_activity(parsed_activity, user_dir))
169152

170153

171-
def _init_network():
172-
"""Mine regtest coins and wait for ln nodes to come online."""
173-
log.info("Initializing network")
174-
wait_for_all_tanks_status(target="running")
175-
176-
warnet("bitcoin rpc tank-0000 createwallet miner")
177-
warnet("bitcoin rpc tank-0000 -generate 110")
178-
wait_for_predicate(lambda: int(warnet("bitcoin rpc tank-0000 getblockcount")) > 100)
179-
180-
def wait_for_all_ln_rpc():
181-
lns = get_mission(LIGHTNING_MISSION)
182-
for v1_pod in lns:
183-
ln = v1_pod.metadata.name
184-
try:
185-
warnet(f"ln rpc {ln} getinfo")
186-
except Exception:
187-
log.info(f"LN node {ln} not ready for rpc yet")
188-
return False
189-
return True
190-
191-
wait_for_predicate(wait_for_all_ln_rpc)
192-
193-
194-
@simln.command()
195-
def init_network():
196-
"""Initialize the demo network."""
197-
_init_network()
198-
199-
200-
def _fund_wallets():
201-
"""Fund each ln node with 10 regtest coins."""
202-
log.info("Funding wallets")
203-
outputs = ""
204-
lns = get_mission(LIGHTNING_MISSION)
205-
for v1_pod in lns:
206-
lnd = v1_pod.metadata.name
207-
addr = json.loads(warnet(f"ln rpc {lnd} newaddress p2wkh"))["address"]
208-
outputs += f',"{addr}":10'
209-
# trim first comma
210-
outputs = outputs[1:]
211-
log.info(warnet("bitcoin rpc tank-0000 sendmany '' '{" + outputs + "}'"))
212-
log.info(warnet("bitcoin rpc tank-0000 -generate 1"))
213-
214-
215-
@simln.command()
216-
def fund_wallets():
217-
"""Fund each ln node with 10 regtest coins."""
218-
_fund_wallets()
219-
220-
221-
def all_ln_nodes_have_a_host() -> bool:
222-
"""Find out if each ln node has a host."""
223-
pods = get_mission(LIGHTNING_MISSION)
224-
host_havers = 0
225-
for pod in pods:
226-
name = pod.metadata.name
227-
result = warnet(f"ln host {name}")
228-
if len(result) > 1:
229-
host_havers += 1
230-
return host_havers == len(pods) and host_havers != 0
231-
232-
233-
@simln.command()
234-
def wait_for_all_ln_nodes_to_have_a_host():
235-
log.info(_wait_for_all_ln_nodes_to_have_a_host())
236-
237-
238-
def _wait_for_all_ln_nodes_to_have_a_host():
239-
wait_for_predicate(all_ln_nodes_have_a_host, timeout=10 * 60)
240-
241-
242-
def wait_for_predicate(predicate, timeout=5 * 60, interval=5):
243-
log.info(
244-
f"Waiting for predicate ({predicate.__name__}) with timeout {timeout}s and interval {interval}s"
245-
)
246-
while timeout > 0:
247-
try:
248-
if predicate():
249-
return
250-
except Exception:
251-
pass
252-
sleep(interval)
253-
timeout -= interval
254-
import inspect
255-
256-
raise Exception(
257-
f"Timed out waiting for Truth from predicate: {inspect.getsource(predicate).strip()}"
258-
)
259-
260-
261-
def wait_for_all_tanks_status(target: str = "running", timeout: int = 20 * 60, interval: int = 5):
262-
"""Poll the warnet server for container status. Block until all tanks are running"""
263-
264-
def check_status():
265-
tanks = network_status()
266-
stats = {"total": 0}
267-
# "Probably" means all tanks are stopped and deleted
268-
if len(tanks) == 0:
269-
return True
270-
for tank in tanks:
271-
status = tank["status"]
272-
stats["total"] += 1
273-
stats[status] = stats.get(status, 0) + 1
274-
log.info(f"Waiting for all tanks to reach '{target}': {stats}")
275-
return target in stats and stats[target] == stats["total"]
276-
277-
wait_for_predicate(check_status, timeout, interval)
278-
279-
280-
def wait_for_gossip_sync(expected: int = 2):
281-
"""Wait for any of the ln nodes to have an `expected` number of edges."""
282-
log.info(f"Waiting for sync (expecting {expected})...")
283-
current = 0
284-
while current < expected:
285-
current = 0
286-
pods = get_mission(LIGHTNING_MISSION)
287-
for v1_pod in pods:
288-
node = v1_pod.metadata.name
289-
chs = json.loads(run_command(f"warnet ln rpc {node} describegraph"))["edges"]
290-
log.info(f"{node}: {len(chs)} channels")
291-
current += len(chs)
292-
sleep(1)
293-
log.info("Synced")
294-
295-
296-
def warnet(cmd: str = "--help"):
297-
"""Pass a `cmd` to Warnet."""
298-
log.info(f"Executing warnet command: {cmd}")
299-
command = ["warnet"] + cmd.split()
300-
proc = run(command, capture_output=True)
301-
if proc.stderr:
302-
raise Exception(proc.stderr.decode().strip())
303-
return proc.stdout.decode()
304-
305-
306154
def _generate_activity_json(activity: list[dict]) -> str:
307155
nodes = []
308156

@@ -321,46 +169,6 @@ def _generate_activity_json(activity: list[dict]) -> str:
321169
return json.dumps(data, indent=2)
322170

323171

324-
def manual_open_channels():
325-
"""Manually open channels between ln nodes 1, 2, and 3"""
326-
327-
def wait_for_two_txs():
328-
wait_for_predicate(
329-
lambda: json.loads(warnet("bitcoin rpc tank-0000 getmempoolinfo"))["size"] == 2
330-
)
331-
332-
# 0 -> 1 -> 2
333-
pk1 = warnet("ln pubkey tank-0001-ln")
334-
pk2 = warnet("ln pubkey tank-0002-ln")
335-
log.info(f"pk1: {pk1}")
336-
log.info(f"pk2: {pk2}")
337-
338-
host1 = ""
339-
host2 = ""
340-
341-
while not host1 or not host2:
342-
if not host1:
343-
host1 = warnet("ln host tank-0001-ln")
344-
if not host2:
345-
host2 = warnet("ln host tank-0002-ln")
346-
sleep(1)
347-
348-
print(
349-
warnet(
350-
f"ln rpc tank-0000-ln openchannel --node_key {pk1} --local_amt 100000 --connect {host1}"
351-
)
352-
)
353-
print(
354-
warnet(
355-
f"ln rpc tank-0001-ln openchannel --node_key {pk2} --local_amt 100000 --connect {host2}"
356-
)
357-
)
358-
359-
wait_for_two_txs()
360-
361-
warnet("bitcoin rpc tank-0000 -generate 10")
362-
363-
364172
def _sh(pod, method: str, params: tuple[str, ...]) -> str:
365173
namespace = get_default_namespace()
366174

@@ -403,5 +211,9 @@ def _sh(pod, method: str, params: tuple[str, ...]) -> str:
403211
@click.argument("method", type=str)
404212
@click.argument("params", type=str, nargs=-1) # this will capture all remaining arguments
405213
def sh(pod: str, method: str, params: tuple[str, ...]):
406-
"""Run commands on a pod"""
214+
"""Run shell commands in a pod"""
407215
print(_sh(pod, method, params))
216+
217+
218+
if __name__ == "__main__":
219+
simln()

test/ln_test.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from test_base import TestBase
88

9-
from warnet.process import stream_command
9+
from warnet.process import run_command, stream_command
1010

1111

1212
class LNTest(TestBase):
@@ -15,13 +15,15 @@ def __init__(self):
1515
self.graph_file = Path(os.path.dirname(__file__)) / "data" / "LN_10.json"
1616
self.imported_network_dir = self.tmpdir / "imported_network"
1717
self.scen_dir = Path(os.path.dirname(__file__)).parent / "resources" / "scenarios"
18+
self.plugins_dir = Path(os.path.dirname(__file__)).parent / "resources" / "plugins"
1819

1920
def run_test(self):
2021
try:
2122
self.import_network()
2223
self.setup_network()
2324
self.test_channel_policies()
2425
self.test_payments()
26+
self.run_simln()
2527
finally:
2628
self.cleanup()
2729

@@ -81,6 +83,14 @@ def get_and_pay(src, tgt):
8183
get_and_pay(8, 7)
8284
get_and_pay(4, 6)
8385

86+
def run_simln(self):
87+
self.log.info("Running activity")
88+
activity_cmd = f"{self.plugins_dir}/simln/simln.py get-example-activity"
89+
activity = run_command(activity_cmd).strip()
90+
self.log.info(f"Activity: {activity}")
91+
command = f"{self.plugins_dir}/simln/simln.py launch-activity '{activity}'"
92+
self.log.info(run_command(command))
93+
8494

8595
if __name__ == "__main__":
8696
test = LNTest()

test/simln_test.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Optional
99

1010
import pexpect
11+
from ln_test import LNTest
1112
from test_base import TestBase
1213

1314
from warnet.k8s import download, get_pods_with_label, pod_log, wait_for_pod
@@ -16,40 +17,35 @@
1617
lightning_selector = "mission=lightning"
1718

1819

19-
class SimLNTest(TestBase):
20+
class SimLNTest(LNTest, TestBase):
2021
def __init__(self):
2122
super().__init__()
2223
self.network_dir = Path(os.path.dirname(__file__)) / "data" / "ln"
24+
self.plugins_dir = Path(os.path.dirname(__file__)).parent / "resources" / "plugins"
2325

2426
def run_test(self):
2527
try:
2628
os.chdir(self.tmpdir)
29+
self.init_directory()
30+
31+
self.import_network()
2732
self.setup_network()
28-
self.run_plugin()
33+
self.run_ln_init_scenario()
34+
self.run_simln()
35+
2936
self.copy_results()
3037
self.run_activity()
3138
self.run_activity_with_user_dir()
3239
finally:
3340
self.cleanup()
3441

35-
def setup_network(self):
36-
self.log.info("Setting up network")
37-
self.log.info(self.warnet(f"deploy {self.network_dir}"))
38-
self.wait_for_all_tanks_status(target="running")
39-
40-
def run_plugin(self):
42+
def init_directory(self):
4143
self.log.info("Initializing SimLN plugin...")
4244
self.sut = pexpect.spawn("warnet init")
4345
self.sut.expect("network", timeout=10)
4446
self.sut.sendline("n")
4547
self.sut.close()
4648

47-
cmd = "warnet plugins simln run-demo"
48-
self.log.info(f"Running: {cmd}")
49-
run_command(cmd)
50-
self.wait_for_predicate(self.found_results_remotely)
51-
self.log.info("Ran SimLn plugin.")
52-
5349
def copy_results(self):
5450
self.log.info("Copying results")
5551
pod = get_pods_with_label("mission=simln")[0]
@@ -59,6 +55,9 @@ def copy_results(self):
5955
log_resp = pod_log(pod.metadata.name, "simln")
6056
self.log.info(log_resp.data.decode("utf-8"))
6157

58+
partial_func = partial(self.found_results_remotely, pod.metadata.name)
59+
self.wait_for_predicate(partial_func)
60+
6261
download(pod.metadata.name, Path("/working/results"), Path("."), pod.metadata.namespace)
6362
self.wait_for_predicate(self.found_results_locally)
6463

0 commit comments

Comments
 (0)