diff --git a/resources/graphs/default.graphml b/resources/graphs/default.graphml index ce84579df..a41de3cb3 100644 --- a/resources/graphs/default.graphml +++ b/resources/graphs/default.graphml @@ -18,60 +18,60 @@ 27.0 - -uacomment=w0 + uacomment=w0 true true 27.0 - -uacomment=w1 + uacomment=w1 true true bitcoindevproject/bitcoin:26.0 - -uacomment=w2 -debug=mempool + uacomment=w2 debug=mempool true true 27.0 - -uacomment=w3 + uacomment=w3 true 27.0 - -uacomment=w4 + uacomment=w4 true 27.0 - -uacomment=w5 + uacomment=w5 true 27.0 - -uacomment=w6 + uacomment=w6 27.0 - -uacomment=w7 + uacomment=w7 27.0 - -uacomment=w8 + uacomment=w8 27.0 - -uacomment=w9 + uacomment=w9 27.0 - -uacomment=w10 + uacomment=w10 27.0 - -uacomment=w11 + uacomment=w11 diff --git a/resources/images/commander/Dockerfile b/resources/images/commander/Dockerfile new file mode 100644 index 000000000..243f88a5e --- /dev/null +++ b/resources/images/commander/Dockerfile @@ -0,0 +1,11 @@ +# Use an official Python runtime as the base image +FROM python:3.12-slim + +# Python dependencies +#RUN pip install --no-cache-dir prometheus_client + +# Prometheus exporter script for bitcoind +COPY src / + +# -u: force the stdout and stderr streams to be unbuffered +CMD ["python", "-u", "/scenario.py"] diff --git a/src/test_framework/__init__.py b/resources/images/commander/src/__init__.py similarity index 100% rename from src/test_framework/__init__.py rename to resources/images/commander/src/__init__.py diff --git a/resources/images/commander/src/commander.py b/resources/images/commander/src/commander.py new file mode 100644 index 000000000..dfdb75a31 --- /dev/null +++ b/resources/images/commander/src/commander.py @@ -0,0 +1,392 @@ +import argparse +import configparser +import ipaddress +import json +import logging +import os +import pathlib +import random +import signal +import sys +import tempfile + +from pathlib import Path +from test_framework.authproxy import AuthServiceProxy +from test_framework.p2p import NetworkThread +from test_framework.test_framework import ( + TMPDIR_PREFIX, + BitcoinTestFramework, + TestStatus, +) +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" +with open(WARNET_FILE, "r") as file: + WARNET = json.load(file) + +# Ensure that all RPC calls are made with brand new http connections +def auth_proxy_request(self, method, path, postdata): + self._set_conn() # creates new http client connection + return self.oldrequest(method, path, postdata) +AuthServiceProxy.oldrequest = AuthServiceProxy._request +AuthServiceProxy._request = auth_proxy_request + + +class Commander(BitcoinTestFramework): + # required by subclasses of BitcoinTestFramework + def set_test_params(self): + pass + + def run_test(self): + pass + + # Utility functions for Warnet scenarios + @staticmethod + def ensure_miner(node): + wallets = node.listwallets() + if "miner" not in wallets: + node.createwallet("miner", descriptors=True) + return node.get_wallet_rpc("miner") + + def network_connected(self): + for tank in self.nodes: + peerinfo = tank.getpeerinfo() + manuals = 0 + for peer in peerinfo: + if peer["connection_type"] == "manual": + manuals += 1 + # Even if more edges are specifed, bitcoind only allows + # 8 manual outbound connections + if min(8, len(tank.init_peers)) > manuals: + return False + return True + + def handle_sigterm(self, signum, frame): + print("SIGTERM received, stopping...") + self.shutdown() + sys.exit(0) + + # The following functions are chopped-up hacks of + # the original methods from BitcoinTestFramework + + def setup(self): + signal.signal(signal.SIGTERM, self.handle_sigterm) + + # hacked from _start_logging() + # Scenarios will log plain messages to stdout only, which will can redirected by warnet + self.log = logging.getLogger(self.__class__.__name__) + self.log.setLevel(logging.INFO) # set this to DEBUG to see ALL RPC CALLS + + # Because scenarios run in their own subprocess, the logger here + # is not the same as the warnet server or other global loggers. + # Scenarios log directly to stdout which gets picked up by the + # subprocess manager in the server, and reprinted to the global log. + ch = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter(fmt="%(name)-8s %(message)s") + ch.setFormatter(formatter) + self.log.addHandler(ch) + + for i, tank in enumerate(WARNET): + self.log.info(f"Adding TestNode #{i} from pod {tank['tank']} with IP {tank['rpc_host']}") + node = TestNode( + i, + pathlib.Path(), # datadir path + chain=tank['chain'], + rpchost=tank['rpc_host'], + timewait=60, + timeout_factor=self.options.timeout_factor, + bitcoind=None, + bitcoin_cli=None, + cwd=self.options.tmpdir, + coverage_dir=self.options.coveragedir, + ) + node.rpc = get_rpc_proxy( + f"http://{tank['rpc_user']}:{tank['rpc_password']}@{tank['rpc_host']}:{tank['rpc_port']}", + i, + timeout=60, + coveragedir=self.options.coveragedir, + ) + node.rpc_connected = True + node.init_peers = tank['init_peers'] + self.nodes.append(node) + + self.num_nodes = len(self.nodes) + + # Set up temp directory and start logging + if self.options.tmpdir: + self.options.tmpdir = os.path.abspath(self.options.tmpdir) + os.makedirs(self.options.tmpdir, exist_ok=False) + else: + self.options.tmpdir = tempfile.mkdtemp(prefix=TMPDIR_PREFIX) + + seed = self.options.randomseed + if seed is None: + seed = random.randrange(sys.maxsize) + else: + self.log.info(f"User supplied random seed {seed}") + random.seed(seed) + self.log.info(f"PRNG seed is: {seed}") + + self.log.debug("Setting up network thread") + self.network_thread = NetworkThread() + self.network_thread.start() + + self.success = TestStatus.PASSED + + def parse_args(self): + previous_releases_path = "" + parser = argparse.ArgumentParser(usage="%(prog)s [options]") + parser.add_argument( + "--nocleanup", + dest="nocleanup", + default=False, + action="store_true", + help="Leave bitcoinds and test.* datadir on exit or error", + ) + parser.add_argument( + "--nosandbox", + dest="nosandbox", + default=False, + action="store_true", + help="Don't use the syscall sandbox", + ) + parser.add_argument( + "--noshutdown", + dest="noshutdown", + default=False, + action="store_true", + help="Don't stop bitcoinds after the test execution", + ) + parser.add_argument( + "--cachedir", + dest="cachedir", + default=None, + help="Directory for caching pregenerated datadirs (default: %(default)s)", + ) + parser.add_argument( + "--tmpdir", dest="tmpdir", default=None, help="Root directory for datadirs" + ) + parser.add_argument( + "-l", + "--loglevel", + dest="loglevel", + default="DEBUG", + help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console. Note that logs at all levels are always written to the test_framework.log file in the temporary test directory.", + ) + parser.add_argument( + "--tracerpc", + dest="trace_rpc", + default=False, + action="store_true", + help="Print out all RPC calls as they are made", + ) + parser.add_argument( + "--portseed", + dest="port_seed", + default=0, + help="The seed to use for assigning port numbers (default: current process id)", + ) + parser.add_argument( + "--previous-releases", + dest="prev_releases", + default=None, + action="store_true", + help="Force test of previous releases (default: %(default)s)", + ) + parser.add_argument( + "--coveragedir", + dest="coveragedir", + default=None, + help="Write tested RPC commands into this directory", + ) + parser.add_argument( + "--configfile", + dest="configfile", + default=None, + help="Location of the test framework config file (default: %(default)s)", + ) + parser.add_argument( + "--pdbonfailure", + dest="pdbonfailure", + default=False, + action="store_true", + help="Attach a python debugger if test fails", + ) + parser.add_argument( + "--usecli", + dest="usecli", + default=False, + action="store_true", + help="use bitcoin-cli instead of RPC for all commands", + ) + parser.add_argument( + "--perf", + dest="perf", + default=False, + action="store_true", + help="profile running nodes with perf for the duration of the test", + ) + parser.add_argument( + "--valgrind", + dest="valgrind", + default=False, + action="store_true", + help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown. valgrind 3.14 or later required.", + ) + parser.add_argument( + "--randomseed", + default=0x7761726E6574, # "warnet" ascii + help="set a random seed for deterministically reproducing a previous test run", + ) + parser.add_argument( + "--timeout-factor", + dest="timeout_factor", + default=1, + help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts", + ) + parser.add_argument( + "--network", + dest="network", + default="warnet", + help="Designate which warnet this should run on (default: warnet)", + ) + parser.add_argument( + "--v2transport", + dest="v2transport", + default=False, + action="store_true", + help="use BIP324 v2 connections between all nodes by default", + ) + + self.add_options(parser) + # Running TestShell in a Jupyter notebook causes an additional -f argument + # To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument + # source: https://stackoverflow.com/questions/48796169/how-to-fix-ipykernel-launcher-py-error-unrecognized-arguments-in-jupyter/56349168#56349168 + parser.add_argument("-f", "--fff", help="a dummy argument to fool ipython", default="1") + self.options = parser.parse_args() + if self.options.timeout_factor == 0: + self.options.timeout_factor = 99999 + self.options.timeout_factor = self.options.timeout_factor or ( + 4 if self.options.valgrind else 1 + ) + self.options.previous_releases_path = previous_releases_path + config = configparser.ConfigParser() + if self.options.configfile is not None: + with open(self.options.configfile) as f: + config.read_file(f) + + config["environment"] = {"PACKAGE_BUGREPORT": ""} + + self.config = config + + if "descriptors" not in self.options: + # Wallet is not required by the test at all and the value of self.options.descriptors won't matter. + # It still needs to exist and be None in order for tests to work however. + # So set it to None to force -disablewallet, because the wallet is not needed. + self.options.descriptors = None + elif self.options.descriptors is None: + # Some wallet is either required or optionally used by the test. + # Prefer SQLite unless it isn't available + if self.is_sqlite_compiled(): + self.options.descriptors = True + elif self.is_bdb_compiled(): + self.options.descriptors = False + else: + # If neither are compiled, tests requiring a wallet will be skipped and the value of self.options.descriptors won't matter + # It still needs to exist and be None in order for tests to work however. + # So set it to None, which will also set -disablewallet. + self.options.descriptors = None + + PortSeed.n = self.options.port_seed + + def connect_nodes(self, a, b, *, peer_advertises_v2=None, wait_for_connect: bool = True): + """ + Kwargs: + wait_for_connect: if True, block until the nodes are verified as connected. You might + want to disable this when using -stopatheight with one of the connected nodes, + since there will be a race between the actual connection and performing + the assertions before one node shuts down. + """ + from_connection = self.nodes[a] + to_connection = self.nodes[b] + + to_ip_port = self.warnet.tanks[b].get_dns_addr() + from_ip_port = self.warnet.tanks[a].get_ip_addr() + + if peer_advertises_v2 is None: + peer_advertises_v2 = self.options.v2transport + + if peer_advertises_v2: + from_connection.addnode(node=to_ip_port, command="onetry", v2transport=True) + else: + # skip the optional third argument (default false) for + # compatibility with older clients + from_connection.addnode(to_ip_port, "onetry") + + if not wait_for_connect: + return + + def get_peer_ip(peer): + try: # we encounter a regular ip address + ip_addr = str(ipaddress.ip_address(peer["addr"].split(":")[0])) + return ip_addr + except ValueError as err: # or we encounter a service name + try: + # NETWORK-tank-TANK_INDEX-service + # NETWORK-test-TEST-tank-TANK_INDEX-service + tank_index = int(peer["addr"].split("-")[-2]) + except (ValueError, IndexError) as inner_err: + raise ValueError( + "could not derive tank index from service name: {} {}".format( + peer["addr"], inner_err + ) + ) from err + + ip_addr = self.warnet.tanks[tank_index].get_ip_addr() + return ip_addr + + # poll until version handshake complete to avoid race conditions + # with transaction relaying + # See comments in net_processing: + # * Must have a version message before anything else + # * Must have a verack message before anything else + self.wait_until( + lambda: any( + peer["addr"] == to_ip_port and peer["version"] != 0 + for peer in from_connection.getpeerinfo() + ) + ) + self.wait_until( + lambda: any( + get_peer_ip(peer) == from_ip_port and peer["version"] != 0 + for peer in to_connection.getpeerinfo() + ) + ) + self.wait_until( + lambda: any( + peer["addr"] == to_ip_port and peer["bytesrecv_per_msg"].pop("verack", 0) >= 21 + for peer in from_connection.getpeerinfo() + ) + ) + self.wait_until( + lambda: any( + get_peer_ip(peer) == from_ip_port + and peer["bytesrecv_per_msg"].pop("verack", 0) >= 21 + for peer in to_connection.getpeerinfo() + ) + ) + # The message bytes are counted before processing the message, so make + # sure it was fully processed by waiting for a ping. + self.wait_until( + lambda: any( + peer["addr"] == to_ip_port and peer["bytesrecv_per_msg"].pop("pong", 0) >= 29 + for peer in from_connection.getpeerinfo() + ) + ) + self.wait_until( + lambda: any( + get_peer_ip(peer) == from_ip_port and peer["bytesrecv_per_msg"].pop("pong", 0) >= 29 + for peer in to_connection.getpeerinfo() + ) + ) diff --git a/resources/images/commander/src/test_framework/__init__.py b/resources/images/commander/src/test_framework/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/test_framework/address.py b/resources/images/commander/src/test_framework/address.py similarity index 100% rename from src/test_framework/address.py rename to resources/images/commander/src/test_framework/address.py diff --git a/src/test_framework/authproxy.py b/resources/images/commander/src/test_framework/authproxy.py similarity index 100% rename from src/test_framework/authproxy.py rename to resources/images/commander/src/test_framework/authproxy.py diff --git a/src/test_framework/bdb.py b/resources/images/commander/src/test_framework/bdb.py similarity index 100% rename from src/test_framework/bdb.py rename to resources/images/commander/src/test_framework/bdb.py diff --git a/src/test_framework/bip340_test_vectors.csv b/resources/images/commander/src/test_framework/bip340_test_vectors.csv similarity index 100% rename from src/test_framework/bip340_test_vectors.csv rename to resources/images/commander/src/test_framework/bip340_test_vectors.csv diff --git a/src/test_framework/blockfilter.py b/resources/images/commander/src/test_framework/blockfilter.py similarity index 100% rename from src/test_framework/blockfilter.py rename to resources/images/commander/src/test_framework/blockfilter.py diff --git a/src/test_framework/blocktools.py b/resources/images/commander/src/test_framework/blocktools.py similarity index 100% rename from src/test_framework/blocktools.py rename to resources/images/commander/src/test_framework/blocktools.py diff --git a/src/test_framework/coverage.py b/resources/images/commander/src/test_framework/coverage.py similarity index 100% rename from src/test_framework/coverage.py rename to resources/images/commander/src/test_framework/coverage.py diff --git a/src/test_framework/descriptors.py b/resources/images/commander/src/test_framework/descriptors.py similarity index 100% rename from src/test_framework/descriptors.py rename to resources/images/commander/src/test_framework/descriptors.py diff --git a/src/test_framework/ellswift.py b/resources/images/commander/src/test_framework/ellswift.py similarity index 100% rename from src/test_framework/ellswift.py rename to resources/images/commander/src/test_framework/ellswift.py diff --git a/src/test_framework/ellswift_decode_test_vectors.csv b/resources/images/commander/src/test_framework/ellswift_decode_test_vectors.csv similarity index 100% rename from src/test_framework/ellswift_decode_test_vectors.csv rename to resources/images/commander/src/test_framework/ellswift_decode_test_vectors.csv diff --git a/src/test_framework/key.py b/resources/images/commander/src/test_framework/key.py similarity index 100% rename from src/test_framework/key.py rename to resources/images/commander/src/test_framework/key.py diff --git a/src/test_framework/messages.py b/resources/images/commander/src/test_framework/messages.py similarity index 100% rename from src/test_framework/messages.py rename to resources/images/commander/src/test_framework/messages.py diff --git a/src/test_framework/muhash.py b/resources/images/commander/src/test_framework/muhash.py similarity index 100% rename from src/test_framework/muhash.py rename to resources/images/commander/src/test_framework/muhash.py diff --git a/src/test_framework/netutil.py b/resources/images/commander/src/test_framework/netutil.py similarity index 100% rename from src/test_framework/netutil.py rename to resources/images/commander/src/test_framework/netutil.py diff --git a/src/test_framework/p2p.py b/resources/images/commander/src/test_framework/p2p.py similarity index 100% rename from src/test_framework/p2p.py rename to resources/images/commander/src/test_framework/p2p.py diff --git a/src/test_framework/psbt.py b/resources/images/commander/src/test_framework/psbt.py similarity index 100% rename from src/test_framework/psbt.py rename to resources/images/commander/src/test_framework/psbt.py diff --git a/src/test_framework/ripemd160.py b/resources/images/commander/src/test_framework/ripemd160.py similarity index 100% rename from src/test_framework/ripemd160.py rename to resources/images/commander/src/test_framework/ripemd160.py diff --git a/src/test_framework/script.py b/resources/images/commander/src/test_framework/script.py similarity index 100% rename from src/test_framework/script.py rename to resources/images/commander/src/test_framework/script.py diff --git a/src/test_framework/script_util.py b/resources/images/commander/src/test_framework/script_util.py similarity index 100% rename from src/test_framework/script_util.py rename to resources/images/commander/src/test_framework/script_util.py diff --git a/src/test_framework/secp256k1.py b/resources/images/commander/src/test_framework/secp256k1.py similarity index 100% rename from src/test_framework/secp256k1.py rename to resources/images/commander/src/test_framework/secp256k1.py diff --git a/src/test_framework/segwit_addr.py b/resources/images/commander/src/test_framework/segwit_addr.py similarity index 100% rename from src/test_framework/segwit_addr.py rename to resources/images/commander/src/test_framework/segwit_addr.py diff --git a/src/test_framework/siphash.py b/resources/images/commander/src/test_framework/siphash.py similarity index 100% rename from src/test_framework/siphash.py rename to resources/images/commander/src/test_framework/siphash.py diff --git a/src/test_framework/socks5.py b/resources/images/commander/src/test_framework/socks5.py similarity index 100% rename from src/test_framework/socks5.py rename to resources/images/commander/src/test_framework/socks5.py diff --git a/src/test_framework/test_framework.py b/resources/images/commander/src/test_framework/test_framework.py similarity index 100% rename from src/test_framework/test_framework.py rename to resources/images/commander/src/test_framework/test_framework.py diff --git a/src/test_framework/test_node.py b/resources/images/commander/src/test_framework/test_node.py similarity index 100% rename from src/test_framework/test_node.py rename to resources/images/commander/src/test_framework/test_node.py diff --git a/src/test_framework/test_shell.py b/resources/images/commander/src/test_framework/test_shell.py similarity index 100% rename from src/test_framework/test_shell.py rename to resources/images/commander/src/test_framework/test_shell.py diff --git a/src/test_framework/util.py b/resources/images/commander/src/test_framework/util.py similarity index 100% rename from src/test_framework/util.py rename to resources/images/commander/src/test_framework/util.py diff --git a/src/test_framework/wallet.py b/resources/images/commander/src/test_framework/wallet.py similarity index 100% rename from src/test_framework/wallet.py rename to resources/images/commander/src/test_framework/wallet.py diff --git a/src/test_framework/wallet_util.py b/resources/images/commander/src/test_framework/wallet_util.py similarity index 100% rename from src/test_framework/wallet_util.py rename to resources/images/commander/src/test_framework/wallet_util.py diff --git a/src/test_framework/xswiftec_inv_test_vectors.csv b/resources/images/commander/src/test_framework/xswiftec_inv_test_vectors.csv similarity index 100% rename from src/test_framework/xswiftec_inv_test_vectors.csv rename to resources/images/commander/src/test_framework/xswiftec_inv_test_vectors.csv diff --git a/src/warnet/cli/k8s.py b/src/warnet/cli/k8s.py index a8c7d2da6..ba45c1762 100644 --- a/src/warnet/cli/k8s.py +++ b/src/warnet/cli/k8s.py @@ -21,6 +21,23 @@ def get_pods(): sclient = get_static_client() return sclient.list_namespaced_pod("warnet") +def get_tanks(): + pods = get_pods() + tanks = [] + # TODO: filter tanks only!!!! + for pod in pods.items: + if "rank" in pod.metadata.labels and pod.metadata.labels["rank"] == "tank": + tanks.append({ + "tank": pod.metadata.name, + "chain": "regtest", + "rpc_host": pod.status.pod_ip, + "rpc_port": 18443, + "rpc_user": "user", + "rpc_password": "password", + "init_peers": [] + }) + return tanks + def run_command(command, stream_output=False, env=None): # Merge the current environment with the provided env full_env = os.environ.copy() @@ -61,6 +78,10 @@ def run_command(command, stream_output=False, env=None): return True +def create_namespace() -> dict: + return {"apiVersion": "v1", "kind": "Namespace", "metadata": {"name": "warnet"}} + + def set_kubectl_context(namespace: str): """ Set the default kubectl context to the specified namespace. @@ -80,10 +101,10 @@ def deploy_base_configurations(): "rbac-config.yaml", ] - for config in base_configs: - command = f"kubectl apply -f {WAR_MANIFESTS}/{config}" + for bconfig in base_configs: + command = f"kubectl apply -f {WAR_MANIFESTS}/{bconfig}" if not run_command(command, stream_output=True): - print(f"Failed to apply {config}") + print(f"Failed to apply {bconfig}") return False return True diff --git a/src/warnet/cli/network.py b/src/warnet/cli/network.py index 587101d23..e3917c700 100644 --- a/src/warnet/cli/network.py +++ b/src/warnet/cli/network.py @@ -13,7 +13,8 @@ set_kubectl_context, deploy_base_configurations, apply_kubernetes_yaml, - delete_namespace + delete_namespace, + create_namespace ) DEFAULT_GRAPH_FILE = files("graphs").joinpath("default.graphml") @@ -137,10 +138,6 @@ def generate_kubernetes_yaml(graph: nx.Graph) -> list: return kubernetes_objects -def create_namespace() -> dict: - return {"apiVersion": "v1", "kind": "Namespace", "metadata": {"name": "warnet"}} - - def create_node_deployment(node: int, data: dict) -> dict: image = data.get("image", "bitcoindevproject/bitcoin:27.0") version = data.get("version", "27.0") @@ -149,9 +146,9 @@ def create_node_deployment(node: int, data: dict) -> dict: "apiVersion": "v1", "kind": "Pod", "metadata": { - "name": f"warnet-node-{node}", + "name": f"warnet-tank-{node}", "namespace": "warnet", - "labels": {"app": "warnet", "node": str(node)}, + "labels": {"rank": "tank", "index": str(node)}, }, "spec": { "containers": [ diff --git a/src/warnet/cli/scenarios.py b/src/warnet/cli/scenarios.py index fb40be6e7..255a67f89 100644 --- a/src/warnet/cli/scenarios.py +++ b/src/warnet/cli/scenarios.py @@ -1,15 +1,23 @@ import base64 +import importlib +import json import os import sys - +import time +import tempfile +import yaml import click from rich import print from rich.console import Console from rich.table import Table +from .k8s import ( + get_tanks, + create_namespace, + apply_kubernetes_yaml +) from .rpc import rpc_call - @click.group(name="scenarios") def scenarios(): """Manage scenarios on a running network""" @@ -44,12 +52,85 @@ def run(scenario, network, additional_args): """ Run from the Warnet Test Framework on [network] with optional arguments """ - params = { - "scenario": scenario, - "additional_args": additional_args, - "network": network, - } - print(rpc_call("scenarios_run", params)) + + # Use importlib.resources to get the scenario path + scenario_package = "warnet.scenarios" + scenario_filename = f"{scenario}.py" + + # Ensure the scenario file exists within the package + with importlib.resources.path(scenario_package, scenario_filename) as scenario_path: + scenario_path = str(scenario_path) # Convert Path object to string + + if not os.path.exists(scenario_path): + raise Exception(f"Scenario {scenario} not found at {scenario_path}.") + + with open(scenario_path, "r") as file: + scenario_text = file.read() + + name = f"commander-{scenario.replace('_', '')}-{int(time.time())}" + + tanks = get_tanks() + kubernetes_objects = [create_namespace()] + kubernetes_objects.extend( + [ + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "warnetjson", + "namespace": "warnet", + }, + "data": {"warnet.json": json.dumps(tanks)}, + }, + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "scnaeriopy", + "namespace": "warnet", + }, + "data": {"scenario.py": scenario_text}, + }, + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": name, + "namespace": "warnet", + "labels": {"app": "warnet"}, + }, + "spec": { + "containers": [ + { + "name": name, + "image": "warnet-commander:latest", + "imagePullPolicy": "Never", + "volumeMounts": [ + { + "name": "warnetjson", + "mountPath": "warnet.json", + "subPath": "warnet.json", + }, + { + "name": "scnaeriopy", + "mountPath": "scenario.py", + "subPath": "scenario.py", + } + ], + } + ], + "volumes": [ + {"name": "warnetjson", "configMap": {"name": "warnetjson"}}, + {"name": "scnaeriopy", "configMap": {"name": "scnaeriopy"}} + ], + } + } + ] + ) + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as temp_file: + yaml.dump_all(kubernetes_objects, temp_file) + temp_file_path = temp_file.name + apply_kubernetes_yaml(temp_file_path) @scenarios.command(context_settings={"ignore_unknown_options": True}) diff --git a/src/warnet/scenarios/miner_std.py b/src/warnet/scenarios/miner_std.py index 063e07f5e..73e91a112 100755 --- a/src/warnet/scenarios/miner_std.py +++ b/src/warnet/scenarios/miner_std.py @@ -2,23 +2,22 @@ from time import sleep -from warnet.scenarios.utils import ensure_miner -from warnet.test_framework_bridge import WarnetTestFramework +# The base class exists inside the commander container +from commander import Commander def cli_help(): return "Generate blocks over time. Options: [--allnodes | --interval= | --mature ]" - class Miner: def __init__(self, node, mature): self.node = node - self.wallet = ensure_miner(self.node) + self.wallet = Commander.ensure_miner(self.node) self.addr = self.wallet.getnewaddress() self.mature = mature -class MinerStd(WarnetTestFramework): +class MinerStd(Commander): def set_test_params(self): # This is just a minimum self.num_nodes = 0 @@ -46,7 +45,7 @@ def add_options(self, parser): ) def run_test(self): - while not self.warnet.network_connected(): + while not self.network_connected(): self.log.info("Waiting for complete network connection...") sleep(5) self.log.info("Network connected. Starting miners.") diff --git a/src/warnet/scenarios/utils.py b/src/warnet/scenarios/utils.py deleted file mode 100644 index b0204d461..000000000 --- a/src/warnet/scenarios/utils.py +++ /dev/null @@ -1,5 +0,0 @@ -def ensure_miner(node): - wallets = node.listwallets() - if "miner" not in wallets: - node.createwallet("miner", descriptors=True) - return node.get_wallet_rpc("miner")