From b46e5e2a9721572b5f2caf0dd31e0a409e8494b6 Mon Sep 17 00:00:00 2001
From: Jenny Shu <28537278+jenshu@users.noreply.github.com>
Date: Tue, 21 Jan 2025 12:47:25 -0500
Subject: [PATCH] Admin server - part 2 (#10482)
---
projects/gateway2/admin/krt_snapshot.go | 14 +++++
projects/gateway2/admin/logging.go | 61 ++++++++++++++++++++
projects/gateway2/admin/pprof.go | 20 +++++++
projects/gateway2/admin/server.go | 77 ++++++++-----------------
projects/gateway2/admin/xds_snapshot.go | 45 +++++++++++++++
test/ginkgo/parallel/ports.go | 6 +-
6 files changed, 168 insertions(+), 55 deletions(-)
create mode 100644 projects/gateway2/admin/krt_snapshot.go
create mode 100644 projects/gateway2/admin/logging.go
create mode 100644 projects/gateway2/admin/pprof.go
create mode 100644 projects/gateway2/admin/xds_snapshot.go
diff --git a/projects/gateway2/admin/krt_snapshot.go b/projects/gateway2/admin/krt_snapshot.go
new file mode 100644
index 00000000000..d459dfd8669
--- /dev/null
+++ b/projects/gateway2/admin/krt_snapshot.go
@@ -0,0 +1,14 @@
+package admin
+
+import (
+ "net/http"
+
+ "istio.io/istio/pkg/kube/krt"
+)
+
+func addKrtSnapshotHandler(path string, mux *http.ServeMux, profiles map[string]dynamicProfileDescription, dbg *krt.DebugHandler) {
+ mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
+ writeJSON(w, dbg, r)
+ })
+ profiles[path] = func() string { return "KRT Snapshot" }
+}
diff --git a/projects/gateway2/admin/logging.go b/projects/gateway2/admin/logging.go
new file mode 100644
index 00000000000..c4de9287a50
--- /dev/null
+++ b/projects/gateway2/admin/logging.go
@@ -0,0 +1,61 @@
+package admin
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/solo-io/go-utils/contextutils"
+)
+
+// The logging handler is an AtomicLevel that supports dynamically changing the log level at runtime.
+func addLoggingHandler(path string, mux *http.ServeMux, profiles map[string]dynamicProfileDescription) {
+ mux.Handle(path, contextutils.GetLogHandler())
+ profiles[path] = getLoggingDescription
+}
+
+// Gets a string representation of the current log level.
+func getLogLevel() string {
+ return contextutils.GetLogLevel().String()
+}
+
+// Gets the html/js to display in the UI for the logging endpoint.
+func getLoggingDescription() string {
+ currentLogLevel := getLogLevel()
+
+ // build the options selector, with the current log level selected by default
+ selectorText := ``
+
+ return `View or change the log level of the program. Note: does not persist across pod restarts.
+
+Log level:
+` + selectorText + `
+
+
+
+
+ `
+}
diff --git a/projects/gateway2/admin/pprof.go b/projects/gateway2/admin/pprof.go
new file mode 100644
index 00000000000..612ec78e670
--- /dev/null
+++ b/projects/gateway2/admin/pprof.go
@@ -0,0 +1,20 @@
+package admin
+
+import (
+ "net/http"
+ "net/http/pprof"
+)
+
+func addPprofHandler(path string, mux *http.ServeMux, profiles map[string]dynamicProfileDescription) {
+ mux.HandleFunc(path, pprof.Index)
+ mux.HandleFunc(path+"cmdline", pprof.Cmdline)
+ mux.HandleFunc(path+"profile", pprof.Profile)
+ mux.HandleFunc(path+"symbol", pprof.Symbol)
+ mux.HandleFunc(path+"trace", pprof.Trace)
+
+ profiles[path] = func() string {
+ return `PProf related things:
+ full goroutine stack dump
+ `
+ }
+}
diff --git a/projects/gateway2/admin/server.go b/projects/gateway2/admin/server.go
index 63591385cd2..f7e66a3a5f1 100644
--- a/projects/gateway2/admin/server.go
+++ b/projects/gateway2/admin/server.go
@@ -6,38 +6,41 @@ import (
"encoding/json"
"fmt"
"net/http"
+ "os"
"sort"
- "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
envoycache "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
- "github.com/rotisserie/eris"
"github.com/solo-io/gloo/projects/gateway2/controller"
"github.com/solo-io/go-utils/contextutils"
- "github.com/solo-io/go-utils/stats"
"istio.io/istio/pkg/kube/krt"
)
const (
- AdminPort = 9095
+ AdminPort = 9091
)
func RunAdminServer(ctx context.Context, setupOpts *controller.SetupOpts) error {
// serverHandlers defines the custom handlers that the Admin Server will support
serverHandlers := getServerHandlers(ctx, setupOpts.KrtDebugger, setupOpts.Cache)
- stats.StartCancellableStatsServerWithPort(ctx, stats.DefaultStartupOptions(), func(mux *http.ServeMux, profiles map[string]string) {
- // let people know these moved
- profiles[fmt.Sprintf("http://localhost:%d/snapshots/", AdminPort)] = fmt.Sprintf("To see snapshots, port forward to port %d", AdminPort)
- })
+ // initialize the atomic log level
+ if envLogLevel := os.Getenv(contextutils.LogLevelEnvName); envLogLevel != "" {
+ contextutils.SetLogLevelFromString(envLogLevel)
+ }
+
startHandlers(ctx, serverHandlers)
return nil
}
+// use a function for the profile descriptions so that every time the admin page is displayed, it can show
+// up-to-date info in the description (e.g. the current log level)
+type dynamicProfileDescription func() string
+
// getServerHandlers returns the custom handlers for the Admin Server, which will be bound to the http.ServeMux
-// These endpoints serve as the basis for an Admin Interface for the Control Plane (https://github.com/solo-io/gloo/issues/6494)
-func getServerHandlers(ctx context.Context, dbg *krt.DebugHandler, cache envoycache.SnapshotCache) func(mux *http.ServeMux, profiles map[string]string) {
- return func(m *http.ServeMux, profiles map[string]string) {
+// These endpoints serve as the basis for an Admin Interface for the Control Plane (https://github.com/kgateway-dev/kgateway/issues/6494)
+func getServerHandlers(ctx context.Context, dbg *krt.DebugHandler, cache envoycache.SnapshotCache) func(mux *http.ServeMux, profiles map[string]dynamicProfileDescription) {
+ return func(m *http.ServeMux, profiles map[string]dynamicProfileDescription) {
/*
// The Input Snapshot is intended to return a list of resources that are persisted in the Kubernetes DB, etcD
@@ -57,18 +60,13 @@ func getServerHandlers(ctx context.Context, dbg *krt.DebugHandler, cache envoyca
profiles["/snapshots/proxies"] = "Proxy Snapshot"
*/
- // The xDS Snapshot is intended to return the full in-memory xDS cache that the Control Plane manages
- // and serves up to running proxies.
- m.HandleFunc("/snapshots/xds", func(w http.ResponseWriter, r *http.Request) {
- response := getXdsSnapshotDataFromCache(cache)
- writeJSON(w, response, r)
- })
- profiles["/snapshots/xds"] = "XDS Snapshot"
+ addXdsSnapshotHandler("/snapshots/xds", m, profiles, cache)
- m.HandleFunc("/snapshots/krt", func(w http.ResponseWriter, r *http.Request) {
- writeJSON(w, dbg, r)
- })
- profiles["/snapshots/krt"] = "KRT Snapshot"
+ addKrtSnapshotHandler("/snapshots/krt", m, profiles, dbg)
+
+ addLoggingHandler("/logging", m, profiles)
+
+ addPprofHandler("/debug/pprof/", m, profiles)
}
}
@@ -116,9 +114,9 @@ func writeJSON(w http.ResponseWriter, obj any, req *http.Request) {
}
}
-func startHandlers(ctx context.Context, addHandlers ...func(mux *http.ServeMux, profiles map[string]string)) error {
+func startHandlers(ctx context.Context, addHandlers ...func(mux *http.ServeMux, profiles map[string]dynamicProfileDescription)) error {
mux := new(http.ServeMux)
- profileDescriptions := map[string]string{}
+ profileDescriptions := map[string]dynamicProfileDescription{}
for _, addHandler := range addHandlers {
addHandler(mux, profileDescriptions)
}
@@ -150,7 +148,7 @@ func startHandlers(ctx context.Context, addHandlers ...func(mux *http.ServeMux,
return nil
}
-func index(profileDescriptions map[string]string) func(w http.ResponseWriter, r *http.Request) {
+func index(profileDescriptions map[string]dynamicProfileDescription) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
type profile struct {
@@ -159,11 +157,11 @@ func index(profileDescriptions map[string]string) func(w http.ResponseWriter, r
Desc string
}
var profiles []profile
- for href, desc := range profileDescriptions {
+ for href, descFunc := range profileDescriptions {
profiles = append(profiles, profile{
Name: href,
Href: href,
- Desc: desc,
+ Desc: descFunc(),
})
}
@@ -181,28 +179,3 @@ func index(profileDescriptions map[string]string) func(w http.ResponseWriter, r
w.Write(buf.Bytes())
}
}
-
-func getXdsSnapshotDataFromCache(xdsCache cache.SnapshotCache) SnapshotResponseData {
- cacheKeys := xdsCache.GetStatusKeys()
- cacheEntries := make(map[string]interface{}, len(cacheKeys))
-
- for _, k := range cacheKeys {
- xdsSnapshot, err := getXdsSnapshot(xdsCache, k)
- if err != nil {
- cacheEntries[k] = err.Error()
- } else {
- cacheEntries[k] = xdsSnapshot
- }
- }
-
- return completeSnapshotResponse(cacheEntries)
-}
-
-func getXdsSnapshot(xdsCache cache.SnapshotCache, k string) (cache cache.ResourceSnapshot, err error) {
- defer func() {
- if r := recover(); r != nil {
- err = eris.New(fmt.Sprintf("panic occurred while getting xds snapshot: %v", r))
- }
- }()
- return xdsCache.GetSnapshot(k)
-}
diff --git a/projects/gateway2/admin/xds_snapshot.go b/projects/gateway2/admin/xds_snapshot.go
new file mode 100644
index 00000000000..a05c4a7948b
--- /dev/null
+++ b/projects/gateway2/admin/xds_snapshot.go
@@ -0,0 +1,45 @@
+package admin
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
+ envoycache "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
+ "github.com/rotisserie/eris"
+)
+
+// The xDS Snapshot is intended to return the full in-memory xDS cache that the Control Plane manages
+// and serves up to running proxies.
+func addXdsSnapshotHandler(path string, mux *http.ServeMux, profiles map[string]dynamicProfileDescription, cache envoycache.SnapshotCache) {
+ mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
+ response := getXdsSnapshotDataFromCache(cache)
+ writeJSON(w, response, r)
+ })
+ profiles[path] = func() string { return "XDS Snapshot" }
+}
+
+func getXdsSnapshotDataFromCache(xdsCache cache.SnapshotCache) SnapshotResponseData {
+ cacheKeys := xdsCache.GetStatusKeys()
+ cacheEntries := make(map[string]interface{}, len(cacheKeys))
+
+ for _, k := range cacheKeys {
+ xdsSnapshot, err := getXdsSnapshot(xdsCache, k)
+ if err != nil {
+ cacheEntries[k] = err.Error()
+ } else {
+ cacheEntries[k] = xdsSnapshot
+ }
+ }
+
+ return completeSnapshotResponse(cacheEntries)
+}
+
+func getXdsSnapshot(xdsCache cache.SnapshotCache, k string) (cache cache.ResourceSnapshot, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = eris.New(fmt.Sprintf("panic occurred while getting xds snapshot: %v", r))
+ }
+ }()
+ return xdsCache.GetSnapshot(k)
+}
diff --git a/test/ginkgo/parallel/ports.go b/test/ginkgo/parallel/ports.go
index 9f13611c203..a3c64012852 100644
--- a/test/ginkgo/parallel/ports.go
+++ b/test/ginkgo/parallel/ports.go
@@ -100,9 +100,9 @@ func portInUseListen(proposedPort uint32) error {
}
var denyListPorts = map[uint32]struct{}{
- // See gloo/pkg/servers/admin/server.go
- // See https://github.com/solo-io/solo-projects/issues/7307 for more details
- 9095: {},
+ // See projects/gateway2/admin/server.go
+ // This port is reserved for the admin server
+ 9091: {},
}
func portInDenyList(proposedPort uint32) error {