Skip to content

Commit b243602

Browse files
committed
TUN-7550: Add pprof endpoint to management service
1 parent 960c5a7 commit b243602

File tree

4 files changed

+106
-32
lines changed

4 files changed

+106
-32
lines changed

component-tests/test_management.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python
2+
import requests
3+
from conftest import CfdModes
4+
from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
5+
from retrying import retry
6+
from cli import CloudflaredCli
7+
from util import LOGGER, write_config, start_cloudflared, wait_tunnel_ready, send_requests
8+
import platform
9+
10+
"""
11+
Each test in TestManagement will:
12+
1. Acquire a management token from Cloudflare public API
13+
2. Make a request against the management service for the running tunnel
14+
"""
15+
class TestManagement:
16+
"""
17+
test_get_host_details does the following:
18+
1. It gets a management token from Tunnelstore using cloudflared tail token <tunnel_id>
19+
2. It gets the connector_id after starting a cloudflare tunnel
20+
3. It sends a request to the management host with the connector_id and management token
21+
4. Asserts that the response has a hostname and ip.
22+
"""
23+
def test_get_host_details(self, tmp_path, component_tests_config):
24+
# TUN-7377 : wait_tunnel_ready does not work properly in windows.
25+
# Skipping this test for windows for now and will address it as part of tun-7377
26+
if platform.system() == "Windows":
27+
return
28+
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
29+
LOGGER.debug(config)
30+
headers = {}
31+
headers["Content-Type"] = "application/json"
32+
config_path = write_config(tmp_path, config.full_config)
33+
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1", "--label" , "test"], cfd_args=["run", "--hello-world"], new_process=True):
34+
wait_tunnel_ready(tunnel_url=config.get_url(),
35+
require_min_connections=1)
36+
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
37+
connector_id = cfd_cli.get_connector_id(config)[0]
38+
url = cfd_cli.get_management_url("host_details", config, config_path)
39+
resp = send_request(url, headers=headers)
40+
41+
# Assert response json.
42+
assert resp.status_code == 200, "Expected cloudflared to return 200 for host details"
43+
assert resp.json()["hostname"] == "custom:test", "Expected cloudflared to return hostname"
44+
assert resp.json()["ip"] != "", "Expected cloudflared to return ip"
45+
assert resp.json()["connector_id"] == connector_id, "Expected cloudflared to return connector_id"
46+
47+
"""
48+
test_get_metrics will verify that the /metrics endpoint returns the prometheus metrics dump
49+
"""
50+
def test_get_metrics(self, tmp_path, component_tests_config):
51+
# TUN-7377 : wait_tunnel_ready does not work properly in windows.
52+
# Skipping this test for windows for now and will address it as part of tun-7377
53+
if platform.system() == "Windows":
54+
return
55+
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
56+
LOGGER.debug(config)
57+
config_path = write_config(tmp_path, config.full_config)
58+
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True):
59+
wait_tunnel_ready(require_min_connections=1)
60+
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
61+
url = cfd_cli.get_management_url("metrics", config, config_path)
62+
resp = send_request(url)
63+
64+
# Assert response.
65+
assert resp.status_code == 200, "Expected cloudflared to return 200 for /metrics"
66+
assert "# HELP build_info Build and version information" in resp.text, "Expected /metrics to have with the build_info details"
67+
68+
"""
69+
test_get_pprof_heap will verify that the /debug/pprof/heap endpoint returns a pprof/heap dump response
70+
"""
71+
def test_get_pprof_heap(self, tmp_path, component_tests_config):
72+
# TUN-7377 : wait_tunnel_ready does not work properly in windows.
73+
# Skipping this test for windows for now and will address it as part of tun-7377
74+
if platform.system() == "Windows":
75+
return
76+
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
77+
LOGGER.debug(config)
78+
config_path = write_config(tmp_path, config.full_config)
79+
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True):
80+
wait_tunnel_ready(require_min_connections=1)
81+
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
82+
url = cfd_cli.get_management_url("debug/pprof/heap", config, config_path)
83+
resp = send_request(url)
84+
85+
# Assert response.
86+
assert resp.status_code == 200, "Expected cloudflared to return 200 for /debug/pprof/heap"
87+
assert resp.headers["Content-Type"] == "application/octet-stream", "Expected /debug/pprof/heap to have return a binary response"
88+
89+
90+
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
91+
def send_request(url, headers={}):
92+
with requests.Session() as s:
93+
return s.get(url, timeout=BACKOFF_SECS, headers=headers)

component-tests/test_tunnel.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,6 @@ def test_tunnel_hello_world(self, tmp_path, component_tests_config):
1616
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["run", "--hello-world"], new_process=True):
1717
wait_tunnel_ready(tunnel_url=config.get_url(),
1818
require_min_connections=1)
19-
20-
"""
21-
test_get_host_details does the following:
22-
1. It gets a management token from Tunnelstore using cloudflared tail token <tunnel_id>
23-
2. It gets the connector_id after starting a cloudflare tunnel
24-
3. It sends a request to the management host with the connector_id and management token
25-
4. Asserts that the response has a hostname and ip.
26-
"""
27-
def test_get_host_details(self, tmp_path, component_tests_config):
28-
# TUN-7377 : wait_tunnel_ready does not work properly in windows.
29-
# Skipping this test for windows for now and will address it as part of tun-7377
30-
if platform.system() == "Windows":
31-
return
32-
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)
33-
LOGGER.debug(config)
34-
headers = {}
35-
headers["Content-Type"] = "application/json"
36-
config_path = write_config(tmp_path, config.full_config)
37-
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1", "--label" , "test"], cfd_args=["run", "--hello-world"], new_process=True):
38-
wait_tunnel_ready(tunnel_url=config.get_url(),
39-
require_min_connections=1)
40-
cfd_cli = CloudflaredCli(config, config_path, LOGGER)
41-
connector_id = cfd_cli.get_connector_id(config)[0]
42-
url = cfd_cli.get_management_url("host_details", config, config_path)
43-
resp = send_request(url, headers=headers)
44-
45-
# Assert response json.
46-
assert resp.status_code == 200, "Expected cloudflared to return 200 for host details"
47-
assert resp.json()["hostname"] == "custom:test", "Expected cloudflared to return hostname"
48-
assert resp.json()["ip"] != "", "Expected cloudflared to return ip"
49-
assert resp.json()["connector_id"] == connector_id, "Expected cloudflared to return connector_id"
50-
5119

5220
def test_tunnel_url(self, tmp_path, component_tests_config):
5321
config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False)

connection/quic.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,14 @@ func (hrw *httpResponseAdapter) WriteRespHeaders(status int, header http.Header)
449449
return hrw.WriteConnectResponseData(nil, metadata...)
450450
}
451451

452+
func (hrw *httpResponseAdapter) Write(p []byte) (int, error) {
453+
// Make sure to send WriteHeader response if not called yet
454+
if !hrw.connectResponseSent {
455+
hrw.WriteRespHeaders(http.StatusOK, hrw.headers)
456+
}
457+
return hrw.RequestServerStream.Write(p)
458+
}
459+
452460
func (hrw *httpResponseAdapter) Header() http.Header {
453461
return hrw.headers
454462
}

management/service.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net"
77
"net/http"
8+
"net/http/pprof"
89
"os"
910
"sync"
1011
"time"
@@ -74,6 +75,10 @@ func New(managementHostname string,
7475
r.Head("/ping", ping)
7576
r.Get("/logs", s.logs)
7677
r.Get("/metrics", s.metricsHandler.ServeHTTP)
78+
79+
// Supports only heap and goroutine
80+
r.Get("/debug/pprof/{profile:heap|goroutine}", pprof.Index)
81+
7782
r.Route("/host_details", func(r chi.Router) {
7883
// CORS middleware required to allow dash to access management.argotunnel.com requests
7984
r.Use(cors.Handler(cors.Options{

0 commit comments

Comments
 (0)