Skip to content

Commit 22f9a42

Browse files
committed
add ingress
1 parent 917ee4a commit 22f9a42

File tree

10 files changed

+110
-36
lines changed

10 files changed

+110
-36
lines changed

.github/workflows/test.yml

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ jobs:
7878
with:
7979
cpus: max
8080
memory: 4000m
81+
- name: Start minikube's loadbalancer tunnel
82+
run: minikube tunnel &> /dev/null &
8183
- name: Download commander artifact
8284
uses: actions/download-artifact@v4
8385
with:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
name: caddy-ingress
5+
annotations:
6+
nginx.ingress.kubernetes.io/ssl-redirect: "false"
7+
spec:
8+
ingressClassName: nginx
9+
rules:
10+
- http:
11+
paths:
12+
- path: /
13+
pathType: Prefix
14+
backend:
15+
service:
16+
name: {{ include "caddy.fullname" . }}
17+
port:
18+
number: {{ .Values.port }}

src/warnet/constants.py

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
DEFAULT_NAMESPACE = "warnet"
1515
LOGGING_NAMESPACE = "warnet-logging"
16+
INGRESS_NAMESPACE = "ingress"
1617
HELM_COMMAND = "helm upgrade --install --create-namespace"
1718

1819
# Directories and files for non-python assets, e.g., helm charts, example scenarios, default configs
@@ -35,6 +36,7 @@
3536
NAMESPACES_CHART_LOCATION = CHARTS_DIR.joinpath("namespaces")
3637
FORK_OBSERVER_CHART = str(files("resources.charts").joinpath("fork-observer"))
3738
CADDY_CHART = str(files("resources.charts").joinpath("caddy"))
39+
CADDY_INGRESS_NAME = "caddy-ingress"
3840

3941
DEFAULT_NETWORK = Path("6_node_bitcoin")
4042
DEFAULT_NAMESPACES = Path("two_namespaces_two_users")
@@ -98,3 +100,10 @@
98100
f"helm upgrade --install grafana-dashboards {CHARTS_DIR}/grafana-dashboards --namespace warnet-logging",
99101
f"helm upgrade --install --namespace warnet-logging loki-grafana grafana/grafana --values {MANIFESTS_DIR}/grafana_values.yaml",
100102
]
103+
104+
105+
INGRESS_HELM_COMMANDS = [
106+
"helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx",
107+
"helm repo update",
108+
f"helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx --namespace {INGRESS_NAMESPACE} --create-namespace",
109+
]

src/warnet/control.py

-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from rich.table import Table
1717

1818
from .constants import COMMANDER_CHART, LOGGING_NAMESPACE
19-
from .deploy import _port_stop_internal
2019
from .k8s import (
2120
get_default_namespace,
2221
get_mission,
@@ -140,8 +139,6 @@ def delete_pod(pod_name, namespace):
140139
for future in as_completed(futures):
141140
console.print(f"[yellow]{future.result()}[/yellow]")
142141

143-
# Shutdown any port forwarding
144-
_port_stop_internal("caddy", namespaces[1])
145142
console.print("[bold yellow]Teardown process initiated for all components.[/bold yellow]")
146143
console.print("[bold yellow]Note: Some processes may continue in the background.[/bold yellow]")
147144
console.print("[bold green]Warnet teardown process completed.[/bold green]")

src/warnet/dashboard.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import click
22

3+
from .k8s import get_ingress_ip_or_host, wait_for_ingress_controller
4+
35

46
@click.command()
57
def dashboard():
68
"""Open the Warnet dashboard in default browser"""
79
import webbrowser
810

9-
url = "http://localhost:2019"
11+
wait_for_ingress_controller()
12+
ip = get_ingress_ip_or_host()
13+
14+
if not ip:
15+
click.echo("Error: Could not get the IP address of the dashboard")
16+
click.echo(
17+
"If you are running Minikube please run 'minikube tunnel' in a separate terminal"
18+
)
19+
return
20+
21+
url = f"http://{ip}"
22+
1023
webbrowser.open(url)
11-
click.echo("warnet dashboard opened in default browser")
24+
click.echo("Warnet dashboard opened in default browser")

src/warnet/deploy.py

+18-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import subprocess
32
import sys
43
import tempfile
@@ -14,13 +13,14 @@
1413
DEFAULTS_NAMESPACE_FILE,
1514
FORK_OBSERVER_CHART,
1615
HELM_COMMAND,
16+
INGRESS_HELM_COMMANDS,
1717
LOGGING_HELM_COMMANDS,
1818
LOGGING_NAMESPACE,
1919
NAMESPACES_CHART_LOCATION,
2020
NAMESPACES_FILE,
2121
NETWORK_FILE,
2222
)
23-
from .k8s import get_default_namespace, wait_for_caddy_ready
23+
from .k8s import get_default_namespace, wait_for_ingress_controller, wait_for_pod_ready
2424
from .process import stream_command
2525

2626

@@ -51,6 +51,7 @@ def deploy(directory, debug):
5151
deploy_network(directory, debug)
5252
df = deploy_fork_observer(directory, debug)
5353
if dl | df:
54+
deploy_ingress(debug)
5455
deploy_caddy(directory, debug)
5556
elif (directory / NAMESPACES_FILE).exists():
5657
deploy_namespaces(directory)
@@ -118,8 +119,21 @@ def deploy_caddy(directory: Path, debug: bool):
118119
click.echo(f"Failed to run Helm command: {cmd}")
119120
return
120121

121-
wait_for_caddy_ready(name, namespace)
122-
_port_start_internal(name, namespace)
122+
wait_for_pod_ready(name, namespace)
123+
click.echo("\nTo access the warnet dashboard run:\n warnet dashboard")
124+
125+
126+
def deploy_ingress(debug: bool):
127+
click.echo("Deploying ingress controller")
128+
129+
for command in INGRESS_HELM_COMMANDS:
130+
if not stream_command(command):
131+
print(f"Failed to run Helm command: {command}")
132+
return False
133+
134+
wait_for_ingress_controller()
135+
136+
return True
123137

124138

125139
def deploy_fork_observer(directory: Path, debug: bool) -> bool:
@@ -279,19 +293,3 @@ def run_detached_process(command):
279293
subprocess.Popen(command, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True)
280294

281295
print(f"Started detached process: {command}")
282-
283-
284-
def _port_start_internal(name, namespace):
285-
click.echo("Starting port-forwarding to warnet dashboard")
286-
command = f"kubectl port-forward -n {namespace} service/{name} 2019:80"
287-
run_detached_process(command)
288-
click.echo("Port forwarding on port 2019 started in the background.")
289-
click.echo("\nTo access the warnet dashboard visit localhost:2019 or run:\n warnet dashboard")
290-
291-
292-
def _port_stop_internal(name, namespace):
293-
if is_windows():
294-
os.system("taskkill /F /IM kubectl.exe")
295-
else:
296-
os.system(f"pkill -f 'kubectl port-forward -n {namespace} service/{name} 2019:80'")
297-
click.echo("Port forwarding stopped.")

src/warnet/k8s.py

+31-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
from kubernetes.dynamic import DynamicClient
1111
from kubernetes.stream import stream
1212

13-
from .constants import DEFAULT_NAMESPACE, KUBECONFIG
13+
from .constants import (
14+
CADDY_INGRESS_NAME,
15+
DEFAULT_NAMESPACE,
16+
INGRESS_NAMESPACE,
17+
KUBECONFIG,
18+
LOGGING_NAMESPACE,
19+
)
1420
from .process import run_command, stream_command
1521

1622

@@ -239,7 +245,7 @@ def snapshot_bitcoin_datadir(
239245
print(f"An error occurred: {str(e)}")
240246

241247

242-
def wait_for_caddy_ready(name, namespace, timeout=300):
248+
def wait_for_pod_ready(name, namespace, timeout=300):
243249
sclient = get_static_client()
244250
w = watch.Watch()
245251
for event in w.stream(
@@ -250,8 +256,29 @@ def wait_for_caddy_ready(name, namespace, timeout=300):
250256
conditions = pod.status.conditions or []
251257
ready_condition = next((c for c in conditions if c.type == "Ready"), None)
252258
if ready_condition and ready_condition.status == "True":
253-
print(f"Caddy pod {name} is ready.")
254259
w.stop()
255260
return True
256-
print(f"Timeout waiting for Caddy pod {name} to be ready.")
261+
print(f"Timeout waiting for pod {name} to be ready.")
257262
return False
263+
264+
265+
def wait_for_ingress_controller(timeout=300):
266+
# get name of ingress controller pod
267+
sclient = get_static_client()
268+
pods = sclient.list_namespaced_pod(namespace=INGRESS_NAMESPACE)
269+
for pod in pods.items:
270+
if "ingress-nginx-controller" in pod.metadata.name:
271+
return wait_for_pod_ready(pod.metadata.name, INGRESS_NAMESPACE, timeout)
272+
273+
274+
def get_ingress_ip_or_host():
275+
config.load_kube_config()
276+
networking_v1 = client.NetworkingV1Api()
277+
try:
278+
ingress = networking_v1.read_namespaced_ingress(CADDY_INGRESS_NAME, LOGGING_NAMESPACE)
279+
if ingress.status.load_balancer.ingress[0].hostname:
280+
return ingress.status.load_balancer.ingress[0].hostname
281+
return ingress.status.load_balancer.ingress[0].ip
282+
except Exception as e:
283+
print(f"Error getting ingress IP: {e}")
284+
return None

src/warnet/process.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ def stream_command(command: str) -> bool:
1818
universal_newlines=True,
1919
)
2020

21+
message = ""
2122
for line in iter(process.stdout.readline, ""):
23+
message += line
2224
print(line, end="")
2325

2426
process.stdout.close()
2527
return_code = process.wait()
2628

2729
if return_code != 0:
28-
raise Exception(process.stderr)
30+
raise Exception(message)
2931
return True

test/logging_test.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import requests
88
from test_base import TestBase
99

10-
GRAFANA_URL = "http://localhost:2019/grafana/"
10+
from warnet.k8s import get_ingress_ip_or_host
1111

1212

1313
class LoggingTest(TestBase):
@@ -29,13 +29,17 @@ def setup_network(self):
2929
self.log.info(self.warnet(f"deploy {self.network_dir}"))
3030
self.wait_for_all_tanks_status(target="running", timeout=10 * 60)
3131
self.wait_for_all_edges()
32+
self.wait_for_predicate(lambda: get_ingress_ip_or_host())
33+
ingress_ip = get_ingress_ip_or_host()
34+
self.grafana_url = f"http://{ingress_ip}/grafana"
35+
self.log.info(f"Grafana URL: {self.grafana_url}")
3236

3337
def wait_for_endpoint_ready(self):
3438
self.log.info("Waiting for Grafana to be ready to receive API calls...")
3539

3640
def check_endpoint():
3741
try:
38-
response = requests.get(f"{GRAFANA_URL}login")
42+
response = requests.get(f"{self.grafana_url}/login")
3943
return response.status_code == 200
4044
except requests.RequestException:
4145
return False
@@ -50,7 +54,7 @@ def make_grafana_api_request(self, ds_uid, start, metric):
5054
"from": f"{start}",
5155
"to": "now",
5256
}
53-
reply = requests.post(f"{GRAFANA_URL}api/ds/query", json=data)
57+
reply = requests.post(f"{self.grafana_url}/api/ds/query", json=data)
5458
if reply.status_code != 200:
5559
self.log.error(f"Grafana API request failed with status code {reply.status_code}")
5660
self.log.error(f"Response content: {reply.text}")
@@ -67,7 +71,7 @@ def test_prometheus_and_grafana(self):
6771
self.warnet(f"run {miner_file} --allnodes --interval=5 --mature")
6872
self.warnet(f"run {tx_flood_file} --interval=1")
6973

70-
prometheus_ds = requests.get(f"{GRAFANA_URL}api/datasources/name/Prometheus")
74+
prometheus_ds = requests.get(f"{self.grafana_url}/api/datasources/name/Prometheus")
7175
assert prometheus_ds.status_code == 200
7276
prometheus_uid = prometheus_ds.json()["uid"]
7377
self.log.info(f"Got Prometheus data source uid from Grafana: {prometheus_uid}")
@@ -92,7 +96,7 @@ def get_five_values_for_metric(metric):
9296
self.wait_for_predicate(lambda: get_five_values_for_metric("txrate"))
9397

9498
# Verify default dashboard exists
95-
dbs = requests.get(f"{GRAFANA_URL}api/search").json()
99+
dbs = requests.get(f"{self.grafana_url}/api/search").json()
96100
assert dbs[0]["title"] == "Default Warnet Dashboard"
97101

98102

test/services_test.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import requests
77
from test_base import TestBase
88

9+
from warnet.k8s import get_ingress_ip_or_host
10+
911

1012
class ServicesTest(TestBase):
1113
def __init__(self):
@@ -32,7 +34,9 @@ def check_fork_observer(self):
3234
# Port will be auto-forwarded by `warnet deploy`, routed through the enabled Caddy pod
3335

3436
def call_fo_api():
35-
fo_root = "http://localhost:2019/fork-observer"
37+
# if on minikube remember to run `minikube tunnel` for this test to run
38+
ingress_ip = get_ingress_ip_or_host()
39+
fo_root = f"http://{ingress_ip}/fork-observer"
3640
try:
3741
fo_res = requests.get(f"{fo_root}/api/networks.json")
3842
network_id = fo_res.json()["networks"][0]["id"]

0 commit comments

Comments
 (0)