Skip to content

Make test_framework available for users when creating scenarios #619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ build-backend = "setuptools.build_meta"
include-package-data = true

[tool.setuptools.packages.find]
where = ["src", "."]
where = ["src", ".", "resources/scenarios"]
include = ["warnet*", "test_framework*", "resources*"]

[tool.setuptools.package-data]
Expand Down
8 changes: 4 additions & 4 deletions resources/charts/commander/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "commander.fullname" . }}-scenario
name: {{ include "commander.fullname" . }}-warnet
labels:
{{- include "commander.labels" . | nindent 4 }}
binaryData:
scenario.py: {{ .Values.scenario }}
warnet.json: {{ .Values.warnet }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "commander.fullname" . }}-warnet
name: {{ include "commander.fullname" . }}-archive
labels:
{{- include "commander.labels" . | nindent 4 }}
binaryData:
warnet.json: {{ .Values.warnet }}
archive.pyz: {{ .Values.archive }}
18 changes: 9 additions & 9 deletions resources/charts/commander/templates/pod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ spec:
restartPolicy: {{ .Values.restartPolicy }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
image: python:3.12-slim
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c"]
args:
- |
python3 /scenario.py {{ .Values.args }}
python3 /archive.pyz {{ .Values.args }}
volumeMounts:
- name: scenario
mountPath: /scenario.py
subPath: scenario.py
- name: warnet
mountPath: /warnet.json
subPath: warnet.json
- name: archive
mountPath: /archive.pyz
subPath: archive.pyz
volumes:
- name: scenario
configMap:
name: {{ include "commander.fullname" . }}-scenario
- name: warnet
configMap:
name: {{ include "commander.fullname" . }}-warnet
- name: archive
configMap:
name: {{ include "commander.fullname" . }}-archive
10 changes: 2 additions & 8 deletions resources/charts/commander/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ namespace: warnet

restartPolicy: Never

image:
repository: bitcoindevproject/warnet-commander
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "latest"

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
Expand Down Expand Up @@ -71,8 +65,8 @@ volumeMounts: []

port:

scenario: ""

warnet: ""

archive: ""

args: ""
11 changes: 0 additions & 11 deletions resources/images/commander/Dockerfile

This file was deleted.

2 changes: 1 addition & 1 deletion resources/scenarios/commander.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from test_framework.test_node import TestNode
from test_framework.util import PortSeed, get_rpc_proxy

WARNET_FILE = Path(os.path.dirname(__file__)) / "warnet.json"
WARNET_FILE = Path("/warnet.json")

try:
with open(WARNET_FILE) as file:
Expand Down
9 changes: 2 additions & 7 deletions resources/scenarios/ln_init.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
#!/usr/bin/env python3

from time import sleep

# The base class exists inside the commander container
try:
from commander import Commander
except ImportError:
from resources.scenarios.commander import Commander
from commander import Commander


class LNInit(Commander):
Expand Down Expand Up @@ -185,5 +180,5 @@ def funded_lnnodes():
)


if __name__ == "__main__":
def main():
LNInit().main()
9 changes: 2 additions & 7 deletions resources/scenarios/miner_std.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
#!/usr/bin/env python3

from time import sleep

# The base class exists inside the commander container
try:
from commander import Commander
except ImportError:
from resources.scenarios.commander import Commander
from commander import Commander


class Miner:
Expand Down Expand Up @@ -72,5 +67,5 @@ def run_test(self):
sleep(self.options.interval)


if __name__ == "__main__":
def main():
MinerStd().main()
9 changes: 2 additions & 7 deletions resources/scenarios/reconnaissance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@

import socket

# The base class exists inside the commander container when deployed,
# but requires a relative path inside the python source code for other functions.
try:
from commander import Commander
except ImportError:
from resources.scenarios.commander import Commander
from commander import Commander

# The entire Bitcoin Core test_framework directory is available as a library
from test_framework.messages import MSG_TX, CInv, hash256, msg_getdata
Expand Down Expand Up @@ -85,5 +80,5 @@ def run_test(self):
self.log.info(f"Got notfound message from {dstaddr}:{dstport}")


if __name__ == "__main__":
def main():
Reconnaissance().main()
8 changes: 2 additions & 6 deletions resources/scenarios/signet_miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@
# we use the authproxy from the test framework.
###

# The base class exists inside the commander container
try:
from commander import Commander
except ImportError:
from resources.scenarios.commander import Commander
from commander import Commander

import json
import logging
Expand Down Expand Up @@ -566,5 +562,5 @@ def get_args(parser):

return args

if __name__ == "__main__":
def main():
SignetMinerScenario().main()
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ def run_test(self):
raise Exception("Failed execution!")


if __name__ == "__main__":
def main():
Failure().main()
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,5 @@ def assert_connection(self, connector, connectee_index, connection_type: Connect
raise ValueError("ConnectionType must be of type DNS or IP")


if __name__ == "__main__":
def main():
ConnectDag().main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ def run_test(self):
p2p_block_store.wait_until(lambda: p2p_block_store.blocks[best_block] == 1)


if __name__ == "__main__":
def main():
GetdataTest().main()
10 changes: 3 additions & 7 deletions resources/scenarios/tx_flood.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
#!/usr/bin/env python3

import threading
from random import choice, randrange
from time import sleep

# The base class exists inside the commander container
try:
from commander import Commander
except ImportError:
from resources.scenarios.commander import Commander
from commander import Commander


class TXFlood(Commander):
Expand Down Expand Up @@ -70,5 +66,5 @@ def run_test(self):
sleep(30)


if __name__ == "__main__":
def main():
TXFlood().main()
2 changes: 1 addition & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
extend-exclude = [
"resources/images/commander/src/test_framework",
"resources/scenarios/test_framework",
"resources/images/exporter/authproxy.py",
"resources/scenarios/signet_miner.py",
"src/test_framework/*",
Expand Down
46 changes: 39 additions & 7 deletions src/warnet/control.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import base64
import io
import json
import os
import subprocess
import sys
import time
import zipapp
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

Expand Down Expand Up @@ -167,11 +169,13 @@ def run(scenario_file: str, additional_args: tuple[str]):
Pass `-- --help` to get individual scenario help
"""
scenario_path = Path(scenario_file).resolve()
scenario_dir = scenario_path.parent
scenario_name = scenario_path.stem

with open(scenario_path, "rb") as file:
scenario_data = base64.b64encode(file.read()).decode()
if additional_args and ("--help" in additional_args or "-h" in additional_args):
return subprocess.run([sys.executable, scenario_path, "--help"])

# Collect tank data for warnet.json
name = f"commander-{scenario_name.replace('_', '')}-{int(time.time())}"
namespace = get_default_namespace()
tankpods = get_mission("tank")
Expand All @@ -188,9 +192,39 @@ def run(scenario_file: str, additional_args: tuple[str]):
for tank in tankpods
]

# Encode warnet data
# Encode tank data for warnet.json
warnet_data = base64.b64encode(json.dumps(tanks).encode()).decode()

# Create in-memory buffer to store python archive instead of writing to disk
archive_buffer = io.BytesIO()

# No need to copy the entire scenarios/ directory into the archive
def filter(path):
if any(
needle in str(path) for needle in [
".pyc",
".csv",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to categorically reject csv files? It's a common format, and I can imagine a user thinking they could use it.

".DS_Store"
]
):
return False
return any(
needle in str(path) for needle in [
"commander.py",
"test_framework",
scenario_name
]
)

# Compile python archive
zipapp.create_archive(
source=scenario_dir, target=archive_buffer, main=f"{scenario_name}:main", compressed=True, filter=filter
)

# Encode the binary data as Base64
archive_buffer.seek(0)
archive_data = base64.b64encode(archive_buffer.read()).decode()

try:
# Construct Helm command
helm_command = [
Expand All @@ -202,16 +236,14 @@ def run(scenario_file: str, additional_args: tuple[str]):
"--set",
f"fullnameOverride={name}",
"--set",
f"scenario={scenario_data}",
"--set",
f"warnet={warnet_data}",
"--set",
f"archive={archive_data}",
]

# Add additional arguments
if additional_args:
helm_command.extend(["--set", f"args={' '.join(additional_args)}"])
if "--help" in additional_args or "-h" in additional_args:
return subprocess.run([sys.executable, scenario_path, "--help"])

helm_command.extend([name, COMMANDER_CHART])

Expand Down
2 changes: 1 addition & 1 deletion test/dag_connection_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def setup_network(self):

def run_connect_dag_scenario(self):
self.log.info("Running connect_dag scenario")
self.warnet("run test/data/scenario_connect_dag.py")
self.warnet("run resources/scenarios/test_connect_dag.py")
self.wait_for_all_scenarios()


Expand Down
4 changes: 2 additions & 2 deletions test/scenarios_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def run_and_check_miner_scenario_from_file(self):
self.stop_scenario()

def run_and_check_scenario_from_file(self):
scenario_file = "test/data/scenario_p2p_interface.py"
scenario_file = "resources/scenarios/test_p2p_interface.py"
self.log.info(f"Running scenario from: {scenario_file}")
self.warnet(f"run {scenario_file}")
self.wait_for_predicate(self.check_scenario_clean_exit)
Expand All @@ -94,7 +94,7 @@ def check_regtest_recon(self):
self.wait_for_predicate(self.check_scenario_clean_exit)

def check_active_count(self):
scenario_file = "test/data/scenario_buggy_failure.py"
scenario_file = "resources/scenarios/test_buggy_failure.py"
self.log.info(f"Running scenario from: {scenario_file}")
self.warnet(f"run {scenario_file}")

Expand Down