Skip to content

Commit bad42be

Browse files
committed
address Max's feedback
- Fix multithreading on macos - Create a separate network file to test the plugin system - Create useful messages in the console when doing plugin operations - Avoid reading the yaml file; instead pass along only those values needed
1 parent 945910b commit bad42be

File tree

8 files changed

+189
-93
lines changed

8 files changed

+189
-93
lines changed

resources/plugins/simln/plugin.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
from pathlib import Path
66

77
import click
8-
import yaml
98
from kubernetes.stream import stream
109

1110
# When we want to select pods based on their role in Warnet, we use "mission" tags. The "mission"
1211
# tag for "lightning" nodes is stored in LIGHTNING_MISSION.
13-
from warnet.constants import LIGHTNING_MISSION, HookValue
12+
from warnet.constants import LIGHTNING_MISSION, PLUGIN_ANNEX, AnnexMember, HookValue, WarnetContent
1413
from warnet.k8s import (
1514
download,
1615
get_default_namespace,
@@ -61,39 +60,35 @@ def simln(ctx):
6160
# value and a variable number of arguments which is used by, for example, preNode and postNode to
6261
# pass along node names.
6362
@simln.command()
64-
@click.argument("network_file_path", type=str)
65-
@click.argument("hook_value", type=str)
66-
@click.argument("namespace", type=str)
67-
@click.argument("nargs", nargs=-1)
63+
@click.argument("plugin_content", type=str)
64+
@click.argument("warnet_content", type=str)
6865
@click.pass_context
69-
def entrypoint(ctx, network_file_path: str, hook_value: str, namespace: str, nargs):
66+
def entrypoint(ctx, plugin_content: str, warnet_content: str):
7067
"""Plugin entrypoint"""
68+
plugin_content: dict = json.loads(plugin_content)
69+
warnet_content: dict = json.loads(warnet_content)
70+
71+
hook_value = warnet_content.get(WarnetContent.HOOK_VALUE.value)
72+
7173
assert hook_value in {
7274
item.value for item in HookValue
7375
}, f"{hook_value} is not a valid HookValue"
7476

75-
network_file_path = Path(network_file_path)
76-
77-
with network_file_path.open() as f:
78-
network_file = yaml.safe_load(f) or {}
79-
if not isinstance(network_file, dict):
80-
raise ValueError(f"Invalid network file structure: {network_file_path}")
81-
82-
plugins_section = network_file.get("plugins", {})
83-
hook_section = plugins_section.get(hook_value, {})
77+
if warnet_content.get(PLUGIN_ANNEX):
78+
for annex_member in [annex_item for annex_item in warnet_content.get(PLUGIN_ANNEX)]:
79+
assert annex_member in {
80+
item.value for item in AnnexMember
81+
}, f"{annex_member} is not a valid AnnexMember"
8482

85-
plugin_name = Path(__file__).resolve().parent.stem
86-
plugin_data = hook_section.get(plugin_name)
87-
if not plugin_data:
88-
raise PluginError(f"Could not find {plugin_name} in {network_file_path}")
83+
warnet_content[WarnetContent.HOOK_VALUE.value] = HookValue(hook_value)
8984

90-
_entrypoint(ctx, plugin_data, HookValue(hook_value), namespace, nargs)
85+
_entrypoint(ctx, plugin_content, warnet_content)
9186

9287

93-
def _entrypoint(ctx, plugin_data: dict, hook_value: HookValue, namespace: str, nargs):
88+
def _entrypoint(ctx, plugin_content: dict, warnet_content: dict):
9489
"""Called by entrypoint"""
9590
# write your plugin startup commands here
96-
activity = plugin_data.get("activity")
91+
activity = plugin_content.get("activity")
9792
activity = json.loads(activity)
9893
print(activity)
9994
_launch_activity(activity, ctx.obj.get(PLUGIN_DIR_TAG))

src/warnet/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ class HookValue(Enum):
3636
POST_NETWORK = "postNetwork"
3737

3838

39+
class WarnetContent(Enum):
40+
HOOK_VALUE = "hook_value"
41+
NAMESPACE = "namespace"
42+
ANNEX = "annex"
43+
44+
45+
class AnnexMember(Enum):
46+
NODE_NAME = "node_name"
47+
48+
49+
PLUGIN_ANNEX = "annex"
50+
51+
3952
# Directories and files for non-python assets, e.g., helm charts, example scenarios, default configs
4053
SRC_DIR = files("warnet")
4154
RESOURCES_DIR = files("resources")

src/warnet/deploy.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import subprocess
23
import sys
34
import tempfile
@@ -22,9 +23,12 @@
2223
NAMESPACES_CHART_LOCATION,
2324
NAMESPACES_FILE,
2425
NETWORK_FILE,
26+
PLUGIN_ANNEX,
2527
SCENARIOS_DIR,
2628
WARGAMES_NAMESPACE_PREFIX,
29+
AnnexMember,
2730
HookValue,
31+
WarnetContent,
2832
)
2933
from .control import _run
3034
from .k8s import (
@@ -132,7 +136,7 @@ def _deploy(directory, debug, namespace, to_all_users):
132136
)
133137

134138

135-
def run_plugins(directory, hook_value: HookValue, namespace, *args):
139+
def run_plugins(directory, hook_value: HookValue, namespace, annex: Optional[dict] = None):
136140
"""Run the plugin commands within a given hook value"""
137141

138142
network_file_path = directory / NETWORK_FILE
@@ -146,29 +150,44 @@ def run_plugins(directory, hook_value: HookValue, namespace, *args):
146150

147151
plugins_section = network_file.get("plugins", {})
148152
hook_section = plugins_section.get(hook_value.value, {})
149-
for plugin_cmd in hook_section.items():
150-
match plugin_cmd:
153+
for plugin_name, plugin_content in hook_section.items():
154+
match (plugin_name, plugin_content):
151155
case (str(), dict()):
152156
try:
153-
entrypoint_path = Path(plugin_cmd[1].get("entrypoint"))
157+
entrypoint_path = Path(plugin_content.get("entrypoint"))
154158
except Exception as err:
155159
raise SyntaxError("Each plugin must have an 'entrypoint'") from err
156160

157-
cmd = f"{network_file_path.parent / entrypoint_path / Path('plugin.py')} entrypoint {network_file_path} {hook_value.value} {namespace} {' '.join(map(str, args))}"
161+
warnet_content = {
162+
WarnetContent.HOOK_VALUE.value: hook_value.value,
163+
WarnetContent.NAMESPACE.value: namespace,
164+
PLUGIN_ANNEX: annex,
165+
}
166+
167+
cmd = (
168+
f"{network_file_path.parent / entrypoint_path / Path('plugin.py')} entrypoint "
169+
f"'{json.dumps(plugin_content)}' '{json.dumps(warnet_content)}'"
170+
)
171+
print(
172+
f"Queuing {hook_value.value} plugin command: {plugin_name} with {plugin_content}"
173+
)
174+
158175
process = Process(target=run_command, args=(cmd,))
159176
processes.append(process)
160177

161178
case _:
162179
print(
163-
f"The following plugin command does not match known plugin command structures: {plugin_cmd}"
180+
f"The following plugin command does not match known plugin command structures: {plugin_name} {plugin_content}"
164181
)
165182
sys.exit(1)
166183

184+
print(f"Starting {hook_value.value} plugins")
167185
for process in processes:
168186
process.start()
169187

170188
for process in processes:
171189
process.join()
190+
print(f"Completed {hook_value.value} plugins")
172191

173192

174193
def check_logging_required(directory: Path):
@@ -385,13 +404,20 @@ def deploy_single_node(node, directory: Path, debug: bool, namespace: str):
385404
temp_override_file_path = Path(temp_file.name)
386405
cmd = f"{cmd} -f {temp_override_file_path}"
387406

388-
run_plugins(directory, HookValue.PRE_NODE, namespace, node_name)
407+
run_plugins(
408+
directory, HookValue.PRE_NODE, namespace, annex={AnnexMember.NODE_NAME.value: node_name}
409+
)
389410

390411
if not stream_command(cmd):
391412
click.echo(f"Failed to run Helm command: {cmd}")
392413
return
393414

394-
run_plugins(directory, HookValue.POST_NODE, namespace, node_name)
415+
run_plugins(
416+
directory,
417+
HookValue.POST_NODE,
418+
namespace,
419+
annex={AnnexMember.NODE_NAME.value: node_name},
420+
)
395421

396422
except Exception as e:
397423
click.echo(f"Error: {e}")

test/data/ln/network.yaml

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -52,37 +52,3 @@ nodes:
5252
- tank-0000
5353
ln:
5454
lnd: true
55-
56-
plugins:
57-
preDeploy:
58-
hello:
59-
entrypoint: "../plugins/hello"
60-
podName: "hello-pre-deploy"
61-
helloTo: "preDeploy!"
62-
postDeploy:
63-
simln:
64-
entrypoint: "../../../resources/plugins/simln"
65-
activity: '[{"source": "tank-0003-ln", "destination": "tank-0005-ln", "interval_secs": 1, "amount_msat": 2000}]'
66-
hello:
67-
entrypoint: "../plugins/hello"
68-
podName: "hello-post-deploy"
69-
helloTo: "postDeploy!"
70-
preNode:
71-
hello:
72-
entrypoint: "../plugins/hello"
73-
helloTo: "preNode!"
74-
postNode:
75-
hello:
76-
entrypoint: "../plugins/hello"
77-
helloTo: "postNode!"
78-
preNetwork:
79-
hello:
80-
entrypoint: "../plugins/hello"
81-
helloTo: "preNetwork!"
82-
podName: "hello-pre-network"
83-
postNetwork:
84-
hello:
85-
entrypoint: "../plugins/hello"
86-
helloTo: "postNetwork!"
87-
podName: "hello-post-network"
88-

test/data/plugins/hello/plugin.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#!/usr/bin/env python3
2+
import json
23
import logging
34
from pathlib import Path
45
from typing import Optional
56

67
import click
7-
import yaml
88
from kubernetes.stream import stream
99

10-
from warnet.constants import HookValue
10+
from warnet.constants import PLUGIN_ANNEX, AnnexMember, HookValue, WarnetContent
1111
from warnet.k8s import (
1212
get_default_namespace,
1313
get_static_client,
@@ -45,59 +45,59 @@ def hello(ctx):
4545

4646

4747
@hello.command()
48-
@click.argument("network_file_path", type=str)
49-
@click.argument("hook_value", type=str)
50-
@click.argument("namespace", type=str)
51-
@click.argument("nargs", nargs=-1)
48+
@click.argument("plugin_content", type=str)
49+
@click.argument("warnet_content", type=str)
5250
@click.pass_context
53-
def entrypoint(ctx, network_file_path: str, hook_value: str, namespace: str, nargs):
51+
def entrypoint(ctx, plugin_content: str, warnet_content: str):
5452
"""Plugin entrypoint"""
53+
plugin_content: dict = json.loads(plugin_content)
54+
warnet_content: dict = json.loads(warnet_content)
55+
56+
hook_value = warnet_content.get(WarnetContent.HOOK_VALUE.value)
57+
5558
assert hook_value in {
5659
item.value for item in HookValue
5760
}, f"{hook_value} is not a valid HookValue"
5861

59-
network_file_path = Path(network_file_path)
60-
61-
with network_file_path.open() as f:
62-
network_file = yaml.safe_load(f) or {}
63-
if not isinstance(network_file, dict):
64-
raise ValueError(f"Invalid network file structure: {network_file_path}")
62+
if warnet_content.get(PLUGIN_ANNEX):
63+
for annex_member in [annex_item for annex_item in warnet_content.get(PLUGIN_ANNEX)]:
64+
assert annex_member in {
65+
item.value for item in AnnexMember
66+
}, f"{annex_member} is not a valid AnnexMember"
6567

66-
plugins_section = network_file.get("plugins", {})
67-
hook_section = plugins_section.get(hook_value, {})
68+
warnet_content[WarnetContent.HOOK_VALUE.value] = HookValue(hook_value)
6869

69-
plugin_name = Path(__file__).resolve().parent.stem
70-
plugin_data = hook_section.get(plugin_name)
71-
if not plugin_data:
72-
raise PluginError(f"Could not find {plugin_name} in {network_file_path}")
70+
_entrypoint(ctx, plugin_content, warnet_content)
7371

74-
_entrypoint(ctx, plugin_data, HookValue(hook_value), namespace, nargs)
7572

76-
77-
def _entrypoint(ctx, plugin_data: dict, hook_value: HookValue, namespace: str, nargs):
73+
def _entrypoint(ctx, plugin_content: dict, warnet_content: dict):
7874
"""Called by entrypoint"""
75+
hook_value = warnet_content[WarnetContent.HOOK_VALUE.value]
76+
7977
match hook_value:
8078
case (
8179
HookValue.PRE_NETWORK
8280
| HookValue.POST_NETWORK
8381
| HookValue.PRE_DEPLOY
8482
| HookValue.POST_DEPLOY
8583
):
86-
data = get_data(plugin_data)
84+
data = get_data(plugin_content)
8785
if data:
8886
_launch_pod(ctx, install_name=hook_value.value.lower() + "-hello", **data)
8987
else:
9088
_launch_pod(ctx, install_name=hook_value.value.lower() + "-hello")
9189
case HookValue.PRE_NODE:
92-
name = nargs[0] + "-pre-hello-pod"
90+
name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-pre-hello-pod"
9391
_launch_pod(ctx, install_name=hook_value.value.lower() + "-" + name, podName=name)
9492
case HookValue.POST_NODE:
95-
name = nargs[0] + "-post-hello-pod"
93+
name = warnet_content[PLUGIN_ANNEX][AnnexMember.NODE_NAME.value] + "-post-hello-pod"
9694
_launch_pod(ctx, install_name=hook_value.value.lower() + "-" + name, podName=name)
9795

9896

99-
def get_data(plugin_data: dict) -> Optional[dict]:
100-
data = {key: plugin_data.get(key) for key in ("podName", "helloTo") if plugin_data.get(key)}
97+
def get_data(plugin_content: dict) -> Optional[dict]:
98+
data = {
99+
key: plugin_content.get(key) for key in ("podName", "helloTo") if plugin_content.get(key)
100+
}
101101
return data or None
102102

103103

0 commit comments

Comments
 (0)