From df96e6ba65c12ae4e744041a28381bbc72135b58 Mon Sep 17 00:00:00 2001 From: Donal Hurley Date: Fri, 6 Oct 2023 15:44:36 +0100 Subject: [PATCH] Add checks to see if stream endpoints exist before calling them (#174) * Add checks to see if stream endpoints exist before calling them * Add unit tests * Renamed functions --------- Co-authored-by: Donal Hurley --- client/nginx.go | 85 ++++++++++++++++++++++++++++++++++---------- client/nginx_test.go | 52 +++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 2 ++ 4 files changed, 122 insertions(+), 19 deletions(-) diff --git a/client/nginx.go b/client/nginx.go index 206ecf8f..60798f83 100644 --- a/client/nginx.go +++ b/client/nginx.go @@ -11,6 +11,8 @@ import ( "reflect" "strings" "time" + + "golang.org/x/exp/slices" ) const ( @@ -1149,6 +1151,11 @@ func determineStreamUpdates(updatedServers []StreamUpstreamServer, nginxServers // GetStats gets process, slab, connection, request, ssl, zone, stream zone, upstream and stream upstream related stats from the NGINX Plus API. func (client *NginxClient) GetStats() (*Stats, error) { + endpoints, err := client.GetAvailableEndpoints() + if err != nil { + return nil, fmt.Errorf("failed to get stats: %w", err) + } + info, err := client.GetNginxInfo() if err != nil { return nil, fmt.Errorf("failed to get stats: %w", err) @@ -1194,21 +1201,6 @@ func (client *NginxClient) GetStats() (*Stats, error) { return nil, fmt.Errorf("failed to get stats: %w", err) } - streamZones, err := client.GetStreamServerZones() - if err != nil { - return nil, fmt.Errorf("failed to get stats: %w", err) - } - - streamUpstreams, err := client.GetStreamUpstreams() - if err != nil { - return nil, fmt.Errorf("failed to get stats: %w", err) - } - - streamZoneSync, err := client.GetStreamZoneSync() - if err != nil { - return nil, fmt.Errorf("failed to get stats: %w", err) - } - locationZones, err := client.GetLocationZones() if err != nil { return nil, fmt.Errorf("failed to get stats: %w", err) @@ -1229,14 +1221,49 @@ func (client *NginxClient) GetStats() (*Stats, error) { return nil, fmt.Errorf("failed to get stats: %w", err) } - limitConnsStream, err := client.GetStreamConnectionsLimit() + workers, err := client.GetWorkers() if err != nil { return nil, fmt.Errorf("failed to get stats: %w", err) } - workers, err := client.GetWorkers() - if err != nil { - return nil, fmt.Errorf("failed to get stats: %w", err) + streamZones := &StreamServerZones{} + streamUpstreams := &StreamUpstreams{} + limitConnsStream := &StreamLimitConnections{} + streamZoneSync := &StreamZoneSync{} + + if slices.Contains(endpoints, "stream") { + streamEndpoints, err := client.GetAvailableStreamEndpoints() + if err != nil { + return nil, fmt.Errorf("failed to get stats: %w", err) + } + + if slices.Contains(streamEndpoints, "server_zones") { + streamZones, err = client.GetStreamServerZones() + if err != nil { + return nil, fmt.Errorf("failed to get stats: %w", err) + } + } + + if slices.Contains(streamEndpoints, "upstreams") { + streamUpstreams, err = client.GetStreamUpstreams() + if err != nil { + return nil, fmt.Errorf("failed to get stats: %w", err) + } + } + + if slices.Contains(streamEndpoints, "limit_conns") { + limitConnsStream, err = client.GetStreamConnectionsLimit() + if err != nil { + return nil, fmt.Errorf("failed to get stats: %w", err) + } + } + + if slices.Contains(streamEndpoints, "zone_sync") { + streamZoneSync, err = client.GetStreamZoneSync() + if err != nil { + return nil, fmt.Errorf("failed to get stats: %w", err) + } + } } return &Stats{ @@ -1261,6 +1288,26 @@ func (client *NginxClient) GetStats() (*Stats, error) { }, nil } +// GetAvailableEndpoints returns available endpoints in the API. +func (client *NginxClient) GetAvailableEndpoints() ([]string, error) { + var endpoints []string + err := client.get("", &endpoints) + if err != nil { + return nil, fmt.Errorf("failed to get endpoints: %w", err) + } + return endpoints, nil +} + +// GetAvailableStreamEndpoints returns available stream endpoints in the API. +func (client *NginxClient) GetAvailableStreamEndpoints() ([]string, error) { + var endpoints []string + err := client.get("stream", &endpoints) + if err != nil { + return nil, fmt.Errorf("failed to get endpoints: %w", err) + } + return endpoints, nil +} + // GetNginxInfo returns Nginx stats. func (client *NginxClient) GetNginxInfo() (*NginxInfo, error) { var info NginxInfo diff --git a/client/nginx_test.go b/client/nginx_test.go index 0acf9914..3465f6a2 100644 --- a/client/nginx_test.go +++ b/client/nginx_test.go @@ -4,6 +4,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "strings" "testing" ) @@ -589,3 +590,54 @@ func TestClientWithHTTPClient(t *testing.T) { t.Fatalf("expected client to be nil, but got %v", client) } } + +func TestGetStats_NoStreamEndpoint(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/" { + _, err := w.Write([]byte(`[4, 5, 6, 7]`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } else if r.RequestURI == "/7/" { + _, err := w.Write([]byte(`["nginx","processes","connections","slabs","http","resolvers","ssl"]`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } else if strings.HasPrefix(r.RequestURI, "/7/stream") { + t.Fatal("Stream endpoint should not be called since it does not exist.") + } else { + _, err := w.Write([]byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + })) + defer ts.Close() + + // Test creating a new client with a supported API version on the server + client, err := NewNginxClient(ts.URL, WithAPIVersion(7), WithCheckAPI()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if client == nil { + t.Fatalf("client is nil") + } + + stats, err := client.GetStats() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(stats.StreamServerZones, StreamServerZones{}) { + t.Fatalf("StreamServerZones: expected %v, actual %v", StreamServerZones{}, stats.StreamServerZones) + } + if !reflect.DeepEqual(stats.StreamLimitConnections, StreamLimitConnections{}) { + t.Fatalf("StreamLimitConnections: expected %v, actual %v", StreamLimitConnections{}, stats.StreamLimitConnections) + } + if !reflect.DeepEqual(stats.StreamUpstreams, StreamUpstreams{}) { + t.Fatalf("StreamUpstreams: expected %v, actual %v", StreamUpstreams{}, stats.StreamUpstreams) + } + if !reflect.DeepEqual(stats.StreamZoneSync, &StreamZoneSync{}) { + t.Fatalf("StreamZoneSync: expected %v, actual %v", &StreamZoneSync{}, stats.StreamZoneSync) + } +} diff --git a/go.mod b/go.mod index 8916c37c..06f94a0c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/nginxinc/nginx-plus-go-client go 1.19 + +require golang.org/x/exp v0.0.0-20230905200255-921286631fa9 diff --git a/go.sum b/go.sum index e69de29b..fa6da3f7 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=