// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package logging import ( "bytes" "fmt" "io" "net/http" "net/http/httptest" "os" "testing" "time" "github.com/aws/aws-app-mesh-agent/agent/config" "github.com/aws/aws-app-mesh-agent/agent/internal/netlistenertest" mux "github.com/gorilla/mux" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" rate "golang.org/x/time/rate" ) func buildHandler(agentConfig *config.AgentConfig) EnvoyLoggingHandler { return EnvoyLoggingHandler{ AgentConfig: *agentConfig, Limiter: rate.NewLimiter(config.TPS_LIMIT, config.BURST_TPS_LIMIT), } } func TestEnvoyLoggingLevelGetRequest(t *testing.T) { var agentConfig config.AgentConfig agentConfig.SetDefaults() envoyHandler := buildHandler(&agentConfig) srv := httptest.NewServer(http.HandlerFunc(envoyHandler.LoggingHandler)) defer srv.Close() res, err := http.Get(srv.URL) assert.Nil(t, err) assert.NotNil(t, res) defer res.Body.Close() assert.Equal(t, http.StatusBadRequest, res.StatusCode) } func TestEnvoyLoggingLevelPostWithBody(t *testing.T) { var agentConfig config.AgentConfig agentConfig.SetDefaults() envoyHandler := buildHandler(&agentConfig) srv := httptest.NewServer(http.HandlerFunc(envoyHandler.LoggingHandler)) defer srv.Close() body := bytes.NewBuffer([]byte("ICantSleepBecauseTheresaTigerInMyCloset")) res, err := http.Post(srv.URL, "text/html", body) assert.Nil(t, err) assert.NotNil(t, res) defer res.Body.Close() assert.Equal(t, http.StatusBadRequest, res.StatusCode) } func TestEnvoyLoggingLevelPostWithNoParameters(t *testing.T) { var agentConfig config.AgentConfig agentConfig.SetDefaults() envoyHandler := buildHandler(&agentConfig) srv := httptest.NewServer(http.HandlerFunc(envoyHandler.LoggingHandler)) defer srv.Close() res, err := http.Post(srv.URL, "", nil) assert.Nil(t, err) assert.NotNil(t, res) defer res.Body.Close() assert.Equal(t, http.StatusBadRequest, res.StatusCode) } func TestEnvoyLoggingLevelPostWithInvalidLevel(t *testing.T) { var agentConfig config.AgentConfig agentConfig.SetDefaults() envoyHandler := buildHandler(&agentConfig) srv := httptest.NewServer(http.HandlerFunc(envoyHandler.LoggingHandler)) defer srv.Close() res, err := http.Post(fmt.Sprintf("%s?%s", srv.URL, "foolevel=debug"), "", nil) assert.Nil(t, err) assert.NotNil(t, res) defer res.Body.Close() assert.Equal(t, http.StatusBadRequest, res.StatusCode) } func TestEnvoyLoggingLevelPostWithEncodedEquals(t *testing.T) { var agentConfig config.AgentConfig agentConfig.SetDefaults() envoyHandler := buildHandler(&agentConfig) srv := httptest.NewServer(http.HandlerFunc(envoyHandler.LoggingHandler)) defer srv.Close() res, err := http.Post(fmt.Sprintf("%s/%s", srv.URL, "level%3ddebug"), "", nil) assert.Nil(t, err) assert.NotNil(t, res) defer res.Body.Close() assert.Equal(t, http.StatusBadRequest, res.StatusCode) } func TestEnvoyLoggingLevelPostWithReservedCharacters(t *testing.T) { var agentConfig config.AgentConfig agentConfig.SetDefaults() envoyHandler := buildHandler(&agentConfig) srv := httptest.NewServer(http.HandlerFunc(envoyHandler.LoggingHandler)) defer srv.Close() res, err := http.Post(fmt.Sprintf("%s/%s", srv.URL, "level=?^!@"), "", nil) assert.Nil(t, err) assert.NotNil(t, res) defer res.Body.Close() assert.Equal(t, http.StatusBadRequest, res.StatusCode) } func setupAndStartServerListener(t *testing.T, handler http.Handler, ctx *netlistenertest.ListenContext) *httptest.Server { server := httptest.NewUnstartedServer(handler) err := server.Listener.Close() assert.Nil(t, err) server.Listener = *ctx.Listener server.Start() return server } func TestEnvoyLoggingLevelChange(t *testing.T) { os.Setenv("ENVOY_ADMIN_MODE", "tcp") defer os.Unsetenv("ENVOY_ADMIN_MODE") var agentConfig config.AgentConfig var envoyCtx netlistenertest.ListenContext var agentCtx netlistenertest.ListenContext agentConfig.SetDefaults() err := agentCtx.GetPortListener() assert.Nil(t, err) agentConfig.AgentHttpPort = agentCtx.Port err = envoyCtx.CreateEnvoyAdminListener(&agentConfig) assert.Nil(t, err) defer envoyCtx.Close() defer agentCtx.Close() // =========================== Envoy Management Setup =========================== envoyRouter := mux.NewRouter() // Using a subset of the modules here for brevity logErrorResponse := ` active loggers: admin: error aws: error assert: error backtrace: error cache_filter: error client: error ` envoyRouter.HandleFunc(agentConfig.EnvoyLoggingUrl, func(w http.ResponseWriter, r *http.Request) { // if there's a query parmeter where the level is debug return the debug reponse if r.URL.Query().Get("level") == "error" { io.WriteString(w, logErrorResponse) return } http.Error(w, "Invalid request for test", http.StatusBadRequest) }) envoyManagmentServer := setupAndStartServerListener( t, envoyRouter, &envoyCtx) defer envoyManagmentServer.Close() // =========================== Agent Listener Setup =========================== agentRouter := mux.NewRouter() // Setup the Envoy logging handler envoyHandler := buildHandler(&agentConfig) agentRouter.HandleFunc(config.AGENT_LOGGING_ENDPOINT_URL, envoyHandler.LoggingHandler) agentHttpServer := setupAndStartServerListener( t, agentRouter, &agentCtx) defer agentHttpServer.Close() // Make a request to the agent to set the level. If we get back a 200 // It indicates we are successfully able to POST to Envoy and confirm we // updated the logging level url := fmt.Sprintf("%s%s?level=%s", agentHttpServer.URL, config.AGENT_LOGGING_ENDPOINT_URL, "error") log.Debugf("Using test url for agent [%s]\n", url) res, err := http.Post(url, "", nil) assert.Nil(t, err) assert.Equal(t, http.StatusOK, res.StatusCode) } func TestEnvoyLoggingLevelReset(t *testing.T) { os.Setenv("APPNET_AGENT_LOGGING_RESET_TIMEOUT", "2") defer os.Unsetenv("APPNET_AGENT_LOGGING_RESET_TIMEOUT") os.Setenv("ENVOY_ADMIN_MODE", "tcp") defer os.Unsetenv("ENVOY_ADMIN_MODE") var agentConfig config.AgentConfig agentConfig.SetDefaults() var envoyCtx netlistenertest.ListenContext var agentCtx netlistenertest.ListenContext err := agentCtx.GetPortListener() assert.Nil(t, err) agentConfig.AgentHttpPort = agentCtx.Port err = envoyCtx.CreateEnvoyAdminListener(&agentConfig) assert.Nil(t, err) defer envoyCtx.Close() defer agentCtx.Close() agentConfig.EnvoyLogLevel = "trace" // =========================== Envoy Management Setup =========================== envoyRouter := mux.NewRouter() // Using a subset of the modules here for brevity logTraceResponse := ` active loggers: admin: trace aws: trace assert: trace backtrace: trace cache_filter: trace client: trace ` logDebugResponse := ` active loggers: admin: debug aws: debug assert: debug backtrace: debug cache_filter: debug client: debug ` debugLevelSet := false var debugTimeStamp time.Time = time.Now() traceLevelSet := false var traceTimeStamp time.Time = time.Now() envoyRouter.HandleFunc(agentConfig.EnvoyLoggingUrl, func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("level") == "debug" { w.WriteHeader(http.StatusOK) io.WriteString(w, logDebugResponse) debugLevelSet = true debugTimeStamp = time.Now() return } if r.URL.Query().Get("level") == "trace" { w.WriteHeader(http.StatusOK) io.WriteString(w, logTraceResponse) traceLevelSet = true traceTimeStamp = time.Now() return } http.Error(w, "Invalid request for test", http.StatusBadRequest) }) envoyManagmentServer := setupAndStartServerListener( t, envoyRouter, &envoyCtx) defer envoyManagmentServer.Close() // =========================== Agent Listener Setup =========================== agentRouter := mux.NewRouter() // Setup the Envoy logging handler envoyHandler := buildHandler(&agentConfig) agentRouter.HandleFunc(config.AGENT_LOGGING_ENDPOINT_URL, envoyHandler.LoggingHandler) agentHttpServer := setupAndStartServerListener( t, agentRouter, &agentCtx) defer agentHttpServer.Close() // Make a request to the agent to set the level. We get back a 200 // indicating we are successfully able to POST to Envoy and confirm // the logging level url := fmt.Sprintf("%s%s?level=%s", agentHttpServer.URL, config.AGENT_LOGGING_ENDPOINT_URL, "debug") log.Debugf("Using test url for agent [%s]\n", url) // Set the log level to debug res, err := http.Post(url, "", nil) assert.Nil(t, err) assert.Equal(t, http.StatusOK, res.StatusCode) assert.True(t, debugLevelSet) // Try changing the log level again. We should get back a 304 res, err = http.Post(url, "", nil) assert.Nil(t, err) assert.Equal(t, http.StatusNotModified, res.StatusCode) // Allow the goroutine to execute. It's configured for 2 seconds time.Sleep(5 * time.Second) // Verify that our test server was called to reset the log level to trace delta := traceTimeStamp.Sub(debugTimeStamp) assert.True(t, traceLevelSet) assert.GreaterOrEqual(t, delta, int64(2)) }