Skip to content

Commit 5a49d86

Browse files
committed
deploy plugins from network.yaml
1 parent c15c3ca commit 5a49d86

File tree

5 files changed

+93
-61
lines changed

5 files changed

+93
-61
lines changed

resources/plugins/simln/simln.py

+15-15
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,21 @@ def get_example_activity():
105105
print(json.dumps(_get_example_activity()))
106106

107107

108+
# Take note of how click expects us to explicitly declare command line arguments.
109+
@simln.command()
110+
@click.argument("activity", type=str)
111+
@click.pass_context
112+
def launch_activity(ctx, activity: str):
113+
"""Deploys a SimLN Activity which is a JSON list of objects"""
114+
try:
115+
parsed_activity = json.loads(activity)
116+
except json.JSONDecodeError:
117+
log.error("Invalid JSON input for activity.")
118+
raise click.BadArgumentUsage("Activity must be a valid JSON string.") from None
119+
plugin_dir = ctx.obj.get(PLUGIN_DIR_TAG)
120+
print(_launch_activity(parsed_activity, plugin_dir))
121+
122+
108123
def _launch_activity(activity: list[dict], plugin_dir: str) -> str:
109124
"""Launch a SimLN chart which includes the `activity`"""
110125
timestamp = int(time.time())
@@ -128,21 +143,6 @@ def _launch_activity(activity: list[dict], plugin_dir: str) -> str:
128143
raise SimLNError(f"Could not write sim.json to the init container: {name}")
129144

130145

131-
# Take note of how click expects us to explicitly declare command line arguments.
132-
@simln.command()
133-
@click.argument("activity", type=str)
134-
@click.pass_context
135-
def launch_activity(ctx, activity: str):
136-
"""Deploys a SimLN Activity which is a JSON list of objects"""
137-
try:
138-
parsed_activity = json.loads(activity)
139-
except json.JSONDecodeError:
140-
log.error("Invalid JSON input for activity.")
141-
raise click.BadArgumentUsage("Activity must be a valid JSON string.") from None
142-
plugin_dir = ctx.obj.get(PLUGIN_DIR_TAG)
143-
print(_launch_activity(parsed_activity, plugin_dir))
144-
145-
146146
def _generate_activity_json(activity: list[dict]) -> str:
147147
nodes = []
148148

src/warnet/deploy.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
wait_for_ingress_controller,
3535
wait_for_pod_ready,
3636
)
37-
from .process import stream_command
37+
from .process import run_command, stream_command
3838

3939
HINT = "\nAre you trying to run a scenario? See `warnet run --help`"
4040

@@ -115,6 +115,8 @@ def _deploy(directory, debug, namespace, to_all_users):
115115
for p in processes:
116116
p.join()
117117

118+
run_plugins(directory)
119+
118120
elif (directory / NAMESPACES_FILE).exists():
119121
deploy_namespaces(directory)
120122
else:
@@ -123,6 +125,19 @@ def _deploy(directory, debug, namespace, to_all_users):
123125
)
124126

125127

128+
def run_plugins(directory):
129+
network_file_path = directory / NETWORK_FILE
130+
131+
with network_file_path.open() as f:
132+
network_file = yaml.safe_load(f)
133+
134+
plugins = network_file.get("plugins") or []
135+
for plugin_cmd in plugins:
136+
fully_qualified_cmd = f"{network_file_path.parent}/{plugin_cmd}" # relative to network.yaml
137+
print(fully_qualified_cmd)
138+
print(run_command(fully_qualified_cmd))
139+
140+
126141
def check_logging_required(directory: Path):
127142
# check if node-defaults has logging or metrics enabled
128143
default_file_path = directory / DEFAULTS_FILE
@@ -137,7 +152,8 @@ def check_logging_required(directory: Path):
137152
network_file_path = directory / NETWORK_FILE
138153
with network_file_path.open() as f:
139154
network_file = yaml.safe_load(f)
140-
nodes = network_file.get("nodes", [])
155+
156+
nodes = network_file.get("nodes") or []
141157
for node in nodes:
142158
if node.get("collectLogs", False):
143159
return True
@@ -295,7 +311,8 @@ def deploy_network(directory: Path, debug: bool = False, namespace: Optional[str
295311
queue = Queue()
296312
processes = []
297313

298-
for node in network_file["nodes"]:
314+
nodes = network_file.get("nodes") or []
315+
for node in nodes:
299316
p = Process(target=deploy_single_node, args=(node, directory, debug, namespace, queue))
300317
p.start()
301318
processes.append(p)

test/data/ln/network.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,7 @@ nodes:
5151
addnode:
5252
- tank-0000
5353
ln:
54-
lnd: true
54+
lnd: true
55+
56+
plugins:
57+
- "../../../resources/plugins/simln/simln.py launch-activity '[{\"source\": \"tank-0003-ln\", \"destination\": \"tank-0005-ln\", \"interval_secs\": 1, \"amount_msat\": 2000}]'"

test/ln_test.py

+34-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#!/usr/bin/env python3
2-
2+
import ast
33
import json
44
import os
55
from pathlib import Path
6+
from typing import Optional
67

78
from test_base import TestBase
89

10+
from warnet.k8s import wait_for_pod
911
from warnet.process import run_command, stream_command
1012

1113

@@ -16,6 +18,7 @@ def __init__(self):
1618
self.imported_network_dir = self.tmpdir / "imported_network"
1719
self.scen_dir = Path(os.path.dirname(__file__)).parent / "resources" / "scenarios"
1820
self.plugins_dir = Path(os.path.dirname(__file__)).parent / "resources" / "plugins"
21+
self.simln_exec = Path("simln/simln.py")
1922

2023
def run_test(self):
2124
try:
@@ -84,12 +87,36 @@ def get_and_pay(src, tgt):
8487
get_and_pay(4, 6)
8588

8689
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))
90+
self.log.info("Running SimLN...")
91+
activity_cmd = f"{self.plugins_dir}/{self.simln_exec} get-example-activity"
92+
activity = run_command(activity_cmd)
93+
launch_cmd = f"{self.plugins_dir}/{self.simln_exec} launch-activity '{activity}'"
94+
pod = run_command(launch_cmd).strip()
95+
wait_for_pod(pod)
96+
self.log.info("Checking SimLN...")
97+
self.wait_for_predicate(self.found_results_remotely)
98+
self.log.info("SimLN was successful.")
99+
100+
def found_results_remotely(self, pod: Optional[str] = None) -> bool:
101+
if pod is None:
102+
pod = self.get_first_simln_pod()
103+
self.log.info(f"Checking for results file in {pod}")
104+
results_file = run_command(
105+
f"{self.plugins_dir}/{self.simln_exec} sh {pod} ls /working/results"
106+
).strip()
107+
self.log.info(f"Results file: {results_file}")
108+
results = run_command(
109+
f"{self.plugins_dir}/{self.simln_exec} sh {pod} cat /working/results/{results_file}"
110+
).strip()
111+
self.log.info(results)
112+
return results.find("Success") > 0
113+
114+
def get_first_simln_pod(self):
115+
command = f"{self.plugins_dir}/{self.simln_exec} list-pod-names"
116+
pod_names_literal = run_command(command)
117+
self.log.info(f"{command}: {pod_names_literal}")
118+
pod_names = ast.literal_eval(pod_names_literal)
119+
return pod_names[0]
93120

94121

95122
if __name__ == "__main__":

test/simln_test.py

+20-35
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@
88
from typing import Optional
99

1010
import pexpect
11-
from ln_test import LNTest
1211
from test_base import TestBase
1312

1413
from warnet.constants import LIGHTNING_MISSION
15-
from warnet.k8s import download, get_mission, pod_log, wait_for_pod
14+
from warnet.k8s import download, get_mission, wait_for_pod
1615
from warnet.process import run_command
1716

1817

19-
class SimLNTest(LNTest, TestBase):
18+
class SimLNTest(TestBase):
2019
def __init__(self):
2120
super().__init__()
2221
self.network_dir = Path(os.path.dirname(__file__)) / "data" / "ln"
@@ -27,15 +26,8 @@ def run_test(self):
2726
try:
2827
os.chdir(self.tmpdir)
2928
self.init_directory()
30-
31-
self.import_network()
32-
self.setup_network()
33-
self.test_channel_policies()
34-
self.test_payments()
35-
self.run_simln()
36-
29+
self.deploy_with_plugin()
3730
self.copy_results()
38-
self.run_activity()
3931
finally:
4032
self.cleanup()
4133

@@ -46,32 +38,20 @@ def init_directory(self):
4638
self.sut.sendline("n")
4739
self.sut.close()
4840

49-
def copy_results(self):
50-
self.log.info("Copying results")
51-
pod = get_mission(f"{self.simln_exec} mission")[0]
52-
self.wait_for_gossip_sync(2)
53-
wait_for_pod(pod.metadata.name, 60)
54-
55-
log_resp = pod_log(pod.metadata.name, f"{self.simln_exec} primary-container")
56-
self.log.info(log_resp.data.decode("utf-8"))
41+
def deploy_with_plugin(self):
42+
self.log.info("Deploy the ln network with a SimLN plugin")
43+
results = self.warnet(f"deploy {self.network_dir}")
44+
self.log.info(results)
45+
wait_for_pod(self.get_first_simln_pod())
5746

58-
partial_func = partial(self.found_results_remotely, pod.metadata.name)
47+
def copy_results(self):
48+
pod = self.get_first_simln_pod()
49+
partial_func = partial(self.found_results_remotely, pod)
5950
self.wait_for_predicate(partial_func)
6051

61-
download(pod.metadata.name, Path("/working/results"), Path("."), pod.metadata.namespace)
52+
download(pod, Path("/working/results"), Path("."))
6253
self.wait_for_predicate(self.found_results_locally)
6354

64-
def run_activity(self):
65-
cmd = f"{self.simln_exec} get-example-activity"
66-
self.log.info(f"Activity: {cmd}")
67-
activity_result = run_command(cmd)
68-
activity = json.loads(activity_result)
69-
pod_result = run_command(f"{self.simln_exec} launch-activity '{json.dumps(activity)}'")
70-
self.log.info(f"launched activity: {pod_result}")
71-
partial_func = partial(self.found_results_remotely, pod_result.strip())
72-
self.wait_for_predicate(partial_func)
73-
self.log.info("Successfully ran activity")
74-
7555
def wait_for_gossip_sync(self, expected: int):
7656
self.log.info(f"Waiting for sync (expecting {expected})...")
7757
current = 0
@@ -88,9 +68,7 @@ def wait_for_gossip_sync(self, expected: int):
8868

8969
def found_results_remotely(self, pod: Optional[str] = None) -> bool:
9070
if pod is None:
91-
pod_names_literal = run_command(f"{self.simln_exec} list-pod-names")
92-
pod_names = ast.literal_eval(pod_names_literal)
93-
pod = pod_names[0]
71+
pod = self.get_first_simln_pod()
9472
self.log.info(f"Checking for results file in {pod}")
9573
results_file = run_command(f"{self.simln_exec} sh {pod} ls /working/results").strip()
9674
self.log.info(f"Results file: {results_file}")
@@ -100,6 +78,13 @@ def found_results_remotely(self, pod: Optional[str] = None) -> bool:
10078
self.log.info(results)
10179
return results.find("Success") > 0
10280

81+
def get_first_simln_pod(self):
82+
command = f"{self.simln_exec} list-pod-names"
83+
pod_names_literal = run_command(command)
84+
self.log.info(f"{command}: {pod_names_literal}")
85+
pod_names = ast.literal_eval(pod_names_literal)
86+
return pod_names[0]
87+
10388
def found_results_locally(self) -> bool:
10489
directory = "results"
10590
self.log.info(f"Searching {directory}")

0 commit comments

Comments
 (0)