Skip to content

Commit c9fb4ac

Browse files
Dean-Coakleyisaachawley
authored andcommitted
Add stream upstream, stream server zones metrics support
* Add stream upstream/server zone metrics support * Extend GetStats to include stream metrics * Added integration test to validate that stream metrics are returned * Handle missing stream block * Add sleep to wait for health checks to pass * Simplify internal error handling * Add internalError.Wrap for preserving context
1 parent ee09bf6 commit c9fb4ac

File tree

7 files changed

+311
-33
lines changed

7 files changed

+311
-33
lines changed

Makefile

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
NGINX_PLUS_VERSION=15-2
22
NGINX_IMAGE=nginxplus:$(NGINX_PLUS_VERSION)
33

4-
test: docker-build run-nginx-plus test-run clean
5-
4+
test: docker-build run-nginx-plus test-run configure-no-stream-block test-run-no-stream-block clean
5+
66
docker-build:
77
docker build --build-arg NGINX_PLUS_VERSION=$(NGINX_PLUS_VERSION)~stretch -t $(NGINX_IMAGE) docker
88

99
run-nginx-plus:
10-
docker run -d --name nginx-plus-test --rm -p 8080:8080 $(NGINX_IMAGE)
10+
docker run -d --name nginx-plus-test --rm -p 8080:8080 -p 8081:8081 $(NGINX_IMAGE)
1111

1212
test-run:
1313
go test client/*
14-
go test tests/client_test.go
14+
GOCACHE=off go test tests/client_test.go
15+
16+
configure-no-stream-block:
17+
docker cp docker/nginx_no_stream.conf nginx-plus-test:/etc/nginx/nginx.conf
18+
docker exec nginx-plus-test nginx -s reload
19+
20+
test-run-no-stream-block:
21+
GOCACHE=off go test tests/client_no_stream_test.go
1522

1623
clean:
1724
docker kill nginx-plus-test

client/nginx.go

Lines changed: 148 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
// APIVersion is a version of NGINX Plus API.
1313
const APIVersion = 2
1414

15+
const streamNotConfiguredCode = "StreamNotConfigured"
16+
1517
// NginxClient lets you access NGINX Plus API.
1618
type NginxClient struct {
1719
apiEndpoint string
@@ -57,14 +59,33 @@ type apiError struct {
5759
Code string
5860
}
5961

62+
type internalError struct {
63+
apiError
64+
err string
65+
}
66+
67+
// Error allows internalError to match the Error interface.
68+
func (internalError *internalError) Error() string {
69+
return internalError.err
70+
}
71+
72+
// Wrap is a way of including current context while preserving previous error information,
73+
// similar to `return fmt.Errof("error doing foo, err: %v", err)` but for our internalError type.
74+
func (internalError *internalError) Wrap(err string) *internalError {
75+
internalError.err = fmt.Sprintf("%v. %v", err, internalError.err)
76+
return internalError
77+
}
78+
6079
// Stats represents NGINX Plus stats fetched from the NGINX Plus API.
6180
// https://nginx.org/en/docs/http/ngx_http_api_module.html
6281
type Stats struct {
63-
Connections Connections
64-
HTTPRequests HTTPRequests
65-
SSL SSL
66-
ServerZones ServerZones
67-
Upstreams Upstreams
82+
Connections Connections
83+
HTTPRequests HTTPRequests
84+
SSL SSL
85+
ServerZones ServerZones
86+
Upstreams Upstreams
87+
StreamServerZones StreamServerZones
88+
StreamUpstreams StreamUpstreams
6889
}
6990

7091
// Connections represents connection related stats.
@@ -101,7 +122,20 @@ type ServerZone struct {
101122
Sent uint64
102123
}
103124

104-
// Responses represents HTTP reponse related stats.
125+
// StreamServerZones is map of stream server zone stats by zone name.
126+
type StreamServerZones map[string]StreamServerZone
127+
128+
// StreamServerZone represents stream server zone related stats.
129+
type StreamServerZone struct {
130+
Processing uint64
131+
Connections uint64
132+
Sessions Sessions
133+
Discarded uint64
134+
Received uint64
135+
Sent uint64
136+
}
137+
138+
// Responses represents HTTP response related stats.
105139
type Responses struct {
106140
Responses1xx uint64 `json:"1xx"`
107141
Responses2xx uint64 `json:"2xx"`
@@ -111,6 +145,14 @@ type Responses struct {
111145
Total uint64
112146
}
113147

148+
// Sessions represents stream session related stats.
149+
type Sessions struct {
150+
Sessions2xx uint64 `json:"2xx"`
151+
Sessions4xx uint64 `josn:"4xx"`
152+
Sessions5xx uint64 `josn:"5xx"`
153+
Total uint64
154+
}
155+
114156
// Upstreams is a map of upstream stats by upstream name.
115157
type Upstreams map[string]Upstream
116158

@@ -123,6 +165,16 @@ type Upstream struct {
123165
Queue Queue
124166
}
125167

168+
// StreamUpstreams is a map of stream upstream stats by upstream name.
169+
type StreamUpstreams map[string]StreamUpstream
170+
171+
// StreamUpstream represents stream upstream related stats.
172+
type StreamUpstream struct {
173+
Peers []StreamPeer
174+
Zombies int
175+
Zone string
176+
}
177+
126178
// Queue represents queue related stats for an upstream.
127179
type Queue struct {
128180
Size int
@@ -155,6 +207,31 @@ type Peer struct {
155207
ResponseTime uint64 `json:"response_time"`
156208
}
157209

210+
// StreamPeer represents peer (stream upstream server) related stats.
211+
type StreamPeer struct {
212+
ID int
213+
Server string
214+
Service string
215+
Name string
216+
Backup bool
217+
Weight int
218+
State string
219+
Active uint64
220+
MaxConns int `json:"max_conns"`
221+
Connections uint64
222+
ConnectTime int `json:"connect_time"`
223+
FirstByteTime int `json:"first_byte_time"`
224+
ResponseTime uint64 `json:"response_time"`
225+
Sent uint64
226+
Received uint64
227+
Fails uint64
228+
Unavail uint64
229+
HealthChecks HealthChecks `json:"health_checks"`
230+
Downtime uint64
231+
Downstart string
232+
Selected string
233+
}
234+
158235
// HealthChecks represents health check related stats for a peer.
159236
type HealthChecks struct {
160237
Checks uint64
@@ -214,13 +291,18 @@ func getAPIVersions(httpClient *http.Client, endpoint string) (*versions, error)
214291
return &vers, nil
215292
}
216293

217-
func createResponseMismatchError(respBody io.ReadCloser, mainErr error) error {
218-
apiErr, err := readAPIErrorResponse(respBody)
294+
func createResponseMismatchError(respBody io.ReadCloser) *internalError {
295+
apiErrResp, err := readAPIErrorResponse(respBody)
219296
if err != nil {
220-
return fmt.Errorf("%v; failed to read the response body: %v", mainErr, err)
297+
return &internalError{
298+
err: fmt.Sprintf("failed to read the response body: %v", err),
299+
}
221300
}
222301

223-
return fmt.Errorf("%v; error: %v", mainErr, apiErr.toString())
302+
return &internalError{
303+
err: apiErrResp.toString(),
304+
apiError: apiErrResp.Error,
305+
}
224306
}
225307

226308
func readAPIErrorResponse(respBody io.ReadCloser) (*apiErrorResponse, error) {
@@ -379,8 +461,9 @@ func (client *NginxClient) get(path string, data interface{}) error {
379461
}
380462
defer resp.Body.Close()
381463
if resp.StatusCode != http.StatusOK {
382-
mainErr := fmt.Errorf("expected %v response, got %v", http.StatusOK, resp.StatusCode)
383-
return createResponseMismatchError(resp.Body, mainErr)
464+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
465+
"expected %v response, got %v",
466+
http.StatusOK, resp.StatusCode))
384467
}
385468

386469
body, err := ioutil.ReadAll(resp.Body)
@@ -409,8 +492,9 @@ func (client *NginxClient) post(path string, input interface{}) error {
409492
}
410493
defer resp.Body.Close()
411494
if resp.StatusCode != http.StatusCreated {
412-
mainErr := fmt.Errorf("expected %v response, got %v", http.StatusCreated, resp.StatusCode)
413-
return createResponseMismatchError(resp.Body, mainErr)
495+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
496+
"expected %v response, got %v",
497+
http.StatusCreated, resp.StatusCode))
414498
}
415499

416500
return nil
@@ -431,9 +515,9 @@ func (client *NginxClient) delete(path string) error {
431515
defer resp.Body.Close()
432516

433517
if resp.StatusCode != http.StatusOK {
434-
mainErr := fmt.Errorf("failed to complete delete request: expected %v response, got %v",
435-
http.StatusOK, resp.StatusCode)
436-
return createResponseMismatchError(resp.Body, mainErr)
518+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
519+
"failed to complete delete request: expected %v response, got %v",
520+
http.StatusOK, resp.StatusCode))
437521
}
438522
return nil
439523
}
@@ -458,7 +542,7 @@ func (client *NginxClient) GetStreamServers(upstream string) ([]StreamUpstreamSe
458542
return servers, nil
459543
}
460544

461-
// AddStreamServer adds the server to the upstream.
545+
// AddStreamServer adds the stream server to the upstream.
462546
func (client *NginxClient) AddStreamServer(upstream string, server StreamUpstreamServer) error {
463547
id, err := client.getIDOfStreamServer(upstream, server.Server)
464548

@@ -572,7 +656,7 @@ func determineStreamUpdates(updatedServers []StreamUpstreamServer, nginxServers
572656
return
573657
}
574658

575-
// GetStats gets connection, request, ssl, zone, and upstream related stats from the NGINX Plus API.
659+
// GetStats gets connection, request, ssl, zone, stream zone, upstream and stream upstream related stats from the NGINX Plus API.
576660
func (client *NginxClient) GetStats() (*Stats, error) {
577661
cons, err := client.getConnections()
578662
if err != nil {
@@ -599,12 +683,24 @@ func (client *NginxClient) GetStats() (*Stats, error) {
599683
return nil, fmt.Errorf("failed to get stats: %v", err)
600684
}
601685

686+
streamZones, err := client.getStreamServerZones()
687+
if err != nil {
688+
return nil, fmt.Errorf("failed to get stats: %v", err)
689+
}
690+
691+
streamUpstreams, err := client.getStreamUpstreams()
692+
if err != nil {
693+
return nil, fmt.Errorf("failed to get stats: %v", err)
694+
}
695+
602696
return &Stats{
603-
Connections: *cons,
604-
HTTPRequests: *requests,
605-
SSL: *ssl,
606-
ServerZones: *zones,
607-
Upstreams: *upstreams,
697+
Connections: *cons,
698+
HTTPRequests: *requests,
699+
SSL: *ssl,
700+
ServerZones: *zones,
701+
StreamServerZones: *streamZones,
702+
Upstreams: *upstreams,
703+
StreamUpstreams: *streamUpstreams,
608704
}, nil
609705
}
610706

@@ -646,6 +742,20 @@ func (client *NginxClient) getServerZones() (*ServerZones, error) {
646742
return &zones, err
647743
}
648744

745+
func (client *NginxClient) getStreamServerZones() (*StreamServerZones, error) {
746+
var zones StreamServerZones
747+
err := client.get("stream/server_zones", &zones)
748+
if err != nil {
749+
if err, ok := err.(*internalError); ok {
750+
if err.Code == streamNotConfiguredCode {
751+
return &zones, nil
752+
}
753+
}
754+
return nil, fmt.Errorf("failed to get stream server zones: %v", err)
755+
}
756+
return &zones, err
757+
}
758+
649759
func (client *NginxClient) getUpstreams() (*Upstreams, error) {
650760
var upstreams Upstreams
651761
err := client.get("http/upstreams", &upstreams)
@@ -654,3 +764,17 @@ func (client *NginxClient) getUpstreams() (*Upstreams, error) {
654764
}
655765
return &upstreams, nil
656766
}
767+
768+
func (client *NginxClient) getStreamUpstreams() (*StreamUpstreams, error) {
769+
var upstreams StreamUpstreams
770+
err := client.get("stream/upstreams", &upstreams)
771+
if err != nil {
772+
if err, ok := err.(*internalError); ok {
773+
if err.Code == streamNotConfiguredCode {
774+
return &upstreams, nil
775+
}
776+
}
777+
return nil, fmt.Errorf("failed to get stream upstreams: %v", err)
778+
}
779+
return &upstreams, nil
780+
}

docker/nginx.conf

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@ http {
3333

3434
stream {
3535
upstream stream_test {
36-
zone stream 64k;
36+
zone stream_test 64k;
37+
}
38+
39+
server {
40+
listen 8081;
41+
proxy_pass stream_test;
42+
status_zone stream_test;
43+
health_check interval=10 fails=3 passes=1;
3744
}
3845

3946
}

docker/nginx_no_stream.conf

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
user nginx;
3+
worker_processes auto;
4+
5+
error_log /var/log/nginx/error.log notice;
6+
pid /var/run/nginx.pid;
7+
8+
9+
events {
10+
worker_connections 1024;
11+
}
12+
13+
14+
http {
15+
include /etc/nginx/mime.types;
16+
default_type application/octet-stream;
17+
18+
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
19+
'$status $body_bytes_sent "$http_referer" '
20+
'"$http_user_agent" "$http_x_forwarded_for"';
21+
22+
access_log /var/log/nginx/access.log main;
23+
24+
sendfile on;
25+
#tcp_nopush on;
26+
27+
keepalive_timeout 65;
28+
29+
#gzip on;
30+
31+
include /etc/nginx/conf.d/*.conf;
32+
}

docker/test.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ server {
2222
health_check interval=10 fails=3 passes=1;
2323
}
2424
status_zone test;
25-
}
25+
}

0 commit comments

Comments
 (0)