diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32742b7a8..0a33d2d3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: strategy: matrix: backend: [compose, k8s] - test: [scenarios_test.py, rpc_test.py, graph_test.py, ln_test.py] + test: [scenarios_test.py, rpc_test.py, graph_test.py, ln_test.py, get_service_ip_test.py] steps: - uses: actions/checkout@v4 - if: matrix.backend == 'compose' diff --git a/src/scenarios/get_service_ip.py b/src/scenarios/get_service_ip.py new file mode 100644 index 000000000..7b07b243f --- /dev/null +++ b/src/scenarios/get_service_ip.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +from time import sleep + +from scenarios.utils import ensure_miner, get_service_ip +from warnet.test_framework_bridge import WarnetTestFramework + + +def cli_help(): + return "Test getting ip addresses from services" + + +class GetServiceIp(WarnetTestFramework): + def set_test_params(self): + # This is just a minimum + self.num_nodes = 8 + + def add_options(self, parser): + parser.add_argument( + "--network_name", + dest="network_name", + default="warnet", + help="", + ) + + def run_test(self): + while not self.warnet.network_connected(): + sleep(1) + + # All permutations of directed graph with zero, one, or two inputs/outputs + # + # | Node | In | Out | Con In | Con Out | + # |------+----+-----+--------+---------| + # | A 0 | 0 | 1 | - | C | + # | B 1 | 0 | 2 | - | C, D | + # | C 2 | 2 | 2 | A, B | D, E | + # | D 3 | 2 | 1 | B, C | F | + # | E 4 | 2 | 0 | C, F | - | + # | F 5 | 1 | 2 | D | E, G | + # | G 6 | 1 | 1 | F | H | + # | H 7 | 1 | 0 | G | - | + # + # ╭──────> E ╭──────> 4 + # │ ∧ │ ∧ + # A ─> C ─┤ │ 0 ─> 2 ─┤ │ + # ∧ ╰─> D ─> F ─> G ─> H ∧ ╰─> 3 ─> 5 ─> 6 ─> 7 + # │ ∧ │ ∧ + # B ───┴──────╯ 1 ───┴──────╯ + + zero_external, zero_internal = get_service_ip(f"{self.options.network_name}-tank-000000-service") + one_external, one_internal = get_service_ip(f"{self.options.network_name}-tank-000001-service") + two_external, two_internal = get_service_ip(f"{self.options.network_name}-tank-000002-service") + three_external, three_internal = get_service_ip(f"{self.options.network_name}-tank-000003-service") + five_external, five_internal = get_service_ip(f"{self.options.network_name}-tank-000005-service") + six_external, six_internal = get_service_ip(f"{self.options.network_name}-tank-000006-service") + + zero_peers = self.nodes[0].getpeerinfo() + one_peers = self.nodes[1].getpeerinfo() + two_peers = self.nodes[2].getpeerinfo() + three_peers = self.nodes[3].getpeerinfo() + four_peers = self.nodes[4].getpeerinfo() + five_peers = self.nodes[5].getpeerinfo() + six_peers = self.nodes[6].getpeerinfo() + seven_peers = self.nodes[7].getpeerinfo() + + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000002-service" for d in zero_peers), f"Could not find {self.options.network_name}-tank-000002-service" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000002-service" for d in one_peers), f"Could not find {self.options.network_name}-tank-000002-service" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000003-service" for d in one_peers), f"Could not find {self.options.network_name}-tank-000003-service" + assert any(d.get("addr").split(":")[0] == str(zero_internal) for d in two_peers), f"Could not find {zero_internal}" + assert any(d.get("addr").split(":")[0] == str(one_internal) for d in two_peers), f"Could not find {one_internal}" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000003-service" for d in two_peers), f"Could not find {self.options.network_name}-tank-000003-service" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000004-service" for d in two_peers), f"Could not find {self.options.network_name}-tank-000004-service" + assert any(d.get("addr").split(":")[0] == str(one_internal) for d in three_peers), f"Could not find {one_internal}" + assert any(d.get("addr").split(":")[0] == str(two_internal) for d in three_peers), f"Could not find {two_internal}" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000005-service" for d in three_peers), f"Could not find {self.options.network_name}-tank-000005-service" + assert any(d.get("addr").split(":")[0] == str(two_internal) for d in four_peers), f"Could not find {two_internal}" + assert any(d.get("addr").split(":")[0] == str(five_internal) for d in four_peers), f"Could not find {five_internal}" + assert any(d.get("addr").split(":")[0] == str(three_internal) for d in five_peers), f"Could not find {three_internal}" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000004-service" for d in five_peers), f"Could not find {self.options.network_name}-tank-000004-service" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000006-service" for d in five_peers), f"Could not find {self.options.network_name}-tank-000006-service" + assert any(d.get("addr").split(":")[0] == str(five_internal) for d in six_peers), f"Could not find {five_internal}" + assert any(d.get("addr").split(":")[0] == f"{self.options.network_name}-tank-000007-service" for d in six_peers), f"Could not find {self.options.network_name}-tank-000007-service" + assert any(d.get("addr").split(":")[0] == str(six_internal) for d in seven_peers), f"Could not find {seven_peers}" + + self.log.info("Successfully ran the get_service_ip scenario.") + +if __name__ == "__main__": + GetServiceIp().main() diff --git a/src/scenarios/utils.py b/src/scenarios/utils.py index b0204d461..91931302b 100644 --- a/src/scenarios/utils.py +++ b/src/scenarios/utils.py @@ -1,5 +1,29 @@ +from ipaddress import IPv4Address, IPv6Address, ip_address +from kubernetes import client, config + + def ensure_miner(node): wallets = node.listwallets() if "miner" not in wallets: node.createwallet("miner", descriptors=True) return node.get_wallet_rpc("miner") + + +def get_service_ip(service_name: str) -> (IPv4Address | IPv6Address, IPv4Address | IPv6Address): + """Given a service name and namespace, returns the service's external ip and internal ip""" + # https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Endpoints.md + config.load_incluster_config() + v1 = client.CoreV1Api() + service = v1.read_namespaced_service(name=service_name, namespace="warnet") + endpoints = v1.read_namespaced_endpoints(name=service_name, namespace="warnet") + + try: + initial_subset = endpoints.subsets[0] + except IndexError: + raise f"{service_name}'s endpoint does not have an initial subset" + try: + initial_address = initial_subset.addresses[0] + except IndexError: + raise f"{service_name}'s initial subset does not have an initial address" + + return ip_address(service.spec.cluster_ip), ip_address(initial_address.ip) diff --git a/src/templates/rpc/rbac-config.yaml b/src/templates/rpc/rbac-config.yaml index 06bbfc480..71f38201d 100644 --- a/src/templates/rpc/rbac-config.yaml +++ b/src/templates/rpc/rbac-config.yaml @@ -8,7 +8,7 @@ rules: resources: [pods] verbs: [get, list, create, update, patch, delete] - apiGroups: [""] - resources: [services] + resources: [services, endpoints] verbs: [get, list, create, update, patch, delete] - apiGroups: [""] resources: [pods/exec] diff --git a/test/data/permutations.graphml b/test/data/permutations.graphml new file mode 100644 index 000000000..fa2514d54 --- /dev/null +++ b/test/data/permutations.graphml @@ -0,0 +1,86 @@ + + + + + + + + + + + + 26.0 + + + + False + False + + + bitcoindevproject/bitcoin:26.0 + + + + False + False + + + 26.0 + + + + False + False + + + 26.0 + + + + False + False + + + 26.0 + + + + False + False + + + 26.0 + + + + False + False + + + 26.0 + + + + False + False + + + 26.0 + + + + False + False + + + + + + + + + + + + \ No newline at end of file diff --git a/test/get_service_ip_test.py b/test/get_service_ip_test.py new file mode 100755 index 000000000..51a203b3a --- /dev/null +++ b/test/get_service_ip_test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import os +import time +from pathlib import Path + +from test_base import TestBase + +graph_file_path = Path(os.path.dirname(__file__)) / "data" / "permutations.graphml" + +base = TestBase() + +if base.backend == "k8s": + base.start_server() + print(base.warcli(f"network start {graph_file_path}")) + base.wait_for_all_tanks_status(target="running") + base.wait_for_all_edges() + + # Start scenario + base.warcli(f"scenarios run get_service_ip --network_name={base.network_name}") + + counter = 0 + while (len(base.rpc("scenarios_list_running")) == 1 + and base.rpc("scenarios_list_running")[0]["active"]): + time.sleep(1) + counter += 1 + if counter > 30: + pid = base.rpc("scenarios_list_running")[0]['pid'] + base.warcli(f"scenarios stop {pid}", False) + assert counter < 30 +else: + print(f"get_service_ip_test does not test {base.backend}") + +base.stop_server() diff --git a/test/scenarios_test.py b/test/scenarios_test.py index cbb4a1964..b5f447a80 100755 --- a/test/scenarios_test.py +++ b/test/scenarios_test.py @@ -14,7 +14,7 @@ # Use rpc instead of warcli so we get raw JSON object scenarios = base.rpc("scenarios_available") -assert len(scenarios) == 4 +assert len(scenarios) == 5 # Start scenario base.warcli("scenarios run miner_std --allnodes --interval=1")