From 44582e82765a3cbeaaa270006404fb0b50aaceb2 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Wed, 9 Nov 2022 16:28:30 +0000 Subject: [PATCH 1/9] Add instance list rest api --- main.go | 1 + src/plugins/rest_api.go | 113 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/plugins/rest_api.go diff --git a/main.go b/main.go index 96b51134a..f6420833d 100644 --- a/main.go +++ b/main.go @@ -189,6 +189,7 @@ func loadPlugins(commander client.Commander, binary *core.NginxBinaryType, env * plugins.NewFileWatcher(loadedConfig, env), plugins.NewFileWatchThrottle(), plugins.NewEvents(loadedConfig, env, sdkGRPC.NewMessageMeta(uuid.NewString()), binary), + plugins.NewRestApi(loadedConfig, env, binary), ) if len(loadedConfig.Nginx.NginxCountingSocket) > 0 { diff --git a/src/plugins/rest_api.go b/src/plugins/rest_api.go new file mode 100644 index 000000000..93f5d717a --- /dev/null +++ b/src/plugins/rest_api.go @@ -0,0 +1,113 @@ +package plugins + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "regexp" + + "github.com/nginx/agent/sdk/v2/proto" + "github.com/nginx/agent/v2/src/core" + "github.com/nginx/agent/v2/src/core/config" + log "github.com/sirupsen/logrus" +) + +type RestApi struct { + env core.Environment + nginxBinary core.NginxBinary + handler *NginxHandler +} + +type NginxHandler struct { + env core.Environment + nginxBinary core.NginxBinary +} + +func NewRestApi(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *RestApi { + return &RestApi{env: env, nginxBinary: nginxBinary} +} + +func (r *RestApi) Init(core.MessagePipeInterface) { + log.Info("REST API initializing") + go r.createHttpServer() +} + +func (r *RestApi) Close() { + log.Info("REST API is wrapping up") +} + +func (r *RestApi) Process(message *core.Message) { + log.Tracef("Process function in the rest_api.go, %s %v", message.Topic(), message.Data()) +} + +func (r *RestApi) Info() *core.Info { + return core.NewInfo("REST API Plugin", "v0.0.1") +} + +func (r *RestApi) Subscriptions() []string { + return []string{} +} + +func (r *RestApi) createHttpServer() { + mux := http.NewServeMux() + r.handler = &NginxHandler{r.env, r.nginxBinary} + mux.Handle("/nginx/", r.handler) + + log.Info("Starting REST API HTTP server") + + server := http.Server{ + Addr: ":9090", + Handler: mux, + } + + if err := server.ListenAndServe(); err != nil { + log.Fatalf("error listening to port: %v", err) + } +} + +var ( + instancesRegex = regexp.MustCompile(`^\/nginx[\/]*$`) +) + +func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("content-type", "application/json") + switch { + case r.Method == http.MethodGet && instancesRegex.MatchString(r.URL.Path): + h.sendInstanceDetailsPayload(w, r) + return + default: + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("not found")) + return + } +} + +func (h *NginxHandler) sendInstanceDetailsPayload(w http.ResponseWriter, r *http.Request) { + var nginxDetails []*proto.NginxDetails + + for _, proc := range h.env.Processes() { + if proc.IsMaster { + nginxDetails = append(nginxDetails, h.nginxBinary.GetNginxDetailsFromProcess(proc)) + } + } + + w.WriteHeader(http.StatusOK) + + if len(nginxDetails) == 0 { + log.Debug("No nginx instances found") + _, err := fmt.Fprint(w, "[]") + if err != nil { + log.Warnf("Failed to send instance details payload: %v", err) + } + return + } + + responseBodyBytes := new(bytes.Buffer) + json.NewEncoder(responseBodyBytes).Encode(nginxDetails) + + _, err := w.Write(responseBodyBytes.Bytes()) + if err != nil { + log.Warnf("Failed to send instance details payload: %v", err) + } +} From fc59d9ea846b51ec04487135d17c661976731320 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Thu, 10 Nov 2022 10:00:03 +0000 Subject: [PATCH 2/9] Split func responsibiliity --- src/plugins/rest_api.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/plugins/rest_api.go b/src/plugins/rest_api.go index 93f5d717a..584fd786b 100644 --- a/src/plugins/rest_api.go +++ b/src/plugins/rest_api.go @@ -54,7 +54,7 @@ func (r *RestApi) createHttpServer() { r.handler = &NginxHandler{r.env, r.nginxBinary} mux.Handle("/nginx/", r.handler) - log.Info("Starting REST API HTTP server") + log.Debug("Starting REST API HTTP server") server := http.Server{ Addr: ":9090", @@ -74,7 +74,7 @@ func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/json") switch { case r.Method == http.MethodGet && instancesRegex.MatchString(r.URL.Path): - h.sendInstanceDetailsPayload(w, r) + h.sendInstanceDetailsPayload(h.getNginxDetails(), w, r) return default: w.WriteHeader(http.StatusNotFound) @@ -83,15 +83,7 @@ func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -func (h *NginxHandler) sendInstanceDetailsPayload(w http.ResponseWriter, r *http.Request) { - var nginxDetails []*proto.NginxDetails - - for _, proc := range h.env.Processes() { - if proc.IsMaster { - nginxDetails = append(nginxDetails, h.nginxBinary.GetNginxDetailsFromProcess(proc)) - } - } - +func (h *NginxHandler) sendInstanceDetailsPayload(nginxDetails []*proto.NginxDetails, w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) if len(nginxDetails) == 0 { @@ -106,8 +98,20 @@ func (h *NginxHandler) sendInstanceDetailsPayload(w http.ResponseWriter, r *http responseBodyBytes := new(bytes.Buffer) json.NewEncoder(responseBodyBytes).Encode(nginxDetails) - _, err := w.Write(responseBodyBytes.Bytes()) + _, err := fmt.Fprint(w, responseBodyBytes.Bytes()) if err != nil { log.Warnf("Failed to send instance details payload: %v", err) } } + +func (h *NginxHandler) getNginxDetails() []*proto.NginxDetails { + var nginxDetails []*proto.NginxDetails + + for _, proc := range h.env.Processes() { + if proc.IsMaster { + nginxDetails = append(nginxDetails, h.nginxBinary.GetNginxDetailsFromProcess(proc)) + } + } + + return nginxDetails +} From 7f09cd79d88cdf0c6e76bc42328a779b922c2d21 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Thu, 10 Nov 2022 10:12:18 +0000 Subject: [PATCH 3/9] Add error handling for payload creation --- src/plugins/rest_api.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plugins/rest_api.go b/src/plugins/rest_api.go index 584fd786b..38497d859 100644 --- a/src/plugins/rest_api.go +++ b/src/plugins/rest_api.go @@ -96,9 +96,13 @@ func (h *NginxHandler) sendInstanceDetailsPayload(nginxDetails []*proto.NginxDet } responseBodyBytes := new(bytes.Buffer) - json.NewEncoder(responseBodyBytes).Encode(nginxDetails) + err := json.NewEncoder(responseBodyBytes).Encode(nginxDetails) + if err != nil { + log.Warnf("Failed to encode nginxDetails payload: %v", err) + return + } - _, err := fmt.Fprint(w, responseBodyBytes.Bytes()) + _, err = fmt.Fprint(w, responseBodyBytes.Bytes()) if err != nil { log.Warnf("Failed to send instance details payload: %v", err) } From cdf3a22595c673ec049f16d7012c68485e31c5dd Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Thu, 10 Nov 2022 10:17:02 +0000 Subject: [PATCH 4/9] Handle api not found error case --- src/plugins/rest_api.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/rest_api.go b/src/plugins/rest_api.go index 38497d859..d552e7e0b 100644 --- a/src/plugins/rest_api.go +++ b/src/plugins/rest_api.go @@ -78,7 +78,10 @@ func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return default: w.WriteHeader(http.StatusNotFound) - w.Write([]byte("not found")) + _, err := fmt.Fprint(w, []byte("not found")) + if err != nil { + log.Warnf("Failed to send api response: %v", err) + } return } } From 749064979637efc70004a9c801a1bef5533db364 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Fri, 11 Nov 2022 09:42:00 +0000 Subject: [PATCH 5/9] Make restPort configurable --- README.md | 1 + nginx-agent.conf | 1 + src/core/config/config.go | 3 ++- src/core/config/config_test.go | 8 ++++++-- src/core/config/defaults.go | 11 +++++++++-- src/core/config/types.go | 1 + src/plugins/testdata/configs/updated.conf | 1 + .../nginx/agent/v2/src/core/config/config.go | 3 ++- .../nginx/agent/v2/src/core/config/defaults.go | 11 +++++++++-- .../nginx/agent/v2/src/core/config/types.go | 1 + 10 files changed, 33 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 36977d060..5054b593b 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ server: # host of the control plane host: 127.0.0.1 grpcPort: 54789 # control plane grpc port + restPort: 9090 # tls options - NOT RECOMMENDED FOR PRODUCTION tls: enable: false diff --git a/nginx-agent.conf b/nginx-agent.conf index 549de3b38..ec91740a3 100644 --- a/nginx-agent.conf +++ b/nginx-agent.conf @@ -12,6 +12,7 @@ server: # host of the control plane host: 127.0.0.1 grpcPort: 443 + restPort: 9090 # provide servername overrides if using SNI # metrics: "" # command: "" diff --git a/src/core/config/config.go b/src/core/config/config.go index bd3a65246..8aad6ba39 100644 --- a/src/core/config/config.go +++ b/src/core/config/config.go @@ -326,7 +326,8 @@ func getNginx() Nginx { func getServer() Server { return Server{ Host: Viper.GetString(ServerHost), - GrpcPort: Viper.GetInt(ServerGrpcport), + GrpcPort: Viper.GetInt(ServerGrpcPort), + RestPort: Viper.GetInt(ServerRestPort), Token: Viper.GetString(ServerToken), Metrics: Viper.GetString(ServerMetrics), Command: Viper.GetString(ServerCommand), diff --git a/src/core/config/config_test.go b/src/core/config/config_test.go index 7af6da135..adfc51d7c 100644 --- a/src/core/config/config_test.go +++ b/src/core/config/config_test.go @@ -19,7 +19,8 @@ import ( const ( updatedServerHost = "192.168.0.1" - updatedServerPort = 11000 + updatedServerGrpcPort = 11000 + updatedServerRestPort = 9010 updatedLogLevel = "fatal" updatedLogPath = "./test-path" updatedConfigDirs = "/usr/local/etc/nginx" @@ -137,6 +138,7 @@ func TestGetConfig(t *testing.T) { assert.Equal(t, Defaults.Server.Host, config.Server.Host) assert.Equal(t, Defaults.Server.GrpcPort, config.Server.GrpcPort) + assert.Equal(t, Defaults.Server.RestPort, config.Server.RestPort) assert.Equal(t, Defaults.Server.Command, config.Server.Command) assert.Equal(t, Defaults.Server.Metrics, config.Server.Metrics) assert.True(t, len(config.AllowedDirectoriesMap) > 0) @@ -193,6 +195,7 @@ func TestGetConfig(t *testing.T) { // Everything else should still be default assert.Equal(t, Defaults.Server.Host, config.Server.Host) assert.Equal(t, Defaults.Server.GrpcPort, config.Server.GrpcPort) + assert.Equal(t, Defaults.Server.RestPort, config.Server.RestPort) assert.Equal(t, []string{updatedTag}, config.Tags) }) @@ -249,7 +252,8 @@ func TestGetConfig(t *testing.T) { require.NoError(t, err) assert.Equal(t, updatedServerHost, config.Server.Host) - assert.Equal(t, updatedServerPort, config.Server.GrpcPort) + assert.Equal(t, updatedServerGrpcPort, config.Server.GrpcPort) + assert.Equal(t, updatedServerRestPort, config.Server.RestPort) assert.Equal(t, updatedConfTags, config.Tags) // Check for updated values diff --git a/src/core/config/defaults.go b/src/core/config/defaults.go index 2cdfc9140..846faaa3c 100644 --- a/src/core/config/defaults.go +++ b/src/core/config/defaults.go @@ -39,6 +39,7 @@ var ( Server: Server{ Host: "127.0.0.1", GrpcPort: 443, + RestPort: 9090, Command: "", Metrics: "", // token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable @@ -107,7 +108,8 @@ const ( ServerKey = "server" ServerHost = ServerKey + agent_config.KeyDelimiter + "host" - ServerGrpcport = ServerKey + agent_config.KeyDelimiter + "grpcport" + ServerGrpcPort = ServerKey + agent_config.KeyDelimiter + "grpcport" + ServerRestPort = ServerKey + agent_config.KeyDelimiter + "restport" ServerToken = ServerKey + agent_config.KeyDelimiter + "token" ServerMetrics = ServerKey + agent_config.KeyDelimiter + "metrics" ServerCommand = ServerKey + agent_config.KeyDelimiter + "command" @@ -203,10 +205,15 @@ var ( DefaultValue: Defaults.Server.Host, }, &IntFlag{ - Name: ServerGrpcport, + Name: ServerGrpcPort, Usage: "The desired GRPC port to use for nginx-agent traffic.", DefaultValue: Defaults.Server.GrpcPort, }, + &IntFlag{ + Name: ServerRestPort, + Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.", + DefaultValue: Defaults.Server.RestPort, + }, &StringFlag{ Name: ServerToken, Usage: "An authentication token that grants nginx-agent access to the commander and metrics services. Auto-generated by default.", diff --git a/src/core/config/types.go b/src/core/config/types.go index f932e4b22..caaa6e1a2 100644 --- a/src/core/config/types.go +++ b/src/core/config/types.go @@ -33,6 +33,7 @@ type Config struct { type Server struct { Host string `mapstructure:"host" yaml:"-"` GrpcPort int `mapstructure:"grpcPort" yaml:"-"` + RestPort int `mapstructure:"restPort" yaml:"-"` Token string `mapstructure:"token" yaml:"-"` Metrics string `mapstructure:"metrics" yaml:"-"` Command string `mapstructure:"command" yaml:"-"` diff --git a/src/plugins/testdata/configs/updated.conf b/src/plugins/testdata/configs/updated.conf index 32874c4fe..e643476b5 100644 --- a/src/plugins/testdata/configs/updated.conf +++ b/src/plugins/testdata/configs/updated.conf @@ -1,6 +1,7 @@ server: host: 192.168.0.1 grpcPort: 11000 + restPort: 9010 config_dirs: /usr/local/etc/nginx log: level: fatal diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go index bd3a65246..8aad6ba39 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go @@ -326,7 +326,8 @@ func getNginx() Nginx { func getServer() Server { return Server{ Host: Viper.GetString(ServerHost), - GrpcPort: Viper.GetInt(ServerGrpcport), + GrpcPort: Viper.GetInt(ServerGrpcPort), + RestPort: Viper.GetInt(ServerRestPort), Token: Viper.GetString(ServerToken), Metrics: Viper.GetString(ServerMetrics), Command: Viper.GetString(ServerCommand), diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go index 2cdfc9140..846faaa3c 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go @@ -39,6 +39,7 @@ var ( Server: Server{ Host: "127.0.0.1", GrpcPort: 443, + RestPort: 9090, Command: "", Metrics: "", // token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable @@ -107,7 +108,8 @@ const ( ServerKey = "server" ServerHost = ServerKey + agent_config.KeyDelimiter + "host" - ServerGrpcport = ServerKey + agent_config.KeyDelimiter + "grpcport" + ServerGrpcPort = ServerKey + agent_config.KeyDelimiter + "grpcport" + ServerRestPort = ServerKey + agent_config.KeyDelimiter + "restport" ServerToken = ServerKey + agent_config.KeyDelimiter + "token" ServerMetrics = ServerKey + agent_config.KeyDelimiter + "metrics" ServerCommand = ServerKey + agent_config.KeyDelimiter + "command" @@ -203,10 +205,15 @@ var ( DefaultValue: Defaults.Server.Host, }, &IntFlag{ - Name: ServerGrpcport, + Name: ServerGrpcPort, Usage: "The desired GRPC port to use for nginx-agent traffic.", DefaultValue: Defaults.Server.GrpcPort, }, + &IntFlag{ + Name: ServerRestPort, + Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.", + DefaultValue: Defaults.Server.RestPort, + }, &StringFlag{ Name: ServerToken, Usage: "An authentication token that grants nginx-agent access to the commander and metrics services. Auto-generated by default.", diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go index f932e4b22..caaa6e1a2 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go @@ -33,6 +33,7 @@ type Config struct { type Server struct { Host string `mapstructure:"host" yaml:"-"` GrpcPort int `mapstructure:"grpcPort" yaml:"-"` + RestPort int `mapstructure:"restPort" yaml:"-"` Token string `mapstructure:"token" yaml:"-"` Metrics string `mapstructure:"metrics" yaml:"-"` Command string `mapstructure:"command" yaml:"-"` From a72a3dd4a0ff92779b7a5b4d6c5a48f61c5f89db Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Fri, 11 Nov 2022 09:42:34 +0000 Subject: [PATCH 6/9] Add restApi test coverage --- src/plugins/rest_api.go | 43 +++--- src/plugins/rest_api_test.go | 80 +++++++++++ .../nginx/agent/v2/src/plugins/rest_api.go | 129 ++++++++++++++++++ 3 files changed, 233 insertions(+), 19 deletions(-) create mode 100644 src/plugins/rest_api_test.go create mode 100644 test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go diff --git a/src/plugins/rest_api.go b/src/plugins/rest_api.go index d552e7e0b..e4839b4a1 100644 --- a/src/plugins/rest_api.go +++ b/src/plugins/rest_api.go @@ -10,13 +10,15 @@ import ( "github.com/nginx/agent/sdk/v2/proto" "github.com/nginx/agent/v2/src/core" "github.com/nginx/agent/v2/src/core/config" + log "github.com/sirupsen/logrus" ) type RestApi struct { - env core.Environment - nginxBinary core.NginxBinary - handler *NginxHandler + config *config.Config + env core.Environment + nginxBinary core.NginxBinary + nginxHandler *NginxHandler } type NginxHandler struct { @@ -25,7 +27,7 @@ type NginxHandler struct { } func NewRestApi(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *RestApi { - return &RestApi{env: env, nginxBinary: nginxBinary} + return &RestApi{config: config, env: env, nginxBinary: nginxBinary} } func (r *RestApi) Init(core.MessagePipeInterface) { @@ -51,13 +53,13 @@ func (r *RestApi) Subscriptions() []string { func (r *RestApi) createHttpServer() { mux := http.NewServeMux() - r.handler = &NginxHandler{r.env, r.nginxBinary} - mux.Handle("/nginx/", r.handler) + r.nginxHandler = &NginxHandler{r.env, r.nginxBinary} + mux.Handle("/nginx/", r.nginxHandler) log.Debug("Starting REST API HTTP server") server := http.Server{ - Addr: ":9090", + Addr: fmt.Sprintf(":%d", r.config.Server.RestPort), Handler: mux, } @@ -74,41 +76,44 @@ func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/json") switch { case r.Method == http.MethodGet && instancesRegex.MatchString(r.URL.Path): - h.sendInstanceDetailsPayload(h.getNginxDetails(), w, r) - return + err := sendInstanceDetailsPayload(h.getNginxDetails(), w, r) + if err != nil { + log.Warnf("Failed to send instance details payload: %v", err) + } default: w.WriteHeader(http.StatusNotFound) _, err := fmt.Fprint(w, []byte("not found")) if err != nil { log.Warnf("Failed to send api response: %v", err) } - return } } -func (h *NginxHandler) sendInstanceDetailsPayload(nginxDetails []*proto.NginxDetails, w http.ResponseWriter, r *http.Request) { +func sendInstanceDetailsPayload(nginxDetails []*proto.NginxDetails, w http.ResponseWriter, r *http.Request) error { w.WriteHeader(http.StatusOK) if len(nginxDetails) == 0 { log.Debug("No nginx instances found") _, err := fmt.Fprint(w, "[]") if err != nil { - log.Warnf("Failed to send instance details payload: %v", err) + return fmt.Errorf("failed to send payload: %v", err) } - return + + return nil } - responseBodyBytes := new(bytes.Buffer) - err := json.NewEncoder(responseBodyBytes).Encode(nginxDetails) + respBody := new(bytes.Buffer) + err := json.NewEncoder(respBody).Encode(nginxDetails) if err != nil { - log.Warnf("Failed to encode nginxDetails payload: %v", err) - return + return fmt.Errorf("failed to encode payload: %v", err) } - _, err = fmt.Fprint(w, responseBodyBytes.Bytes()) + _, err = fmt.Fprint(w, respBody) if err != nil { - log.Warnf("Failed to send instance details payload: %v", err) + return fmt.Errorf("failed to send payload: %v", err) } + + return nil } func (h *NginxHandler) getNginxDetails() []*proto.NginxDetails { diff --git a/src/plugins/rest_api_test.go b/src/plugins/rest_api_test.go new file mode 100644 index 000000000..f2aff70a9 --- /dev/null +++ b/src/plugins/rest_api_test.go @@ -0,0 +1,80 @@ +package plugins + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/nginx/agent/sdk/v2/proto" + "github.com/stretchr/testify/assert" +) + +func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) { + tests := []struct { + name string + nginxDetails []*proto.NginxDetails + expectedJSON string + }{ + { + name: "no instances", + nginxDetails: []*proto.NginxDetails{}, + expectedJSON: "[]", + }, + { + name: "single instance", + nginxDetails: []*proto.NginxDetails{ + { + NginxId: "1", Version: "21", ConfPath: "/etc/yo", ProcessId: "123", StartTime: 1238043824, + BuiltFromSource: false, + LoadableModules: []string{}, + RuntimeModules: []string{}, + Plus: &proto.NginxPlusMetaData{Enabled: true}, + ConfigureArgs: []string{}, + }, + }, + expectedJSON: `[{"nginx_id":"1","version":"21","conf_path":"/etc/yo","process_id":"123","process_path":"","start_time":1238043824,"built_from_source":false,"loadable_modules":[],"runtime_modules":[],"plus":{"enabled":true,"release":""},"ssl":null,"status_url":"","configure_args":[]}]` + "\n", + }, + { + name: "multi instance", + nginxDetails: []*proto.NginxDetails{ + { + NginxId: "1", Version: "21", ConfPath: "/etc/yo", ProcessId: "123", StartTime: 1238043824, + BuiltFromSource: false, + LoadableModules: []string{}, + RuntimeModules: []string{}, + Plus: &proto.NginxPlusMetaData{Enabled: true}, + ConfigureArgs: []string{}, + }, + { + NginxId: "2", Version: "21", ConfPath: "/etc/yo", ProcessId: "123", StartTime: 1238043824, + BuiltFromSource: false, + LoadableModules: []string{}, + RuntimeModules: []string{}, + Plus: &proto.NginxPlusMetaData{Enabled: true}, + ConfigureArgs: []string{}, + }, + }, + expectedJSON: `[{"nginx_id":"1","version":"21","conf_path":"/etc/yo","process_id":"123","process_path":"","start_time":1238043824,"built_from_source":false,"loadable_modules":[],"runtime_modules":[],"plus":{"enabled":true,"release":""},"ssl":null,"status_url":"","configure_args":[]},{"nginx_id":"2","version":"21","conf_path":"/etc/yo","process_id":"123","process_path":"","start_time":1238043824,"built_from_source":false,"loadable_modules":[],"runtime_modules":[],"plus":{"enabled":true,"release":""},"ssl":null,"status_url":"","configure_args":[]}]` + "\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + respRec := httptest.NewRecorder() + path := "/nginx/" + req := httptest.NewRequest(http.MethodGet, path, nil) + + err := sendInstanceDetailsPayload(tt.nginxDetails, respRec, req) + assert.NoError(t, err) + + resp := respRec.Result() + defer resp.Body.Close() + + jsonStr := respRec.Body.String() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.True(t, json.Valid([]byte(jsonStr))) + assert.Equal(t, tt.expectedJSON, jsonStr) + }) + } +} diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go new file mode 100644 index 000000000..e4839b4a1 --- /dev/null +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go @@ -0,0 +1,129 @@ +package plugins + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "regexp" + + "github.com/nginx/agent/sdk/v2/proto" + "github.com/nginx/agent/v2/src/core" + "github.com/nginx/agent/v2/src/core/config" + + log "github.com/sirupsen/logrus" +) + +type RestApi struct { + config *config.Config + env core.Environment + nginxBinary core.NginxBinary + nginxHandler *NginxHandler +} + +type NginxHandler struct { + env core.Environment + nginxBinary core.NginxBinary +} + +func NewRestApi(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *RestApi { + return &RestApi{config: config, env: env, nginxBinary: nginxBinary} +} + +func (r *RestApi) Init(core.MessagePipeInterface) { + log.Info("REST API initializing") + go r.createHttpServer() +} + +func (r *RestApi) Close() { + log.Info("REST API is wrapping up") +} + +func (r *RestApi) Process(message *core.Message) { + log.Tracef("Process function in the rest_api.go, %s %v", message.Topic(), message.Data()) +} + +func (r *RestApi) Info() *core.Info { + return core.NewInfo("REST API Plugin", "v0.0.1") +} + +func (r *RestApi) Subscriptions() []string { + return []string{} +} + +func (r *RestApi) createHttpServer() { + mux := http.NewServeMux() + r.nginxHandler = &NginxHandler{r.env, r.nginxBinary} + mux.Handle("/nginx/", r.nginxHandler) + + log.Debug("Starting REST API HTTP server") + + server := http.Server{ + Addr: fmt.Sprintf(":%d", r.config.Server.RestPort), + Handler: mux, + } + + if err := server.ListenAndServe(); err != nil { + log.Fatalf("error listening to port: %v", err) + } +} + +var ( + instancesRegex = regexp.MustCompile(`^\/nginx[\/]*$`) +) + +func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("content-type", "application/json") + switch { + case r.Method == http.MethodGet && instancesRegex.MatchString(r.URL.Path): + err := sendInstanceDetailsPayload(h.getNginxDetails(), w, r) + if err != nil { + log.Warnf("Failed to send instance details payload: %v", err) + } + default: + w.WriteHeader(http.StatusNotFound) + _, err := fmt.Fprint(w, []byte("not found")) + if err != nil { + log.Warnf("Failed to send api response: %v", err) + } + } +} + +func sendInstanceDetailsPayload(nginxDetails []*proto.NginxDetails, w http.ResponseWriter, r *http.Request) error { + w.WriteHeader(http.StatusOK) + + if len(nginxDetails) == 0 { + log.Debug("No nginx instances found") + _, err := fmt.Fprint(w, "[]") + if err != nil { + return fmt.Errorf("failed to send payload: %v", err) + } + + return nil + } + + respBody := new(bytes.Buffer) + err := json.NewEncoder(respBody).Encode(nginxDetails) + if err != nil { + return fmt.Errorf("failed to encode payload: %v", err) + } + + _, err = fmt.Fprint(w, respBody) + if err != nil { + return fmt.Errorf("failed to send payload: %v", err) + } + + return nil +} + +func (h *NginxHandler) getNginxDetails() []*proto.NginxDetails { + var nginxDetails []*proto.NginxDetails + + for _, proc := range h.env.Processes() { + if proc.IsMaster { + nginxDetails = append(nginxDetails, h.nginxBinary.GetNginxDetailsFromProcess(proc)) + } + } + + return nginxDetails +} From 4aa35e4f2b285c4c463255c9b15d17d2d9900e43 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Fri, 11 Nov 2022 12:36:07 +0000 Subject: [PATCH 7/9] WIP: Change config keys --- README.md | 3 +- main.go | 2 +- nginx-agent.conf | 4 ++- src/core/config/config.go | 8 ++++- src/core/config/config_test.go | 13 +++---- src/core/config/defaults.go | 25 +++++++++----- src/core/config/types.go | 6 +++- src/plugins/rest_api.go | 34 +++++++++---------- src/plugins/testdata/configs/nginx-agent.conf | 2 ++ src/plugins/testdata/configs/updated.conf | 3 +- .../nginx/agent/v2/src/core/config/config.go | 8 ++++- .../agent/v2/src/core/config/defaults.go | 25 +++++++++----- .../nginx/agent/v2/src/core/config/types.go | 6 +++- .../nginx/agent/v2/src/plugins/rest_api.go | 34 +++++++++---------- 14 files changed, 107 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 5054b593b..de25a16d4 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,8 @@ server: # host of the control plane host: 127.0.0.1 grpcPort: 54789 # control plane grpc port - restPort: 9090 +api: + port: 9090 # nginx-agent http api # tls options - NOT RECOMMENDED FOR PRODUCTION tls: enable: false diff --git a/main.go b/main.go index f6420833d..d7d9d7ae0 100644 --- a/main.go +++ b/main.go @@ -189,7 +189,7 @@ func loadPlugins(commander client.Commander, binary *core.NginxBinaryType, env * plugins.NewFileWatcher(loadedConfig, env), plugins.NewFileWatchThrottle(), plugins.NewEvents(loadedConfig, env, sdkGRPC.NewMessageMeta(uuid.NewString()), binary), - plugins.NewRestApi(loadedConfig, env, binary), + plugins.NewAgentAPI(loadedConfig, env, binary), ) if len(loadedConfig.Nginx.NginxCountingSocket) > 0 { diff --git a/nginx-agent.conf b/nginx-agent.conf index ec91740a3..ebd24b4e9 100644 --- a/nginx-agent.conf +++ b/nginx-agent.conf @@ -12,10 +12,12 @@ server: # host of the control plane host: 127.0.0.1 grpcPort: 443 - restPort: 9090 # provide servername overrides if using SNI # metrics: "" # command: "" +api: + # port to expose http api + port: 9090 # tls options tls: # enable tls in the nginx-agent setup for grpcs diff --git a/src/core/config/config.go b/src/core/config/config.go index 8aad6ba39..f5ef49cf8 100644 --- a/src/core/config/config.go +++ b/src/core/config/config.go @@ -162,6 +162,7 @@ func GetConfig(clientId string) (*Config, error) { ClientID: clientId, CloudAccountID: Viper.GetString(CloudAccountIdKey), Server: getServer(), + AgentAPI: getAgentAPI(), ConfigDirs: Viper.GetString(ConfigDirsKey), Log: getLog(), TLS: getTLS(), @@ -327,13 +328,18 @@ func getServer() Server { return Server{ Host: Viper.GetString(ServerHost), GrpcPort: Viper.GetInt(ServerGrpcPort), - RestPort: Viper.GetInt(ServerRestPort), Token: Viper.GetString(ServerToken), Metrics: Viper.GetString(ServerMetrics), Command: Viper.GetString(ServerCommand), } } +func getAgentAPI() AgentAPI { + return AgentAPI{ + Port: viper.GetInt(AgentAPIPort), + } +} + func getTLS() TLSConfig { return TLSConfig{ Enable: Viper.GetBool(TlsEnable), diff --git a/src/core/config/config_test.go b/src/core/config/config_test.go index adfc51d7c..aa30bab54 100644 --- a/src/core/config/config_test.go +++ b/src/core/config/config_test.go @@ -13,14 +13,13 @@ import ( "github.com/stretchr/testify/require" agent_config "github.com/nginx/agent/sdk/v2/agent/config" - sysutils "github.com/nginx/agent/v2/test/utils/system" ) const ( updatedServerHost = "192.168.0.1" updatedServerGrpcPort = 11000 - updatedServerRestPort = 9010 + updatedAgentAPIPort = 9010 updatedLogLevel = "fatal" updatedLogPath = "./test-path" updatedConfigDirs = "/usr/local/etc/nginx" @@ -138,9 +137,11 @@ func TestGetConfig(t *testing.T) { assert.Equal(t, Defaults.Server.Host, config.Server.Host) assert.Equal(t, Defaults.Server.GrpcPort, config.Server.GrpcPort) - assert.Equal(t, Defaults.Server.RestPort, config.Server.RestPort) assert.Equal(t, Defaults.Server.Command, config.Server.Command) assert.Equal(t, Defaults.Server.Metrics, config.Server.Metrics) + + assert.Equal(t, Defaults.AgentAPI.Port, config.AgentAPI.Port) + assert.True(t, len(config.AllowedDirectoriesMap) > 0) assert.Equal(t, Defaults.ConfigDirs, config.ConfigDirs) assert.Equal(t, Defaults.TLS.Enable, config.TLS.Enable) @@ -191,12 +192,12 @@ func TestGetConfig(t *testing.T) { // Check for updated values assert.Equal(t, updatedLogLevel, config.Log.Level) assert.Equal(t, updatedTag, config.DisplayName) + assert.Equal(t, []string{updatedTag}, config.Tags) // Everything else should still be default assert.Equal(t, Defaults.Server.Host, config.Server.Host) assert.Equal(t, Defaults.Server.GrpcPort, config.Server.GrpcPort) - assert.Equal(t, Defaults.Server.RestPort, config.Server.RestPort) - assert.Equal(t, []string{updatedTag}, config.Tags) + assert.Equal(t, Defaults.AgentAPI.Port, config.AgentAPI.Port) }) t.Run("test override defaults with config file values", func(t *testing.T) { @@ -253,7 +254,7 @@ func TestGetConfig(t *testing.T) { assert.Equal(t, updatedServerHost, config.Server.Host) assert.Equal(t, updatedServerGrpcPort, config.Server.GrpcPort) - assert.Equal(t, updatedServerRestPort, config.Server.RestPort) + assert.Equal(t, updatedAgentAPIPort, config.AgentAPI.Port) assert.Equal(t, updatedConfTags, config.Tags) // Check for updated values diff --git a/src/core/config/defaults.go b/src/core/config/defaults.go index 846faaa3c..25c599879 100644 --- a/src/core/config/defaults.go +++ b/src/core/config/defaults.go @@ -4,9 +4,9 @@ import ( "os" "time" - "github.com/google/uuid" - agent_config "github.com/nginx/agent/sdk/v2/agent/config" + + "github.com/google/uuid" log "github.com/sirupsen/logrus" ) @@ -39,13 +39,15 @@ var ( Server: Server{ Host: "127.0.0.1", GrpcPort: 443, - RestPort: 9090, Command: "", Metrics: "", // token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable // so setting to random uuid at the moment, tls connection won't work without the auth header Token: uuid.New().String(), }, + AgentAPI: AgentAPI{ + Port: 9090, + }, Nginx: Nginx{ Debug: false, NginxCountingSocket: "unix:/var/run/nginx-agent/nginx.sock", @@ -109,11 +111,15 @@ const ( ServerHost = ServerKey + agent_config.KeyDelimiter + "host" ServerGrpcPort = ServerKey + agent_config.KeyDelimiter + "grpcport" - ServerRestPort = ServerKey + agent_config.KeyDelimiter + "restport" ServerToken = ServerKey + agent_config.KeyDelimiter + "token" ServerMetrics = ServerKey + agent_config.KeyDelimiter + "metrics" ServerCommand = ServerKey + agent_config.KeyDelimiter + "command" + // viper keys used in config + APIKey = "api" + + AgentAPIPort = APIKey + agent_config.KeyDelimiter + "port" + // viper keys used in config TlsKey = "tls" @@ -209,11 +215,6 @@ var ( Usage: "The desired GRPC port to use for nginx-agent traffic.", DefaultValue: Defaults.Server.GrpcPort, }, - &IntFlag{ - Name: ServerRestPort, - Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.", - DefaultValue: Defaults.Server.RestPort, - }, &StringFlag{ Name: ServerToken, Usage: "An authentication token that grants nginx-agent access to the commander and metrics services. Auto-generated by default.", @@ -229,6 +230,12 @@ var ( Usage: "The name of the command server sent in the tls configuration.", DefaultValue: Defaults.Server.Command, }, + // API Config + &IntFlag{ + Name: AgentAPIPort, + Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.", + DefaultValue: Defaults.AgentAPI.Port, + }, &StringFlag{ Name: ConfigDirsKey, Usage: "Defines the paths that you want to grant nginx-agent read/write access to. This key is formatted as a string and follows Unix PATH format.", diff --git a/src/core/config/types.go b/src/core/config/types.go index caaa6e1a2..976cee3a6 100644 --- a/src/core/config/types.go +++ b/src/core/config/types.go @@ -12,6 +12,7 @@ type Config struct { ClientID string `mapstructure:"agent_id" yaml:"-"` CloudAccountID string `mapstructure:"cloud_account" yaml:"-"` Server Server `mapstructure:"server" yaml:"-"` + AgentAPI AgentAPI `mapstructure:"api" yaml:"-"` ConfigDirs string `mapstructure:"config-dirs" yaml:"-"` Log LogConfig `mapstructure:"log" yaml:"-"` TLS TLSConfig `mapstructure:"tls" yaml:"-"` @@ -33,7 +34,6 @@ type Config struct { type Server struct { Host string `mapstructure:"host" yaml:"-"` GrpcPort int `mapstructure:"grpcPort" yaml:"-"` - RestPort int `mapstructure:"restPort" yaml:"-"` Token string `mapstructure:"token" yaml:"-"` Metrics string `mapstructure:"metrics" yaml:"-"` Command string `mapstructure:"command" yaml:"-"` @@ -41,6 +41,10 @@ type Server struct { Target string `mapstructure:"target" yaml:"-"` } +type AgentAPI struct { + Port int `mapstructure:"port" yaml:"-"` +} + // LogConfig for logging type LogConfig struct { Level string `mapstructure:"level" yaml:"-"` diff --git a/src/plugins/rest_api.go b/src/plugins/rest_api.go index e4839b4a1..5da196ec2 100644 --- a/src/plugins/rest_api.go +++ b/src/plugins/rest_api.go @@ -14,7 +14,7 @@ import ( log "github.com/sirupsen/logrus" ) -type RestApi struct { +type AgentAPI struct { config *config.Config env core.Environment nginxBinary core.NginxBinary @@ -26,40 +26,40 @@ type NginxHandler struct { nginxBinary core.NginxBinary } -func NewRestApi(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *RestApi { - return &RestApi{config: config, env: env, nginxBinary: nginxBinary} +func NewAgentAPI(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *AgentAPI { + return &AgentAPI{config: config, env: env, nginxBinary: nginxBinary} } -func (r *RestApi) Init(core.MessagePipeInterface) { - log.Info("REST API initializing") - go r.createHttpServer() +func (a *AgentAPI) Init(core.MessagePipeInterface) { + log.Info("Agent API initializing") + go a.createHttpServer() } -func (r *RestApi) Close() { - log.Info("REST API is wrapping up") +func (a *AgentAPI) Close() { + log.Info("Agent API is wrapping up") } -func (r *RestApi) Process(message *core.Message) { +func (a *AgentAPI) Process(message *core.Message) { log.Tracef("Process function in the rest_api.go, %s %v", message.Topic(), message.Data()) } -func (r *RestApi) Info() *core.Info { - return core.NewInfo("REST API Plugin", "v0.0.1") +func (a *AgentAPI) Info() *core.Info { + return core.NewInfo("Agent API Plugin", "v0.0.1") } -func (r *RestApi) Subscriptions() []string { +func (a *AgentAPI) Subscriptions() []string { return []string{} } -func (r *RestApi) createHttpServer() { +func (a *AgentAPI) createHttpServer() { mux := http.NewServeMux() - r.nginxHandler = &NginxHandler{r.env, r.nginxBinary} - mux.Handle("/nginx/", r.nginxHandler) + a.nginxHandler = &NginxHandler{a.env, a.nginxBinary} + mux.Handle("/nginx/", a.nginxHandler) - log.Debug("Starting REST API HTTP server") + log.Debug("Starting Agent API HTTP server") server := http.Server{ - Addr: fmt.Sprintf(":%d", r.config.Server.RestPort), + Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port), Handler: mux, } diff --git a/src/plugins/testdata/configs/nginx-agent.conf b/src/plugins/testdata/configs/nginx-agent.conf index f3e21a7ef..a759d44df 100644 --- a/src/plugins/testdata/configs/nginx-agent.conf +++ b/src/plugins/testdata/configs/nginx-agent.conf @@ -10,6 +10,8 @@ server: token: "goodfellow" command: "test-server-command" metrics: "test-server-metrics" +api: + port: 9090 # tls options tls: # enable tls in the nginx-agent setup for grpcs diff --git a/src/plugins/testdata/configs/updated.conf b/src/plugins/testdata/configs/updated.conf index e643476b5..fe171db67 100644 --- a/src/plugins/testdata/configs/updated.conf +++ b/src/plugins/testdata/configs/updated.conf @@ -1,7 +1,8 @@ server: host: 192.168.0.1 grpcPort: 11000 - restPort: 9010 +api: + port: 9010 config_dirs: /usr/local/etc/nginx log: level: fatal diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go index 8aad6ba39..2bce3a3da 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go @@ -162,6 +162,7 @@ func GetConfig(clientId string) (*Config, error) { ClientID: clientId, CloudAccountID: Viper.GetString(CloudAccountIdKey), Server: getServer(), + AgentAPI: getAgentAPI(), ConfigDirs: Viper.GetString(ConfigDirsKey), Log: getLog(), TLS: getTLS(), @@ -327,13 +328,18 @@ func getServer() Server { return Server{ Host: Viper.GetString(ServerHost), GrpcPort: Viper.GetInt(ServerGrpcPort), - RestPort: Viper.GetInt(ServerRestPort), Token: Viper.GetString(ServerToken), Metrics: Viper.GetString(ServerMetrics), Command: Viper.GetString(ServerCommand), } } +func getAgentAPI() API { + return API{ + Port: viper.GetInt(AgentAPIPort), + } +} + func getTLS() TLSConfig { return TLSConfig{ Enable: Viper.GetBool(TlsEnable), diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go index 846faaa3c..140770b3b 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go @@ -4,9 +4,9 @@ import ( "os" "time" - "github.com/google/uuid" - agent_config "github.com/nginx/agent/sdk/v2/agent/config" + + "github.com/google/uuid" log "github.com/sirupsen/logrus" ) @@ -39,13 +39,15 @@ var ( Server: Server{ Host: "127.0.0.1", GrpcPort: 443, - RestPort: 9090, Command: "", Metrics: "", // token needs to be validated on the server side - can be overridden by the config value or the cli / environment variable // so setting to random uuid at the moment, tls connection won't work without the auth header Token: uuid.New().String(), }, + AgentAPI: API{ + Port: 9090, + }, Nginx: Nginx{ Debug: false, NginxCountingSocket: "unix:/var/run/nginx-agent/nginx.sock", @@ -109,11 +111,15 @@ const ( ServerHost = ServerKey + agent_config.KeyDelimiter + "host" ServerGrpcPort = ServerKey + agent_config.KeyDelimiter + "grpcport" - ServerRestPort = ServerKey + agent_config.KeyDelimiter + "restport" ServerToken = ServerKey + agent_config.KeyDelimiter + "token" ServerMetrics = ServerKey + agent_config.KeyDelimiter + "metrics" ServerCommand = ServerKey + agent_config.KeyDelimiter + "command" + // viper keys used in config + APIKey = "api" + + AgentAPIPort = APIKey + agent_config.KeyDelimiter + "port" + // viper keys used in config TlsKey = "tls" @@ -209,11 +215,6 @@ var ( Usage: "The desired GRPC port to use for nginx-agent traffic.", DefaultValue: Defaults.Server.GrpcPort, }, - &IntFlag{ - Name: ServerRestPort, - Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.", - DefaultValue: Defaults.Server.RestPort, - }, &StringFlag{ Name: ServerToken, Usage: "An authentication token that grants nginx-agent access to the commander and metrics services. Auto-generated by default.", @@ -229,6 +230,12 @@ var ( Usage: "The name of the command server sent in the tls configuration.", DefaultValue: Defaults.Server.Command, }, + // API Config + &IntFlag{ + Name: AgentAPIPort, + Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.", + DefaultValue: Defaults.AgentAPI.Port, + }, &StringFlag{ Name: ConfigDirsKey, Usage: "Defines the paths that you want to grant nginx-agent read/write access to. This key is formatted as a string and follows Unix PATH format.", diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go index caaa6e1a2..7671d2097 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go @@ -12,6 +12,7 @@ type Config struct { ClientID string `mapstructure:"agent_id" yaml:"-"` CloudAccountID string `mapstructure:"cloud_account" yaml:"-"` Server Server `mapstructure:"server" yaml:"-"` + AgentAPI API `mapstructure:"api" yaml:"-"` ConfigDirs string `mapstructure:"config-dirs" yaml:"-"` Log LogConfig `mapstructure:"log" yaml:"-"` TLS TLSConfig `mapstructure:"tls" yaml:"-"` @@ -33,7 +34,6 @@ type Config struct { type Server struct { Host string `mapstructure:"host" yaml:"-"` GrpcPort int `mapstructure:"grpcPort" yaml:"-"` - RestPort int `mapstructure:"restPort" yaml:"-"` Token string `mapstructure:"token" yaml:"-"` Metrics string `mapstructure:"metrics" yaml:"-"` Command string `mapstructure:"command" yaml:"-"` @@ -41,6 +41,10 @@ type Server struct { Target string `mapstructure:"target" yaml:"-"` } +type API struct { + Port int `mapstructure:"port" yaml:"-"` +} + // LogConfig for logging type LogConfig struct { Level string `mapstructure:"level" yaml:"-"` diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go index e4839b4a1..5da196ec2 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go @@ -14,7 +14,7 @@ import ( log "github.com/sirupsen/logrus" ) -type RestApi struct { +type AgentAPI struct { config *config.Config env core.Environment nginxBinary core.NginxBinary @@ -26,40 +26,40 @@ type NginxHandler struct { nginxBinary core.NginxBinary } -func NewRestApi(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *RestApi { - return &RestApi{config: config, env: env, nginxBinary: nginxBinary} +func NewAgentAPI(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *AgentAPI { + return &AgentAPI{config: config, env: env, nginxBinary: nginxBinary} } -func (r *RestApi) Init(core.MessagePipeInterface) { - log.Info("REST API initializing") - go r.createHttpServer() +func (a *AgentAPI) Init(core.MessagePipeInterface) { + log.Info("Agent API initializing") + go a.createHttpServer() } -func (r *RestApi) Close() { - log.Info("REST API is wrapping up") +func (a *AgentAPI) Close() { + log.Info("Agent API is wrapping up") } -func (r *RestApi) Process(message *core.Message) { +func (a *AgentAPI) Process(message *core.Message) { log.Tracef("Process function in the rest_api.go, %s %v", message.Topic(), message.Data()) } -func (r *RestApi) Info() *core.Info { - return core.NewInfo("REST API Plugin", "v0.0.1") +func (a *AgentAPI) Info() *core.Info { + return core.NewInfo("Agent API Plugin", "v0.0.1") } -func (r *RestApi) Subscriptions() []string { +func (a *AgentAPI) Subscriptions() []string { return []string{} } -func (r *RestApi) createHttpServer() { +func (a *AgentAPI) createHttpServer() { mux := http.NewServeMux() - r.nginxHandler = &NginxHandler{r.env, r.nginxBinary} - mux.Handle("/nginx/", r.nginxHandler) + a.nginxHandler = &NginxHandler{a.env, a.nginxBinary} + mux.Handle("/nginx/", a.nginxHandler) - log.Debug("Starting REST API HTTP server") + log.Debug("Starting Agent API HTTP server") server := http.Server{ - Addr: fmt.Sprintf(":%d", r.config.Server.RestPort), + Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port), Handler: mux, } From 923a0570b041f1b7c08dd9ac8e73cd65722cefe6 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Fri, 11 Nov 2022 12:44:39 +0000 Subject: [PATCH 8/9] Fix Viper not viper --- src/core/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config/config.go b/src/core/config/config.go index f5ef49cf8..63e74ce4d 100644 --- a/src/core/config/config.go +++ b/src/core/config/config.go @@ -336,7 +336,7 @@ func getServer() Server { func getAgentAPI() AgentAPI { return AgentAPI{ - Port: viper.GetInt(AgentAPIPort), + Port: Viper.GetInt(AgentAPIPort), } } From 2127b5687a31727e11ba79034329a36f492bec4b Mon Sep 17 00:00:00 2001 From: dhurley Date: Tue, 15 Nov 2022 17:46:40 +0000 Subject: [PATCH 9/9] Added component tests for agent API --- src/plugins/{rest_api.go => agent_api.go} | 11 +- .../{rest_api_test.go => agent_api_test.go} | 12 +- test/component/agent_api_test.go | 108 ++++++++++++++++++ .../nginx/agent/v2/src/core/config/config.go | 6 +- .../agent/v2/src/core/config/defaults.go | 2 +- .../nginx/agent/v2/src/core/config/types.go | 4 +- .../src/plugins/{rest_api.go => agent_api.go} | 11 +- 7 files changed, 135 insertions(+), 19 deletions(-) rename src/plugins/{rest_api.go => agent_api.go} (89%) rename src/plugins/{rest_api_test.go => agent_api_test.go} (64%) create mode 100644 test/component/agent_api_test.go rename test/performance/vendor/github.com/nginx/agent/v2/src/plugins/{rest_api.go => agent_api.go} (89%) diff --git a/src/plugins/rest_api.go b/src/plugins/agent_api.go similarity index 89% rename from src/plugins/rest_api.go rename to src/plugins/agent_api.go index 5da196ec2..5bb369be2 100644 --- a/src/plugins/rest_api.go +++ b/src/plugins/agent_api.go @@ -2,6 +2,7 @@ package plugins import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -17,6 +18,7 @@ import ( type AgentAPI struct { config *config.Config env core.Environment + server http.Server nginxBinary core.NginxBinary nginxHandler *NginxHandler } @@ -37,10 +39,13 @@ func (a *AgentAPI) Init(core.MessagePipeInterface) { func (a *AgentAPI) Close() { log.Info("Agent API is wrapping up") + if err := a.server.Shutdown(context.Background()); err != nil { + log.Errorf("Agent API HTTP Server Shutdown Error: %v", err) + } } func (a *AgentAPI) Process(message *core.Message) { - log.Tracef("Process function in the rest_api.go, %s %v", message.Topic(), message.Data()) + log.Tracef("Process function in the agent_api.go, %s %v", message.Topic(), message.Data()) } func (a *AgentAPI) Info() *core.Info { @@ -58,12 +63,12 @@ func (a *AgentAPI) createHttpServer() { log.Debug("Starting Agent API HTTP server") - server := http.Server{ + a.server = http.Server{ Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port), Handler: mux, } - if err := server.ListenAndServe(); err != nil { + if err := a.server.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("error listening to port: %v", err) } } diff --git a/src/plugins/rest_api_test.go b/src/plugins/agent_api_test.go similarity index 64% rename from src/plugins/rest_api_test.go rename to src/plugins/agent_api_test.go index f2aff70a9..2b243167f 100644 --- a/src/plugins/rest_api_test.go +++ b/src/plugins/agent_api_test.go @@ -14,12 +14,10 @@ func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) { tests := []struct { name string nginxDetails []*proto.NginxDetails - expectedJSON string }{ { name: "no instances", nginxDetails: []*proto.NginxDetails{}, - expectedJSON: "[]", }, { name: "single instance", @@ -33,7 +31,6 @@ func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) { ConfigureArgs: []string{}, }, }, - expectedJSON: `[{"nginx_id":"1","version":"21","conf_path":"/etc/yo","process_id":"123","process_path":"","start_time":1238043824,"built_from_source":false,"loadable_modules":[],"runtime_modules":[],"plus":{"enabled":true,"release":""},"ssl":null,"status_url":"","configure_args":[]}]` + "\n", }, { name: "multi instance", @@ -55,7 +52,6 @@ func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) { ConfigureArgs: []string{}, }, }, - expectedJSON: `[{"nginx_id":"1","version":"21","conf_path":"/etc/yo","process_id":"123","process_path":"","start_time":1238043824,"built_from_source":false,"loadable_modules":[],"runtime_modules":[],"plus":{"enabled":true,"release":""},"ssl":null,"status_url":"","configure_args":[]},{"nginx_id":"2","version":"21","conf_path":"/etc/yo","process_id":"123","process_path":"","start_time":1238043824,"built_from_source":false,"loadable_modules":[],"runtime_modules":[],"plus":{"enabled":true,"release":""},"ssl":null,"status_url":"","configure_args":[]}]` + "\n", }, } for _, tt := range tests { @@ -70,11 +66,13 @@ func TestNginxHandler_sendInstanceDetailsPayload(t *testing.T) { resp := respRec.Result() defer resp.Body.Close() - jsonStr := respRec.Body.String() + var nginxDetailsResponse []*proto.NginxDetails + err = json.Unmarshal(respRec.Body.Bytes(), &nginxDetailsResponse) + assert.Nil(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) - assert.True(t, json.Valid([]byte(jsonStr))) - assert.Equal(t, tt.expectedJSON, jsonStr) + assert.True(t, json.Valid(respRec.Body.Bytes())) + assert.Equal(t, tt.nginxDetails, nginxDetailsResponse) }) } } diff --git a/test/component/agent_api_test.go b/test/component/agent_api_test.go new file mode 100644 index 000000000..c68a80774 --- /dev/null +++ b/test/component/agent_api_test.go @@ -0,0 +1,108 @@ +package component + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "testing" + + "github.com/nginx/agent/sdk/v2/proto" + "github.com/nginx/agent/v2/src/core" + "github.com/nginx/agent/v2/src/core/config" + "github.com/nginx/agent/v2/src/plugins" + tutils "github.com/nginx/agent/v2/test/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGetNginxInstances(t *testing.T) { + port := 9090 + processID := "12345" + var pid int32 = 12345 + + tests := []struct { + name string + nginxDetails *proto.NginxDetails + expectedJSON string + }{ + { + name: "no instances", + nginxDetails: nil, + }, + { + name: "single instance", + nginxDetails: &proto.NginxDetails{ + NginxId: "45d4sf5d4sf4e8s4f8es4564", Version: "21", ConfPath: "/etc/nginx/conf", ProcessId: processID, StartTime: 1238043824, + BuiltFromSource: false, + Plus: &proto.NginxPlusMetaData{Enabled: true}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + conf := &config.Config{ + AgentAPI: config.AgentAPI{ + Port: port, + }, + } + + mockEnvironment := tutils.NewMockEnvironment() + if tt.nginxDetails == nil { + mockEnvironment.On("Processes").Return([]core.Process{{Pid: pid, IsMaster: false}}) + } else { + mockEnvironment.On("Processes").Return([]core.Process{{Pid: pid, IsMaster: true}}) + } + + mockNginxBinary := tutils.NewMockNginxBinary() + mockNginxBinary.On("GetNginxDetailsFromProcess", mock.Anything).Return(tt.nginxDetails) + + agentAPI := plugins.NewAgentAPI(conf, mockEnvironment, mockNginxBinary) + agentAPI.Init(core.NewMockMessagePipe(context.TODO())) + + response, err := http.Get(fmt.Sprintf("http://localhost:%d/nginx/", port)) + assert.Nil(t, err) + + responseData, err := io.ReadAll(response.Body) + assert.Nil(t, err) + + var nginxDetailsResponse []*proto.NginxDetails + err = json.Unmarshal(responseData, &nginxDetailsResponse) + assert.Nil(t, err) + + assert.Equal(t, http.StatusOK, response.StatusCode) + assert.True(t, json.Valid(responseData)) + if tt.nginxDetails == nil { + assert.Equal(t, 0, len(nginxDetailsResponse)) + } else { + assert.Equal(t, 1, len(nginxDetailsResponse)) + assert.Equal(t, tt.nginxDetails, nginxDetailsResponse[0]) + } + + agentAPI.Close() + }) + } +} + +func TestInvalidPath(t *testing.T) { + port := 9090 + + conf := &config.Config{ + AgentAPI: config.AgentAPI{ + Port: port, + }, + } + + mockEnvironment := tutils.NewMockEnvironment() + mockNginxBinary := tutils.NewMockNginxBinary() + + agentAPI := plugins.NewAgentAPI(conf, mockEnvironment, mockNginxBinary) + agentAPI.Init(core.NewMockMessagePipe(context.TODO())) + + response, err := http.Get(fmt.Sprintf("http://localhost:%d/invalid/", port)) + assert.Nil(t, err) + + assert.Equal(t, http.StatusNotFound, response.StatusCode) +} diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go index 2bce3a3da..63e74ce4d 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/config.go @@ -334,9 +334,9 @@ func getServer() Server { } } -func getAgentAPI() API { - return API{ - Port: viper.GetInt(AgentAPIPort), +func getAgentAPI() AgentAPI { + return AgentAPI{ + Port: Viper.GetInt(AgentAPIPort), } } diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go index 140770b3b..25c599879 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/defaults.go @@ -45,7 +45,7 @@ var ( // so setting to random uuid at the moment, tls connection won't work without the auth header Token: uuid.New().String(), }, - AgentAPI: API{ + AgentAPI: AgentAPI{ Port: 9090, }, Nginx: Nginx{ diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go index 7671d2097..976cee3a6 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/core/config/types.go @@ -12,7 +12,7 @@ type Config struct { ClientID string `mapstructure:"agent_id" yaml:"-"` CloudAccountID string `mapstructure:"cloud_account" yaml:"-"` Server Server `mapstructure:"server" yaml:"-"` - AgentAPI API `mapstructure:"api" yaml:"-"` + AgentAPI AgentAPI `mapstructure:"api" yaml:"-"` ConfigDirs string `mapstructure:"config-dirs" yaml:"-"` Log LogConfig `mapstructure:"log" yaml:"-"` TLS TLSConfig `mapstructure:"tls" yaml:"-"` @@ -41,7 +41,7 @@ type Server struct { Target string `mapstructure:"target" yaml:"-"` } -type API struct { +type AgentAPI struct { Port int `mapstructure:"port" yaml:"-"` } diff --git a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go similarity index 89% rename from test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go rename to test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go index 5da196ec2..5bb369be2 100644 --- a/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/rest_api.go +++ b/test/performance/vendor/github.com/nginx/agent/v2/src/plugins/agent_api.go @@ -2,6 +2,7 @@ package plugins import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -17,6 +18,7 @@ import ( type AgentAPI struct { config *config.Config env core.Environment + server http.Server nginxBinary core.NginxBinary nginxHandler *NginxHandler } @@ -37,10 +39,13 @@ func (a *AgentAPI) Init(core.MessagePipeInterface) { func (a *AgentAPI) Close() { log.Info("Agent API is wrapping up") + if err := a.server.Shutdown(context.Background()); err != nil { + log.Errorf("Agent API HTTP Server Shutdown Error: %v", err) + } } func (a *AgentAPI) Process(message *core.Message) { - log.Tracef("Process function in the rest_api.go, %s %v", message.Topic(), message.Data()) + log.Tracef("Process function in the agent_api.go, %s %v", message.Topic(), message.Data()) } func (a *AgentAPI) Info() *core.Info { @@ -58,12 +63,12 @@ func (a *AgentAPI) createHttpServer() { log.Debug("Starting Agent API HTTP server") - server := http.Server{ + a.server = http.Server{ Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port), Handler: mux, } - if err := server.ListenAndServe(); err != nil { + if err := a.server.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("error listening to port: %v", err) } }