Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
88 changes: 88 additions & 0 deletions src/scenarios/get_service_ip.py
Original file line number Diff line number Diff line change
@@ -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()
24 changes: 24 additions & 0 deletions src/scenarios/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
from ipaddress import IPv4Address, IPv6Address, ip_address
from kubernetes import client, config
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the utils file should be importing backend stuff. I feel like this dependency indicates that get_service_ip() is not a utility and should probably be implemented in kubernetes_backend.py

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could make get_service_ip into an abstract method similar to get_tank_ipv4. Then rename it to resolve_tank_dns to make it less k8s specific.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah or even use @Property to get (and then cache) the IP:

    @property
    def ipv4(self):
        if self._ipv4 is None:
            self._ipv4 = generate_ipv4_addr(self.warnet.subnet)
        return self._ipv4



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)
2 changes: 1 addition & 1 deletion src/templates/rpc/rbac-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
86 changes: 86 additions & 0 deletions test/data/permutations.graphml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version='1.0' encoding='utf-8'?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="image" for="node" attr.name="image" attr.type="string" />
<key id="collect_logs" for="node" attr.name="collect_logs" attr.type="boolean" />
<key id="exporter" for="node" attr.name="exporter" attr.type="boolean" />
<key id="build_args" for="node" attr.name="build_args" attr.type="string" />
<key id="tc_netem" for="node" attr.name="tc_netem" attr.type="string" />
<key id="bitcoin_config" for="node" attr.name="bitcoin_config" attr.type="string" />
<key id="version" for="node" attr.name="version" attr.type="string" />
<graph edgedefault="directed">
<node id="0">
<data key="version">26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<node id="1">
<data key="image">bitcoindevproject/bitcoin:26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<node id="2">
<data key="version">26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<node id="3">
<data key="version">26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<node id="4">
<data key="version">26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<node id="5">
<data key="version">26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<node id="6">
<data key="version">26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<node id="7">
<data key="version">26.0</data>
<data key="bitcoin_config" />
<data key="tc_netem" />
<data key="build_args" />
<data key="exporter">False</data>
<data key="collect_logs">False</data>
</node>
<edge source="0" target="2" id="0" />
<edge source="1" target="2" id="0" />
<edge source="1" target="3" id="0" />
<edge source="2" target="3" id="0" />
<edge source="2" target="4" id="0" />
<edge source="3" target="5" id="0" />
<edge source="5" target="4" id="0" />
<edge source="5" target="6" id="0" />
<edge source="6" target="7" id="0" />
</graph>
</graphml>
34 changes: 34 additions & 0 deletions test/get_service_ip_test.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion test/scenarios_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down