Skip to content

Commit 0a43f57

Browse files
Gateway health status
1 parent 0b213be commit 0a43f57

10 files changed

Lines changed: 153 additions & 27 deletions

File tree

gateway/docker-compose.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ services:
6565
- GATEWAY_CONTROLLER_HOST=gateway-controller
6666
- MOESIF_KEY=${MOESIF_KEY:-}
6767
- LOG_LEVEL=info
68+
healthcheck:
69+
test: ["CMD", "health-check.sh"]
70+
interval: 5s
71+
timeout: 3s
72+
retries: 10
73+
start_period: 15s
6874
volumes:
6975
- ./configs/config.toml:/etc/policy-engine/config.toml:ro
7076
networks:

gateway/gateway-controller/api/openapi-admin.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ paths:
4141
schema:
4242
$ref: "#/components/schemas/ErrorResponse"
4343

44+
/health:
45+
get:
46+
summary: Health check
47+
description: |
48+
Returns the health status of the gateway controller.
49+
This endpoint is not subject to IP whitelist restrictions so that
50+
Docker and Kubernetes health probes can reach it.
51+
operationId: getHealth
52+
tags:
53+
- System
54+
responses:
55+
"200":
56+
description: Gateway controller is healthy
57+
content:
58+
application/json:
59+
schema:
60+
$ref: "#/components/schemas/HealthResponse"
61+
4462
/xds_sync_status:
4563
get:
4664
summary: Get xDS policy sync status
@@ -172,6 +190,17 @@ components:
172190
xds_sync:
173191
$ref: "#/components/schemas/ConfigDumpXDSSync"
174192

193+
HealthResponse:
194+
type: object
195+
properties:
196+
status:
197+
type: string
198+
description: Health status ("healthy")
199+
timestamp:
200+
type: string
201+
format: date-time
202+
description: Timestamp of the health check
203+
175204
XDSSyncStatusResponse:
176205
type: object
177206
properties:

gateway/gateway-controller/pkg/adminapi/generated/generated.go

Lines changed: 30 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gateway/gateway-controller/pkg/adminserver/server.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"log/slog"
88
"net"
99
"net/http"
10+
"time"
1011

1112
adminapi "github.com/wso2/api-platform/gateway/gateway-controller/pkg/adminapi/generated"
1213
"github.com/wso2/api-platform/gateway/gateway-controller/pkg/config"
@@ -37,6 +38,8 @@ func NewServer(cfg *config.AdminServerConfig, apiServer apiServer, logger *slog.
3738

3839
mux.Handle("/config_dump", ipWhitelistMiddleware(cfg.AllowedIPs, http.HandlerFunc(s.handleConfigDump)))
3940
mux.Handle("/xds_sync_status", ipWhitelistMiddleware(cfg.AllowedIPs, http.HandlerFunc(s.handleXDSSyncStatus)))
41+
// Health endpoint is registered without IP whitelist so Docker/k8s health probes can reach it
42+
mux.Handle("/health", http.HandlerFunc(s.handleHealth))
4043

4144
s.httpSrv = &http.Server{
4245
Addr: fmt.Sprintf(":%d", cfg.Port),
@@ -93,6 +96,22 @@ func (s *Server) handleXDSSyncStatus(w http.ResponseWriter, r *http.Request) {
9396
_ = json.NewEncoder(w).Encode(resp)
9497
}
9598

99+
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
100+
if r.Method != http.MethodGet {
101+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
102+
return
103+
}
104+
105+
resp := map[string]string{
106+
"status": "healthy",
107+
"timestamp": time.Now().UTC().Format(time.RFC3339),
108+
}
109+
110+
w.Header().Set("Content-Type", "application/json")
111+
w.WriteHeader(http.StatusOK)
112+
_ = json.NewEncoder(w).Encode(resp)
113+
}
114+
96115
func ipWhitelistMiddleware(allowedIPs []string, next http.Handler) http.Handler {
97116
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98117
clientIP := extractClientIP(r)

gateway/gateway-controller/pkg/adminserver/server_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,48 @@ func TestAdminServer_MethodNotAllowed(t *testing.T) {
100100
assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
101101
}
102102

103+
func TestAdminServer_HealthHandler(t *testing.T) {
104+
stub := &stubAPIServer{}
105+
s := NewServer(&config.AdminServerConfig{Port: 9092, AllowedIPs: []string{"*"}}, stub, slog.Default())
106+
107+
req := httptest.NewRequest(http.MethodGet, "/health", nil)
108+
req.RemoteAddr = "127.0.0.1:12345"
109+
rr := httptest.NewRecorder()
110+
111+
s.httpSrv.Handler.ServeHTTP(rr, req)
112+
assert.Equal(t, http.StatusOK, rr.Code)
113+
114+
var body map[string]string
115+
assert.NoError(t, json.NewDecoder(rr.Body).Decode(&body))
116+
assert.Equal(t, "healthy", body["status"])
117+
assert.NotEmpty(t, body["timestamp"])
118+
}
119+
120+
func TestAdminServer_HealthHandler_MethodNotAllowed(t *testing.T) {
121+
stub := &stubAPIServer{}
122+
s := NewServer(&config.AdminServerConfig{Port: 9092, AllowedIPs: []string{"*"}}, stub, slog.Default())
123+
124+
req := httptest.NewRequest(http.MethodPost, "/health", nil)
125+
req.RemoteAddr = "127.0.0.1:12345"
126+
rr := httptest.NewRecorder()
127+
128+
s.httpSrv.Handler.ServeHTTP(rr, req)
129+
assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
130+
}
131+
132+
func TestAdminServer_HealthHandler_NoIPWhitelist(t *testing.T) {
133+
stub := &stubAPIServer{}
134+
// Restrict IPs to only 127.0.0.1 — health should still be accessible from other IPs
135+
s := NewServer(&config.AdminServerConfig{Port: 9092, AllowedIPs: []string{"127.0.0.1"}}, stub, slog.Default())
136+
137+
req := httptest.NewRequest(http.MethodGet, "/health", nil)
138+
req.RemoteAddr = "192.168.1.10:12345"
139+
rr := httptest.NewRecorder()
140+
141+
s.httpSrv.Handler.ServeHTTP(rr, req)
142+
assert.Equal(t, http.StatusOK, rr.Code)
143+
}
144+
103145
func TestIsIPAllowed(t *testing.T) {
104146
assert.True(t, isIPAllowed("127.0.0.1", []string{"*"}))
105147
assert.True(t, isIPAllowed("127.0.0.1", []string{"0.0.0.0/0"}))

gateway/gateway-runtime/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ COPY --from=policy-compiler /workspace/output/policy-engine/policy-engine /app/p
140140
COPY router/config/envoy-bootstrap.yaml /etc/envoy/envoy.yaml
141141
COPY router/config/config-override.yaml /etc/envoy/config-override.yaml
142142
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
143+
COPY health-check.sh /usr/local/bin/health-check.sh
143144

144-
RUN chmod +x /app/policy-engine /usr/local/bin/docker-entrypoint.sh
145+
RUN chmod +x /app/policy-engine /usr/local/bin/docker-entrypoint.sh /usr/local/bin/health-check.sh
145146

146147
RUN groupadd -r -g 10001 wso2 && \
147148
useradd -r -u 10001 -g wso2 -s /sbin/nologin -c "WSO2 Application User" wso2 && \

gateway/it/docker-compose.test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ services:
8282
mock-openapi:
8383
condition: service_healthy
8484
healthcheck:
85-
test: ["CMD", "bash", "-c", "wget --quiet --tries=1 --spider http://localhost:9901/ready && wget --quiet --tries=1 --spider http://localhost:9002/health"]
85+
test: ["CMD", "health-check.sh"]
8686
interval: 5s
8787
timeout: 3s
8888
retries: 10

gateway/it/features/health.feature

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ Feature: Gateway Health Check
4848
When I send a GET request to the gateway controller health endpoint
4949
Then the response status code should be 200
5050

51+
# ==================== GATEWAY CONTROLLER ADMIN HEALTH ====================
52+
53+
Scenario: Gateway controller admin health endpoint returns OK
54+
When I send a GET request to the gateway controller admin health endpoint
55+
Then the response status code should be 200
56+
And the response should indicate healthy status
57+
58+
Scenario: Gateway controller admin health endpoint returns valid JSON
59+
When I send a GET request to the gateway controller admin health endpoint
60+
Then the response status code should be 200
61+
And the response should be valid JSON
62+
5163
# ==================== POLICY ENGINE HEALTH ====================
5264

5365
Scenario: Policy engine health endpoint returns OK

gateway/it/steps_health.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func RegisterHealthSteps(ctx *godog.ScenarioContext, state *TestState, httpSteps
4141
ctx.Step(`^the gateway services are running$`, h.theGatewayServicesAreRunning)
4242
ctx.Step(`^I send a GET request to the gateway controller health endpoint$`, h.iSendGETRequestToGatewayControllerHealth)
4343
ctx.Step(`^I send a GET request to the router ready endpoint$`, h.iSendGETRequestToRouterReady)
44+
ctx.Step(`^I send a GET request to the gateway controller admin health endpoint$`, h.iSendGETRequestToGatewayControllerAdminHealth)
4445
ctx.Step(`^I send a GET request to the policy engine health endpoint$`, h.iSendGETRequestToPolicyEngineHealth)
4546
// Note: "the response status code should be X" is registered in AssertSteps which uses HTTPSteps response
4647
ctx.Step(`^the response should indicate healthy status$`, h.theResponseShouldIndicateHealthyStatus)
@@ -71,6 +72,12 @@ func (h *HealthSteps) iSendGETRequestToRouterReady() error {
7172
return h.httpSteps.SendGETRequest(url)
7273
}
7374

75+
// iSendGETRequestToGatewayControllerAdminHealth sends a GET request to the gateway controller admin health endpoint
76+
func (h *HealthSteps) iSendGETRequestToGatewayControllerAdminHealth() error {
77+
url := fmt.Sprintf("%s/health", h.state.Config.GatewayControllerAdminURL)
78+
return h.httpSteps.SendGETRequest(url)
79+
}
80+
7481
// iSendGETRequestToPolicyEngineHealth sends a GET request to the policy engine health endpoint
7582
func (h *HealthSteps) iSendGETRequestToPolicyEngineHealth() error {
7683
url := fmt.Sprintf("%s/health", h.state.Config.PolicyEngineURL)
@@ -100,6 +107,7 @@ func (h *HealthSteps) iCheckHealthOfAllGatewayServices() error {
100107
url string
101108
}{
102109
{"gateway-controller", fmt.Sprintf("%s/health", h.state.Config.GatewayControllerURL)},
110+
{"gateway-controller-admin", fmt.Sprintf("%s/health", h.state.Config.GatewayControllerAdminURL)},
103111
{"router", fmt.Sprintf("http://localhost:%s/ready", EnvoyAdminPort)},
104112
{"policy-engine", fmt.Sprintf("%s/health", h.state.Config.PolicyEngineURL)},
105113
}

kubernetes/helm/gateway-helm-chart/values.yaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -463,17 +463,15 @@ gateway:
463463
podLabels: {}
464464
priorityClassName: ""
465465
livenessProbe:
466-
httpGet:
467-
path: /server_info
468-
port: envoy-admin
466+
exec:
467+
command: ["health-check.sh"]
469468
initialDelaySeconds: 30
470469
periodSeconds: 10
471470
timeoutSeconds: 5
472471
failureThreshold: 3
473472
readinessProbe:
474-
httpGet:
475-
path: /server_info
476-
port: envoy-admin
473+
exec:
474+
command: ["health-check.sh"]
477475
initialDelaySeconds: 10
478476
periodSeconds: 5
479477
timeoutSeconds: 3

0 commit comments

Comments
 (0)