diff --git a/config/samples/config.yaml b/config/samples/config.yaml index 6656cb90f..4d3c76a80 100644 --- a/config/samples/config.yaml +++ b/config/samples/config.yaml @@ -19,6 +19,8 @@ leader_election: metrics_addr: ":8080" # The address the metrics endpoint binds to. # The default value is ":8080". +server_addr: "127.0.0.1:9092" # Available endpoints: /debug can be used to debug in-memory state of translated adc configs to be synced with data plane. + enable_http2: false # Whether to enable HTTP/2 for the server. # The default value is false. diff --git a/docs/en/latest/reference/configuration-file.md b/docs/en/latest/reference/configuration-file.md index dc3d82c7f..588d945ae 100644 --- a/docs/en/latest/reference/configuration-file.md +++ b/docs/en/latest/reference/configuration-file.md @@ -51,6 +51,8 @@ leader_election: metrics_addr: ":8080" # The address the metrics endpoint binds to. # The default value is ":8080". +server_addr: "127.0.0.1:9092" # Available endpoints: /debug can be used to debug in-memory state of translated adc configs to be synced with data plane. + enable_http2: false # Whether to enable HTTP/2 for the server. # The default value is false. diff --git a/internal/adc/client/client.go b/internal/adc/client/client.go index 9bf38321e..e257c9531 100644 --- a/internal/adc/client/client.go +++ b/internal/adc/client/client.go @@ -46,7 +46,8 @@ type Client struct { executor ADCExecutor BackendMode string - ConfigManager *common.ConfigManager[types.NamespacedNameKind, adctypes.Config] + ConfigManager *common.ConfigManager[types.NamespacedNameKind, adctypes.Config] + ADCDebugProvider *common.ADCDebugProvider } func New(mode string, timeout time.Duration) (*Client, error) { @@ -55,12 +56,15 @@ func New(mode string, timeout time.Duration) (*Client, error) { serverURL = defaultHTTPADCExecutorAddr } + store := cache.NewStore() + configManager := common.NewConfigManager[types.NamespacedNameKind, adctypes.Config]() log.Infow("using HTTP ADC Executor", zap.String("server_url", serverURL)) return &Client{ - Store: cache.NewStore(), - executor: NewHTTPADCExecutor(serverURL, timeout), - BackendMode: mode, - ConfigManager: common.NewConfigManager[types.NamespacedNameKind, adctypes.Config](), + Store: store, + executor: NewHTTPADCExecutor(serverURL, timeout), + BackendMode: mode, + ConfigManager: configManager, + ADCDebugProvider: common.NewADCDebugProvider(store, configManager), }, nil } diff --git a/internal/controller/config/config.go b/internal/controller/config/config.go index ea8dfbac3..2b1140041 100644 --- a/internal/controller/config/config.go +++ b/internal/controller/config/config.go @@ -48,6 +48,7 @@ func NewDefaultConfig() *Config { LeaderElectionID: DefaultLeaderElectionID, ProbeAddr: DefaultProbeAddr, MetricsAddr: DefaultMetricsAddr, + ServerAddr: DefaultServerAddr, LeaderElection: NewLeaderElection(), ExecADCTimeout: types.TimeDuration{Duration: 15 * time.Second}, ProviderConfig: ProviderConfig{ diff --git a/internal/controller/config/types.go b/internal/controller/config/types.go index 8d167a2be..843f1a8df 100644 --- a/internal/controller/config/types.go +++ b/internal/controller/config/types.go @@ -60,6 +60,8 @@ type Config struct { ControllerName string `json:"controller_name" yaml:"controller_name"` LeaderElectionID string `json:"leader_election_id" yaml:"leader_election_id"` MetricsAddr string `json:"metrics_addr" yaml:"metrics_addr"` + ServerAddr string `json:"server_addr" yaml:"server_addr"` + EnableServer bool `json:"enable_server" yaml:"enable_server"` EnableHTTP2 bool `json:"enable_http2" yaml:"enable_http2"` ProbeAddr string `json:"probe_addr" yaml:"probe_addr"` SecureMetrics bool `json:"secure_metrics" yaml:"secure_metrics"` diff --git a/internal/manager/run.go b/internal/manager/run.go index 12a074ea4..de64f62a2 100644 --- a/internal/manager/run.go +++ b/internal/manager/run.go @@ -43,6 +43,7 @@ import ( "github.com/apache/apisix-ingress-controller/internal/controller/config" "github.com/apache/apisix-ingress-controller/internal/controller/status" "github.com/apache/apisix-ingress-controller/internal/manager/readiness" + "github.com/apache/apisix-ingress-controller/internal/manager/server" "github.com/apache/apisix-ingress-controller/internal/provider" _ "github.com/apache/apisix-ingress-controller/internal/provider/init" _ "github.com/apache/apisix-ingress-controller/pkg/metrics" @@ -189,6 +190,14 @@ func Run(ctx context.Context, logger logr.Logger) error { return err } + if cfg.EnableServer { + srv := server.NewServer(config.ControllerConfig.ServerAddr) + srv.Register("/debug", provider) + if err := mgr.Add(srv); err != nil { + setupLog.Error(err, "unable to add debug server to manager") + return err + } + } if err := mgr.Add(provider); err != nil { setupLog.Error(err, "unable to add provider to manager") return err diff --git a/internal/manager/server/server.go b/internal/manager/server/server.go new file mode 100644 index 000000000..543c328bd --- /dev/null +++ b/internal/manager/server/server.go @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License 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 server + +import ( + "context" + "net/http" + "time" + + "github.com/apache/apisix-ingress-controller/internal/provider" +) + +type Server struct { + server *http.Server + mux *http.ServeMux +} + +func (s *Server) Start(ctx context.Context) error { + stop := make(chan error, 1) + go func() { + if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + stop <- err + } + close(stop) + }() + select { + case <-ctx.Done(): + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return s.server.Shutdown(shutdownCtx) + case err := <-stop: + return err + } +} + +func (s *Server) Register(pathPrefix string, registrant provider.RegisterHandler) { + subMux := http.NewServeMux() + registrant.Register(pathPrefix, subMux) + s.mux.Handle(pathPrefix+"/", http.StripPrefix(pathPrefix, subMux)) + s.mux.HandleFunc(pathPrefix, func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, pathPrefix+"/", http.StatusPermanentRedirect) + }) +} + +func NewServer(addr string) *Server { + mux := http.NewServeMux() + return &Server{ + server: &http.Server{ + Addr: addr, + Handler: mux, + }, + mux: mux, + } +} diff --git a/internal/provider/api7ee/provider.go b/internal/provider/api7ee/provider.go index 3c3784a00..4ce4af064 100644 --- a/internal/provider/api7ee/provider.go +++ b/internal/provider/api7ee/provider.go @@ -19,6 +19,7 @@ package api7ee import ( "context" + "net/http" "sync/atomic" "time" @@ -84,6 +85,10 @@ func New(updater status.Updater, readier readiness.ReadinessManager, opts ...pro }, nil } +func (d *api7eeProvider) Register(pathPrefix string, mux *http.ServeMux) { + d.client.ADCDebugProvider.SetupHandler(pathPrefix, mux) +} + func (d *api7eeProvider) Update(ctx context.Context, tctx *provider.TranslateContext, obj client.Object) error { log.Debugw("updating object", zap.Any("object", obj)) var ( diff --git a/internal/provider/apisix/provider.go b/internal/provider/apisix/provider.go index 395ba62c7..846923305 100644 --- a/internal/provider/apisix/provider.go +++ b/internal/provider/apisix/provider.go @@ -19,6 +19,7 @@ package apisix import ( "context" + "net/http" "sync" "time" @@ -91,6 +92,10 @@ func New(updater status.Updater, readier readiness.ReadinessManager, opts ...pro }, nil } +func (d *apisixProvider) Register(pathPrefix string, mux *http.ServeMux) { + d.client.ADCDebugProvider.SetupHandler(pathPrefix, mux) +} + func (d *apisixProvider) Update(ctx context.Context, tctx *provider.TranslateContext, obj client.Object) error { log.Debugw("updating object", zap.Any("object", obj)) var ( diff --git a/internal/provider/common/adcdebugserver.go b/internal/provider/common/adcdebugserver.go new file mode 100644 index 000000000..b1ca9c5b2 --- /dev/null +++ b/internal/provider/common/adcdebugserver.go @@ -0,0 +1,341 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License 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 common + +import ( + "encoding/json" + "fmt" + "html/template" + "net/http" + "net/url" + + adctypes "github.com/apache/apisix-ingress-controller/api/adc" + "github.com/apache/apisix-ingress-controller/internal/adc/cache" + "github.com/apache/apisix-ingress-controller/internal/types" +) + +type ResourceInfo struct { + ID string + Name string + Type string + Link string +} + +type ADCDebugProvider struct { + store *cache.Store + configManager *ConfigManager[types.NamespacedNameKind, adctypes.Config] + pathPrefix string +} + +func newTemplate(name, body string) *template.Template { + return template.Must(template.New(name). + Funcs(template.FuncMap{"urlencode": url.QueryEscape}). + Parse(body)) +} + +func (asrv *ADCDebugProvider) SetupHandler(pathPrefix string, mux *http.ServeMux) { + asrv.pathPrefix = pathPrefix + mux.HandleFunc("/config", asrv.handleConfig) + mux.HandleFunc("/", asrv.handleIndex) +} + +func NewADCDebugProvider(store *cache.Store, configManager *ConfigManager[types.NamespacedNameKind, adctypes.Config]) *ADCDebugProvider { + return &ADCDebugProvider{store: store, configManager: configManager} +} + +func (asrv *ADCDebugProvider) handleIndex(w http.ResponseWriter, r *http.Request) { + configs := asrv.configManager.List() + configNames := make([]string, 0, len(configs)) + for _, cfg := range configs { + configNames = append(configNames, cfg.Name) + } + + tmpl := newTemplate("index", ` + +
{{.Resource}}
+ Back
+
+
+ `)
+
+ _ = tmpl.Execute(w, struct {
+ ConfigName string
+ Resource string
+ ResourceID string
+ ResourceType string
+ Prefix string
+ }{
+ ConfigName: configName,
+ Resource: string(jsonData),
+ ResourceID: resourceID,
+ ResourceType: resourceType,
+ Prefix: asrv.pathPrefix,
+ })
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index ef93de541..3f0bcded4 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -33,6 +33,7 @@ import (
)
type Provider interface {
+ RegisterHandler
Update(context.Context, *TranslateContext, client.Object) error
Delete(context.Context, client.Object) error
Start(context.Context) error
diff --git a/internal/provider/register.go b/internal/provider/register.go
index a2542ad70..25cc670dc 100644
--- a/internal/provider/register.go
+++ b/internal/provider/register.go
@@ -19,11 +19,16 @@ package provider
import (
"fmt"
+ "net/http"
"github.com/apache/apisix-ingress-controller/internal/controller/status"
"github.com/apache/apisix-ingress-controller/internal/manager/readiness"
)
+type RegisterHandler interface {
+ Register(pathPrefix string, mux *http.ServeMux)
+}
+
type RegisterFunc func(status.Updater, readiness.ReadinessManager, ...Option) (Provider, error)
var providers = map[string]RegisterFunc{}