Backport of feature - [NET - 4005] - [Supportability] Reloadable Configuration - enable_debug into release/1.16.x (#17969)

* backport of commit 10f500e895d92cc3691ade7b74a33db755d22039

* backport of commit e08c30910167fa2c6dd5a639b92338d2389457e4

* backport of commit 58638deeb3ac16b6556bba6a2b24034d7f886cce

* merge conf resolve

---------

Co-authored-by: Ashesh Vidyut <ashesh.vidyut@hashicorp.com>
Co-authored-by: Ashesh Vidyut <134911583+absolutelightning@users.noreply.github.com>
This commit is contained in:
hc-github-team-consul-core 2023-06-30 08:10:20 -05:00 committed by GitHub
parent 8b41480c63
commit 5e3ad77fc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 107 additions and 30 deletions

3
.changelog/17565.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
reloadable config: Made enable_debug config reloadable and enable pprof command to work when config toggles to true
```

View File

@ -19,6 +19,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
@ -415,6 +416,8 @@ type Agent struct {
// enterpriseAgent embeds fields that we only access in consul-enterprise builds // enterpriseAgent embeds fields that we only access in consul-enterprise builds
enterpriseAgent enterpriseAgent
enableDebug atomic.Bool
} }
// New process the desired options and creates a new Agent. // New process the desired options and creates a new Agent.
@ -597,6 +600,8 @@ func (a *Agent) Start(ctx context.Context) error {
// Overwrite the configuration. // Overwrite the configuration.
a.config = c a.config = c
a.enableDebug.Store(c.EnableDebug)
if err := a.tlsConfigurator.Update(a.config.TLS); err != nil { if err := a.tlsConfigurator.Update(a.config.TLS); err != nil {
return fmt.Errorf("Failed to load TLS configurations after applying auto-config settings: %w", err) return fmt.Errorf("Failed to load TLS configurations after applying auto-config settings: %w", err)
} }
@ -1126,13 +1131,13 @@ func (a *Agent) listenHTTP() ([]apiServer, error) {
httpServer := &http.Server{ httpServer := &http.Server{
Addr: l.Addr().String(), Addr: l.Addr().String(),
TLSConfig: tlscfg, TLSConfig: tlscfg,
Handler: srv.handler(a.config.EnableDebug), Handler: srv.handler(),
MaxHeaderBytes: a.config.HTTPMaxHeaderBytes, MaxHeaderBytes: a.config.HTTPMaxHeaderBytes,
} }
if scada.IsCapability(l.Addr()) { if scada.IsCapability(l.Addr()) {
// wrap in http2 server handler // wrap in http2 server handler
httpServer.Handler = h2c.NewHandler(srv.handler(a.config.EnableDebug), &http2.Server{}) httpServer.Handler = h2c.NewHandler(srv.handler(), &http2.Server{})
} }
// Load the connlimit helper into the server // Load the connlimit helper into the server
@ -4290,6 +4295,9 @@ func (a *Agent) reloadConfigInternal(newCfg *config.RuntimeConfig) error {
a.proxyConfig.SetUpdateRateLimit(newCfg.XDSUpdateRateLimit) a.proxyConfig.SetUpdateRateLimit(newCfg.XDSUpdateRateLimit)
a.enableDebug.Store(newCfg.EnableDebug)
a.config.EnableDebug = newCfg.EnableDebug
return nil return nil
} }

View File

@ -1625,7 +1625,7 @@ func TestHTTPHandlers_AgentMetricsStream_ACLDeny(t *testing.T) {
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/v1/agent/metrics/stream", nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/v1/agent/metrics/stream", nil)
require.NoError(t, err) require.NoError(t, err)
handle := h.handler(false) handle := h.handler()
handle.ServeHTTP(resp, req) handle.ServeHTTP(resp, req)
require.Equal(t, http.StatusForbidden, resp.Code) require.Equal(t, http.StatusForbidden, resp.Code)
require.Contains(t, resp.Body.String(), "Permission denied") require.Contains(t, resp.Body.String(), "Permission denied")
@ -1662,7 +1662,7 @@ func TestHTTPHandlers_AgentMetricsStream(t *testing.T) {
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/v1/agent/metrics/stream", nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "/v1/agent/metrics/stream", nil)
require.NoError(t, err) require.NoError(t, err)
handle := h.handler(false) handle := h.handler()
handle.ServeHTTP(resp, req) handle.ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code) require.Equal(t, http.StatusOK, resp.Code)
@ -6010,8 +6010,10 @@ func TestAgent_Monitor(t *testing.T) {
cancelCtx, cancelFunc := context.WithCancel(context.Background()) cancelCtx, cancelFunc := context.WithCancel(context.Background())
req = req.WithContext(cancelCtx) req = req.WithContext(cancelCtx)
a.enableDebug.Store(true)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
handler := a.srv.handler(true) handler := a.srv.handler()
go handler.ServeHTTP(resp, req) go handler.ServeHTTP(resp, req)
args := &structs.ServiceDefinition{ args := &structs.ServiceDefinition{

View File

@ -4193,6 +4193,39 @@ func TestAgent_ReloadConfig_XDSUpdateRateLimit(t *testing.T) {
require.Equal(t, rate.Limit(1000), a.proxyConfig.UpdateRateLimit()) require.Equal(t, rate.Limit(1000), a.proxyConfig.UpdateRateLimit())
} }
func TestAgent_ReloadConfig_EnableDebug(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
cfg := fmt.Sprintf(`data_dir = %q`, testutil.TempDir(t, "agent"))
a := NewTestAgent(t, cfg)
defer a.Shutdown()
c := TestConfig(
testutil.Logger(t),
config.FileSource{
Name: t.Name(),
Format: "hcl",
Data: cfg + ` enable_debug = true`,
},
)
require.NoError(t, a.reloadConfigInternal(c))
require.Equal(t, true, a.enableDebug.Load())
c = TestConfig(
testutil.Logger(t),
config.FileSource{
Name: t.Name(),
Format: "hcl",
Data: cfg + ` enable_debug = false`,
},
)
require.NoError(t, a.reloadConfigInternal(c))
require.Equal(t, false, a.enableDebug.Load())
}
func TestAgent_consulConfig_AutoEncryptAllowTLS(t *testing.T) { func TestAgent_consulConfig_AutoEncryptAllowTLS(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("too slow for testing.Short") t.Skip("too slow for testing.Short")

View File

@ -324,8 +324,8 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
rt.DevMode = true rt.DevMode = true
rt.DisableAnonymousSignature = true rt.DisableAnonymousSignature = true
rt.DisableKeyringFile = true rt.DisableKeyringFile = true
rt.EnableDebug = true
rt.Experiments = []string{"resource-apis"} rt.Experiments = []string{"resource-apis"}
rt.EnableDebug = true
rt.UIConfig.Enabled = true rt.UIConfig.Enabled = true
rt.LeaveOnTerm = false rt.LeaveOnTerm = false
rt.Logging.LogLevel = "DEBUG" rt.Logging.LogLevel = "DEBUG"

View File

@ -167,7 +167,7 @@ func (s *HTTPHandlers) ReloadConfig(newCfg *config.RuntimeConfig) error {
// //
// The first call must not be concurrent with any other call. Subsequent calls // The first call must not be concurrent with any other call. Subsequent calls
// may be concurrent with HTTP requests since no state is modified. // may be concurrent with HTTP requests since no state is modified.
func (s *HTTPHandlers) handler(enableDebug bool) http.Handler { func (s *HTTPHandlers) handler() http.Handler {
// Memoize multiple calls. // Memoize multiple calls.
if s.h != nil { if s.h != nil {
return s.h return s.h
@ -210,7 +210,15 @@ func (s *HTTPHandlers) handler(enableDebug bool) http.Handler {
// handlePProf takes the given pattern and pprof handler // handlePProf takes the given pattern and pprof handler
// and wraps it to add authorization and metrics // and wraps it to add authorization and metrics
handlePProf := func(pattern string, handler http.HandlerFunc) { handlePProf := func(pattern string, handler http.HandlerFunc) {
wrapper := func(resp http.ResponseWriter, req *http.Request) { wrapper := func(resp http.ResponseWriter, req *http.Request) {
// If enableDebug register wrapped pprof handlers
if !s.agent.enableDebug.Load() && s.checkACLDisabled() {
resp.WriteHeader(http.StatusNotFound)
return
}
var token string var token string
s.parseToken(req, &token) s.parseToken(req, &token)
@ -245,14 +253,11 @@ func (s *HTTPHandlers) handler(enableDebug bool) http.Handler {
handleFuncMetrics(pattern, s.wrap(bound, methods)) handleFuncMetrics(pattern, s.wrap(bound, methods))
} }
// If enableDebug or ACL enabled, register wrapped pprof handlers
if enableDebug || !s.checkACLDisabled() {
handlePProf("/debug/pprof/", pprof.Index) handlePProf("/debug/pprof/", pprof.Index)
handlePProf("/debug/pprof/cmdline", pprof.Cmdline) handlePProf("/debug/pprof/cmdline", pprof.Cmdline)
handlePProf("/debug/pprof/profile", pprof.Profile) handlePProf("/debug/pprof/profile", pprof.Profile)
handlePProf("/debug/pprof/symbol", pprof.Symbol) handlePProf("/debug/pprof/symbol", pprof.Symbol)
handlePProf("/debug/pprof/trace", pprof.Trace) handlePProf("/debug/pprof/trace", pprof.Trace)
}
if s.IsUIEnabled() { if s.IsUIEnabled() {
// Note that we _don't_ support reloading ui_config.{enabled, content_dir, // Note that we _don't_ support reloading ui_config.{enabled, content_dir,

View File

@ -144,7 +144,8 @@ func TestHTTPAPI_OptionMethod_OSS(t *testing.T) {
uri := fmt.Sprintf("http://%s%s", a.HTTPAddr(), path) uri := fmt.Sprintf("http://%s%s", a.HTTPAddr(), path)
req, _ := http.NewRequest("OPTIONS", uri, nil) req, _ := http.NewRequest("OPTIONS", uri, nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
allMethods := append([]string{"OPTIONS"}, methods...) allMethods := append([]string{"OPTIONS"}, methods...)
if resp.Code != http.StatusOK { if resp.Code != http.StatusOK {
@ -190,7 +191,9 @@ func TestHTTPAPI_AllowedNets_OSS(t *testing.T) {
req, _ := http.NewRequest(method, uri, nil) req, _ := http.NewRequest(method, uri, nil)
req.RemoteAddr = "192.168.1.2:5555" req.RemoteAddr = "192.168.1.2:5555"
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
require.Equal(t, http.StatusForbidden, resp.Code, "%s %s", method, path) require.Equal(t, http.StatusForbidden, resp.Code, "%s %s", method, path)
}) })

View File

@ -288,7 +288,9 @@ func TestSetupHTTPServer_HTTP2(t *testing.T) {
err = setupHTTPS(httpServer, noopConnState, time.Second) err = setupHTTPS(httpServer, noopConnState, time.Second)
require.NoError(t, err) require.NoError(t, err)
srvHandler := a.srv.handler(true) a.enableDebug.Store(true)
srvHandler := a.srv.handler()
mux, ok := srvHandler.(*wrappedMux) mux, ok := srvHandler.(*wrappedMux)
require.True(t, ok, "expected a *wrappedMux, got %T", handler) require.True(t, ok, "expected a *wrappedMux, got %T", handler)
mux.mux.HandleFunc("/echo", handler) mux.mux.HandleFunc("/echo", handler)
@ -483,7 +485,9 @@ func TestHTTPAPI_Ban_Nonprintable_Characters(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
if got, want := resp.Code, http.StatusBadRequest; got != want { if got, want := resp.Code, http.StatusBadRequest; got != want {
t.Fatalf("bad response code got %d want %d", got, want) t.Fatalf("bad response code got %d want %d", got, want)
} }
@ -506,7 +510,9 @@ func TestHTTPAPI_Allow_Nonprintable_Characters_With_Flag(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
// Key doesn't actually exist so we should get 404 // Key doesn't actually exist so we should get 404
if got, want := resp.Code, http.StatusNotFound; got != want { if got, want := resp.Code, http.StatusNotFound; got != want {
t.Fatalf("bad response code got %d want %d", got, want) t.Fatalf("bad response code got %d want %d", got, want)
@ -645,7 +651,9 @@ func requireHasHeadersSet(t *testing.T, a *TestAgent, path string) {
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", path, nil) req, _ := http.NewRequest("GET", path, nil)
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
hdrs := resp.Header() hdrs := resp.Header()
require.Equal(t, "*", hdrs.Get("Access-Control-Allow-Origin"), require.Equal(t, "*", hdrs.Get("Access-Control-Allow-Origin"),
@ -706,14 +714,18 @@ func TestAcceptEncodingGzip(t *testing.T) {
// negotiation, but since this call doesn't go through a real // negotiation, but since this call doesn't go through a real
// transport, the header has to be set manually // transport, the header has to be set manually
req.Header["Accept-Encoding"] = []string{"gzip"} req.Header["Accept-Encoding"] = []string{"gzip"}
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
require.Equal(t, 200, resp.Code) require.Equal(t, 200, resp.Code)
require.Equal(t, "", resp.Header().Get("Content-Encoding")) require.Equal(t, "", resp.Header().Get("Content-Encoding"))
resp = httptest.NewRecorder() resp = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/v1/kv/long", nil) req, _ = http.NewRequest("GET", "/v1/kv/long", nil)
req.Header["Accept-Encoding"] = []string{"gzip"} req.Header["Accept-Encoding"] = []string{"gzip"}
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
require.Equal(t, 200, resp.Code) require.Equal(t, 200, resp.Code)
require.Equal(t, "gzip", resp.Header().Get("Content-Encoding")) require.Equal(t, "gzip", resp.Header().Get("Content-Encoding"))
} }
@ -1068,8 +1080,9 @@ func TestHTTPServer_PProfHandlers_EnableDebug(t *testing.T) {
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/debug/pprof/profile?seconds=1", nil) req, _ := http.NewRequest("GET", "/debug/pprof/profile?seconds=1", nil)
a.enableDebug.Store(true)
httpServer := &HTTPHandlers{agent: a.Agent} httpServer := &HTTPHandlers{agent: a.Agent}
httpServer.handler(true).ServeHTTP(resp, req) httpServer.handler().ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code) require.Equal(t, http.StatusOK, resp.Code)
} }
@ -1087,7 +1100,7 @@ func TestHTTPServer_PProfHandlers_DisableDebugNoACLs(t *testing.T) {
req, _ := http.NewRequest("GET", "/debug/pprof/profile", nil) req, _ := http.NewRequest("GET", "/debug/pprof/profile", nil)
httpServer := &HTTPHandlers{agent: a.Agent} httpServer := &HTTPHandlers{agent: a.Agent}
httpServer.handler(false).ServeHTTP(resp, req) httpServer.handler().ServeHTTP(resp, req)
require.Equal(t, http.StatusNotFound, resp.Code) require.Equal(t, http.StatusNotFound, resp.Code)
} }
@ -1168,7 +1181,9 @@ func TestHTTPServer_PProfHandlers_ACLs(t *testing.T) {
t.Run(fmt.Sprintf("case %d (%#v)", i, c), func(t *testing.T) { t.Run(fmt.Sprintf("case %d (%#v)", i, c), func(t *testing.T) {
req, _ := http.NewRequest("GET", fmt.Sprintf("%s?token=%s", c.endpoint, c.token), nil) req, _ := http.NewRequest("GET", fmt.Sprintf("%s?token=%s", c.endpoint, c.token), nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
assert.Equal(t, c.code, resp.Code) assert.Equal(t, c.code, resp.Code)
}) })
} }
@ -1478,7 +1493,9 @@ func TestEnableWebUI(t *testing.T) {
req, _ := http.NewRequest("GET", "/ui/", nil) req, _ := http.NewRequest("GET", "/ui/", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code) require.Equal(t, http.StatusOK, resp.Code)
// Validate that it actually sent the index page we expect since an error // Validate that it actually sent the index page we expect since an error
@ -1507,7 +1524,9 @@ func TestEnableWebUI(t *testing.T) {
{ {
req, _ := http.NewRequest("GET", "/ui/", nil) req, _ := http.NewRequest("GET", "/ui/", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
a.srv.handler(true).ServeHTTP(resp, req) a.enableDebug.Store(true)
a.srv.handler().ServeHTTP(resp, req)
require.Equal(t, http.StatusOK, resp.Code) require.Equal(t, http.StatusOK, resp.Code)
require.Contains(t, resp.Body.String(), `<!-- CONSUL_VERSION:`) require.Contains(t, resp.Body.String(), `<!-- CONSUL_VERSION:`)
require.Contains(t, resp.Body.String(), `valid-but-unlikely-metrics-provider-name`) require.Contains(t, resp.Body.String(), `valid-but-unlikely-metrics-provider-name`)

View File

@ -58,7 +58,9 @@ func TestUIEndpoint_MetricsProxy_ACLDeny(t *testing.T) {
`, backendURL)) `, backendURL))
defer a.Shutdown() defer a.Shutdown()
h := a.srv.handler(true) a.enableDebug.Store(true)
h := a.srv.handler()
testrpc.WaitForLeader(t, a.RPC, "dc1") testrpc.WaitForLeader(t, a.RPC, "dc1")

View File

@ -2620,7 +2620,9 @@ func TestUIEndpoint_MetricsProxy(t *testing.T) {
require.NoError(t, a.Agent.reloadConfigInternal(&cfg)) require.NoError(t, a.Agent.reloadConfigInternal(&cfg))
// Now fetch the API handler to run requests against // Now fetch the API handler to run requests against
h := a.srv.handler(true) a.enableDebug.Store(true)
h := a.srv.handler()
req := httptest.NewRequest("GET", tc.path, nil) req := httptest.NewRequest("GET", tc.path, nil)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()