Skip to content

Commit 380a939

Browse files
authored
Add types and methods for fetching stats from the API
1 parent e7266e3 commit 380a939

File tree

4 files changed

+264
-9
lines changed

4 files changed

+264
-9
lines changed

client/nginx_client.go

Lines changed: 195 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,27 @@ import (
99
"net/http"
1010
)
1111

12-
// APIVersion is a version of NGINX Plus API
12+
// APIVersion is a version of NGINX Plus API.
1313
const APIVersion = 2
1414

15-
// NginxClient lets you add/remove servers to/from NGINX Plus via its API
15+
// NginxClient lets you add/remove servers to/from NGINX Plus via its API.
1616
type NginxClient struct {
1717
apiEndpoint string
1818
httpClient *http.Client
1919
}
2020

2121
type versions []int
2222

23-
// UpstreamServer lets you configure HTTP upstreams
23+
// UpstreamServer lets you configure HTTP upstreams.
2424
type UpstreamServer struct {
25-
ID int64 `json:"id,omitempty"`
25+
ID int `json:"id,omitempty"`
2626
Server string `json:"server"`
27-
MaxFails int64 `json:"max_fails"`
27+
MaxFails int `json:"max_fails"`
2828
FailTimeout string `json:"fail_timeout,omitempty"`
2929
SlowStart string `json:"slow_start,omitempty"`
3030
}
3131

32-
// StreamUpstreamServer lets you configure Stream upstreams
32+
// StreamUpstreamServer lets you configure Stream upstreams.
3333
type StreamUpstreamServer struct {
3434
ID int64 `json:"id,omitempty"`
3535
Server string `json:"server"`
@@ -57,6 +57,111 @@ type apiError struct {
5757
Code string
5858
}
5959

60+
// Stats represents NGINX Plus stats fetched from the NGINX Plus API.
61+
// https://nginx.org/en/docs/http/ngx_http_api_module.html
62+
type Stats struct {
63+
Connections Connections
64+
HTTPRequests HTTPRequests
65+
SSL SSL
66+
ServerZones ServerZones
67+
Upstreams Upstreams
68+
}
69+
70+
// Connections represents connection related stats.
71+
type Connections struct {
72+
Accepted uint64
73+
Dropped uint64
74+
Active uint64
75+
Idle uint64
76+
}
77+
78+
// HTTPRequests represents HTTP request related stats.
79+
type HTTPRequests struct {
80+
Total uint64
81+
Current uint64
82+
}
83+
84+
// SSL represents SSL related stats.
85+
type SSL struct {
86+
Handshakes uint64
87+
HandshakesFailed uint64 `json:"handshakes_failed"`
88+
SessionReuses uint64 `json:"session_reuses"`
89+
}
90+
91+
// ServerZones is map of server zone stats by zone name
92+
type ServerZones map[string]ServerZone
93+
94+
// ServerZone represents server zone related stats.
95+
type ServerZone struct {
96+
Processing uint64
97+
Requests uint64
98+
Responses Responses
99+
Discarded uint64
100+
Received uint64
101+
Sent uint64
102+
}
103+
104+
// Responses represents HTTP reponse related stats.
105+
type Responses struct {
106+
Responses1xx uint64 `json:"1xx"`
107+
Responses2xx uint64 `json:"2xx"`
108+
Responses3xx uint64 `json:"3xx"`
109+
Responses4xx uint64 `json:"4xx"`
110+
Responses5xx uint64 `json:"5xx"`
111+
}
112+
113+
// Upstreams is a map of upstream stats by upstream name.
114+
type Upstreams map[string]Upstream
115+
116+
// Upstream represents upstream related stats.
117+
type Upstream struct {
118+
Peers []Peer
119+
Keepalives int
120+
Zombies int
121+
Zone string
122+
Queue Queue
123+
}
124+
125+
// Queue represents queue related stats for an upstream.
126+
type Queue struct {
127+
Size int
128+
MaxSize int `json:"max_size"`
129+
Overflows uint64
130+
}
131+
132+
// Peer represents peer (upstream server) related stats.
133+
type Peer struct {
134+
ID int
135+
Server string
136+
Service string
137+
Name string
138+
Backup bool
139+
Weight int
140+
State string
141+
Active uint64
142+
MaxConns int `json:"max_conns"`
143+
Requests uint64
144+
Responses Responses
145+
Sent uint64
146+
Received uint64
147+
Fails uint64
148+
Unavail uint64
149+
HealthChecks HealthChecks
150+
Downtime uint64
151+
Downstart string
152+
Selected string
153+
HeaderTime uint64 `json:"header_time"`
154+
ResponseTime uint64 `json:"response_time"`
155+
}
156+
157+
// HealthChecks represents health check related stats for a peer.
158+
type HealthChecks struct {
159+
Checks uint64
160+
Fails uint64
161+
Unhealthy uint64
162+
LastPassed bool `json:"last_passed"`
163+
}
164+
60165
// NewNginxClient creates an NginxClient.
61166
func NewNginxClient(httpClient *http.Client, apiEndpoint string) (*NginxClient, error) {
62167
versions, err := getAPIVersions(httpClient, apiEndpoint)
@@ -250,7 +355,7 @@ func determineUpdates(updatedServers []UpstreamServer, nginxServers []UpstreamSe
250355
return
251356
}
252357

253-
func (client *NginxClient) getIDOfHTTPServer(upstream string, name string) (int64, error) {
358+
func (client *NginxClient) getIDOfHTTPServer(upstream string, name string) (int, error) {
254359
servers, err := client.GetHTTPServers(upstream)
255360
if err != nil {
256361
return -1, fmt.Errorf("error getting id of server %v of upstream %v: %v", name, upstream, err)
@@ -465,3 +570,86 @@ func determineStreamUpdates(updatedServers []StreamUpstreamServer, nginxServers
465570

466571
return
467572
}
573+
574+
// GetStats gets connection, request, ssl, zone, and upstream related stats from the NGINX Plus API.
575+
func (client *NginxClient) GetStats() (*Stats, error) {
576+
cons, err := client.getConnections()
577+
if err != nil {
578+
return nil, fmt.Errorf("failed to get stats: %v", err)
579+
}
580+
581+
requests, err := client.getHTTPRequests()
582+
if err != nil {
583+
return nil, fmt.Errorf("Failed to get stats: %v", err)
584+
}
585+
586+
ssl, err := client.getSSL()
587+
if err != nil {
588+
return nil, fmt.Errorf("failed to get stats: %v", err)
589+
}
590+
591+
zones, err := client.getServerZones()
592+
if err != nil {
593+
return nil, fmt.Errorf("failed to get stats: %v", err)
594+
}
595+
596+
upstreams, err := client.getUpstreams()
597+
if err != nil {
598+
return nil, fmt.Errorf("failed to get stats: %v", err)
599+
}
600+
601+
return &Stats{
602+
Connections: *cons,
603+
HTTPRequests: *requests,
604+
SSL: *ssl,
605+
ServerZones: *zones,
606+
Upstreams: *upstreams,
607+
}, nil
608+
}
609+
610+
func (client *NginxClient) getConnections() (*Connections, error) {
611+
var cons Connections
612+
err := client.get("connections", &cons)
613+
if err != nil {
614+
return nil, fmt.Errorf("failed to get connections: %v", err)
615+
}
616+
return &cons, nil
617+
}
618+
619+
func (client *NginxClient) getHTTPRequests() (*HTTPRequests, error) {
620+
var requests HTTPRequests
621+
622+
err := client.get("http/requests", &requests)
623+
if err != nil {
624+
return nil, fmt.Errorf("failed to get http requests: %v", err)
625+
}
626+
627+
return &requests, nil
628+
}
629+
630+
func (client *NginxClient) getSSL() (*SSL, error) {
631+
var ssl SSL
632+
err := client.get("ssl", &ssl)
633+
if err != nil {
634+
return nil, fmt.Errorf("failed to get ssl: %v", err)
635+
}
636+
return &ssl, nil
637+
}
638+
639+
func (client *NginxClient) getServerZones() (*ServerZones, error) {
640+
var zones ServerZones
641+
err := client.get("http/server_zones", &zones)
642+
if err != nil {
643+
return nil, fmt.Errorf("failed to get server zones: %v", err)
644+
}
645+
return &zones, err
646+
}
647+
648+
func (client *NginxClient) getUpstreams() (*Upstreams, error) {
649+
var upstreams Upstreams
650+
err := client.get("http/upstreams", &upstreams)
651+
if err != nil {
652+
return nil, fmt.Errorf("failed to get upstreams: %v", err)
653+
}
654+
return &upstreams, nil
655+
}

client/nginx_client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func TestDetermineUpdates(t *testing.T) {
107107
Server: "10.0.0.3:80",
108108
},
109109
}}, {
110-
// empty values
110+
// empty values
111111
}}
112112

113113
for _, test := range tests {

docker/test.conf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ server {
1616
}
1717
api write=on;
1818
}
19-
}
19+
status_zone test;
20+
}

tests/client_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ func TestStreamClient(t *testing.T) {
5151
}
5252

5353
streamServers, err := c.GetStreamServers(streamUpstream)
54+
if err != nil {
55+
t.Errorf("Error getting stream servers: %v", err)
56+
}
5457
if len(streamServers) != 0 {
5558
t.Errorf("Expected 0 servers, got %v", streamServers)
5659
}
@@ -175,6 +178,9 @@ func TestStreamUpstreamServerSlowStart(t *testing.T) {
175178
t.Errorf("Error adding upstream server: %v", err)
176179
}
177180
servers, err := c.GetStreamServers(streamUpstream)
181+
if err != nil {
182+
t.Errorf("Error getting stream servers: %v", err)
183+
}
178184
if len(servers) != 1 {
179185
t.Errorf("Too many servers")
180186
}
@@ -364,6 +370,9 @@ func TestUpstreamServerSlowStart(t *testing.T) {
364370
t.Errorf("Error adding upstream server: %v", err)
365371
}
366372
servers, err := c.GetHTTPServers(upstream)
373+
if err != nil {
374+
t.Errorf("Error getting HTTPServers: %v", err)
375+
}
367376
if len(servers) != 1 {
368377
t.Errorf("Too many servers")
369378
}
@@ -381,6 +390,63 @@ func TestUpstreamServerSlowStart(t *testing.T) {
381390
}
382391
}
383392

393+
func TestStats(t *testing.T) {
394+
httpClient := &http.Client{}
395+
c, err := client.NewNginxClient(httpClient, "http://127.0.0.1:8080/api")
396+
if err != nil {
397+
t.Fatalf("Error connecting to nginx: %v", err)
398+
}
399+
400+
// need upstream for stats
401+
server := client.UpstreamServer{
402+
Server: "127.0.0.1:2000",
403+
}
404+
err = c.AddHTTPServer(upstream, server)
405+
if err != nil {
406+
t.Errorf("Error adding upstream server: %v", err)
407+
}
408+
409+
stats, err := c.GetStats()
410+
if err != nil {
411+
t.Errorf("Error getting stats: %v", err)
412+
}
413+
414+
if stats.Connections.Accepted < 1 {
415+
t.Errorf("Bad connections: %v", stats.Connections)
416+
}
417+
if stats.HTTPRequests.Total < 1 {
418+
t.Errorf("Bad HTTPRequests: %v", stats.HTTPRequests)
419+
}
420+
// SSL metrics blank in this example
421+
if len(stats.ServerZones) < 1 {
422+
t.Errorf("No ServerZone metrics: %v", stats.ServerZones)
423+
}
424+
if val, ok := stats.ServerZones["test"]; ok {
425+
if val.Requests < 1 {
426+
t.Errorf("ServerZone stats missing: %v", val)
427+
}
428+
} else {
429+
t.Errorf("ServerZone 'test' not found")
430+
}
431+
if ups, ok := stats.Upstreams["test"]; ok {
432+
if len(ups.Peers) < 1 {
433+
t.Errorf("upstream server not visible in stats")
434+
} else {
435+
if ups.Peers[0].State != "up" {
436+
t.Errorf("upstream server state should be 'up'")
437+
}
438+
}
439+
} else {
440+
t.Errorf("Upstream 'test' not found")
441+
}
442+
443+
// cleanup upstream servers
444+
_, _, err = c.UpdateHTTPServers(upstream, []client.UpstreamServer{})
445+
if err != nil {
446+
t.Errorf("Couldn't remove servers: %v", err)
447+
}
448+
}
449+
384450
func compareUpstreamServers(x []client.UpstreamServer, y []client.UpstreamServer) bool {
385451
var xServers []string
386452
for _, us := range x {

0 commit comments

Comments
 (0)