From 9c68f7fdf442805ff7a2bb41317b91eaf744cee0 Mon Sep 17 00:00:00 2001 From: SamYuan1990 Date: Wed, 18 Jun 2025 10:05:21 +0800 Subject: [PATCH] chore: using exporter-toolkit/web for web config Signed-off-by: SamYuan1990 --- config/config.go | 41 ++++++++++++-------- config/config_test.go | 24 ++++++++---- internal/server/server.go | 61 ++++++++++++++---------------- internal/server/server_test.go | 18 +++------ internal/server/server_tls_test.go | 12 ++---- 5 files changed, 79 insertions(+), 77 deletions(-) diff --git a/config/config.go b/config/config.go index 2bfceb7785..b1183bc824 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,8 @@ import ( "time" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus/exporter-toolkit/web" + "github.com/prometheus/exporter-toolkit/web/kingpinflag" "gopkg.in/yaml.v3" "k8s.io/utils/ptr" ) @@ -39,7 +41,7 @@ type ( } `yaml:"fake-cpu-meter"` } Web struct { - Config string `yaml:"configFile"` + Config *web.FlagConfig } Monitor struct { @@ -83,9 +85,9 @@ type ( Monitor Monitor `yaml:"monitor"` Rapl Rapl `yaml:"rapl"` Exporter Exporter `yaml:"exporter"` - Web Web `yaml:"web"` - Debug Debug `yaml:"debug"` - Dev Dev `yaml:"dev"` // WARN: do not expose dev settings as flags + Web Web + Debug Debug `yaml:"debug"` + Dev Dev `yaml:"dev"` // WARN: do not expose dev settings as flags Kube Kube `yaml:"kube"` } @@ -99,6 +101,7 @@ const ( ) const ( + DefaultPort = ":28282" // Flags LogLevelFlag = "log.level" LogFormatFlag = "log.format" @@ -114,7 +117,7 @@ const ( pprofEnabledFlag = "debug.pprof" - WebConfigFlag = "web.config-file" + WebConfigFlag = "web.config.file" // Exporters ExporterStdoutEnabledFlag = "exporter.stdout" @@ -133,6 +136,7 @@ const ( // DefaultConfig returns a Config with default values func DefaultConfig() *Config { + TLSconfig := "" cfg := &Config{ Log: Log{ Level: "info", @@ -166,6 +170,12 @@ func DefaultConfig() *Config { Kube: Kube{ Enabled: ptr.To(false), }, + Web: Web{ + Config: &web.FlagConfig{ + WebListenAddresses: &[]string{DefaultPort}, + WebConfigFile: &TLSconfig, + }, + }, } cfg.Dev.FakeCpuMeter.Enabled = ptr.To(false) @@ -245,7 +255,8 @@ func RegisterFlags(app *kingpin.Application) ConfigUpdaterFn { "Interval for monitoring resources (processes, container, vm, etc...); 0 to disable").Default("5s").Duration() enablePprof := app.Flag(pprofEnabledFlag, "Enable pprof debug endpoints").Default("false").Bool() - webConfig := app.Flag(WebConfigFlag, "Web config file path").Default("").String() + //webConfig using github.com/prometheus/exporter-toolkit/web/ + webConfig := kingpinflag.AddFlags(app, DefaultPort) // exporters stdoutExporterEnabled := app.Flag(ExporterStdoutEnabledFlag, "Enable stdout exporter").Default("false").Bool() @@ -284,7 +295,7 @@ func RegisterFlags(app *kingpin.Application) ConfigUpdaterFn { } if flagsSet[WebConfigFlag] { - cfg.Web.Config = *webConfig + cfg.Web.Config = webConfig } if flagsSet[ExporterStdoutEnabledFlag] { @@ -317,7 +328,6 @@ func (c *Config) sanitize() { c.Log.Format = strings.TrimSpace(c.Log.Format) c.Host.SysFS = strings.TrimSpace(c.Host.SysFS) c.Host.ProcFS = strings.TrimSpace(c.Host.ProcFS) - c.Web.Config = strings.TrimSpace(c.Web.Config) for i := range c.Rapl.Zones { c.Rapl.Zones[i] = strings.TrimSpace(c.Rapl.Zones[i]) @@ -359,7 +369,13 @@ func (c *Config) Validate(skips ...SkipValidation) error { errs = append(errs, fmt.Sprintf("invalid log format: %s", c.Log.Format)) } } - + { // Web config file + if c.Web.Config != nil && *c.Web.Config.WebConfigFile != "" { + if err := canReadFile(*c.Web.Config.WebConfigFile); err != nil { + errs = append(errs, fmt.Sprintf("invalid web config file. path: %q: %s", *c.Web.Config.WebConfigFile, err.Error())) + } + } + } { // Validate host settings if _, skip := validationSkipped[SkipHostValidation]; !skip { if err := canReadDir(c.Host.SysFS); err != nil { @@ -370,13 +386,6 @@ func (c *Config) Validate(skips ...SkipValidation) error { } } } - { // Web config file - if c.Web.Config != "" { - if err := canReadFile(c.Web.Config); err != nil { - errs = append(errs, fmt.Sprintf("invalid web config file. path: %q: %s", c.Web.Config, err.Error())) - } - } - } { // Monitor if c.Monitor.Interval < 0 { errs = append(errs, fmt.Sprintf("invalid monitor interval: %s can't be negative", c.Monitor.Interval)) diff --git a/config/config_test.go b/config/config_test.go index 30b6abd0e9..f248c867b6 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus/exporter-toolkit/web" "github.com/stretchr/testify/assert" "k8s.io/utils/ptr" ) @@ -22,7 +23,10 @@ func TestDefaultConfig(t *testing.T) { // Assert default values are set correctly assert.Equal(t, "info", cfg.Log.Level) assert.Equal(t, "text", cfg.Log.Format) - assert.Equal(t, "", cfg.Web.Config) + addrs := *cfg.Web.Config.WebListenAddresses + addr := addrs[0] + assert.Equal(t, DefaultPort, addr) + assert.Equal(t, "", *cfg.Web.Config.WebConfigFile) } func TestLoadFromYAML(t *testing.T) { @@ -216,6 +220,7 @@ func TestReadError(t *testing.T) { func TestInvalidConfigurationValues(t *testing.T) { // Test validation of configuration values (command line and YAML) // Create a kingpin app and register flags + unreadableWebConfig := "/from/unreadable/path/web.yaml" tt := []struct { name string config *Config @@ -288,7 +293,9 @@ func TestInvalidConfigurationValues(t *testing.T) { name: "unreadable web config", config: &Config{ Web: Web{ - Config: "/from/unreadable/path/web.yaml", + Config: &web.FlagConfig{ + WebConfigFile: &unreadableWebConfig, + }, }, }, error: "invalid web config file", @@ -367,6 +374,7 @@ func TestConfigValidation(t *testing.T) { } func TestConfigString(t *testing.T) { + fackWebConfig := "/fake/web.config.yml" tt := []struct { name string config *Config @@ -404,7 +412,9 @@ func TestConfigString(t *testing.T) { name: "custom web.config", config: &Config{ Web: Web{ - Config: "/fake/web.config.yml", + Config: &web.FlagConfig{ + WebConfigFile: &fackWebConfig, + }, }, }, }} @@ -479,12 +489,12 @@ func TestWebConfig(t *testing.T) { cfg := DefaultConfig() err := updateConfig(cfg) assert.NoError(t, err, "unexpected config update error") - assert.Equal(t, cfg.Web.Config, "", "unexpected web.config-file configured") + assert.Equal(t, *cfg.Web.Config.WebConfigFile, "", "unexpected web.config-file configured") }) t.Run("invalid web config", func(t *testing.T) { app := kingpin.New("test", "Test application") updateConfig := RegisterFlags(app) - _, parseErr := app.Parse([]string{"--web.config-file=/fake/web.yml"}) + _, parseErr := app.Parse([]string{"--web.config.file=/fake/web.yml"}) assert.NoError(t, parseErr, "unexpected flag parsing error") cfg := DefaultConfig() err := updateConfig(cfg) @@ -503,13 +513,13 @@ tls_server_config: app := kingpin.New("test", "Test application") updateConfig := RegisterFlags(app) - flagStr := fmt.Sprintf("--web.config-file=%s", tempWebConfig.Name()) + flagStr := fmt.Sprintf("--web.config.file=%s", tempWebConfig.Name()) _, parseErr := app.Parse([]string{flagStr}) assert.NoError(t, parseErr, "unexpected flag parsing error") cfg := DefaultConfig() err = updateConfig(cfg) assert.NoError(t, err, "expected config update error") - assert.Equal(t, cfg.Web.Config, tempWebConfig.Name(), "unexpected config update") + assert.Equal(t, *cfg.Web.Config.WebConfigFile, tempWebConfig.Name(), "unexpected config update") _ = os.Remove(tempWebConfig.Name()) }) } diff --git a/internal/server/server.go b/internal/server/server.go index 04e5603ef6..c3dbef3857 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -11,6 +11,7 @@ import ( "time" "github.com/prometheus/exporter-toolkit/web" + "github.com/sustainable-computing-io/kepler/config" "github.com/sustainable-computing-io/kepler/internal/service" ) @@ -23,22 +24,19 @@ type APIService interface { // APIServer implements APIServer type APIServer struct { // input - logger *slog.Logger - listenAddrs []string - + logger *slog.Logger // http server *http.Server mux *http.ServeMux endpointDescription string - webCfgPath string + webConfig *web.FlagConfig } var _ APIService = (*APIServer)(nil) type Opts struct { - logger *slog.Logger - listenAddrs []string - webCfgPath string + logger *slog.Logger + webConfig *web.FlagConfig } // OptionFn is a function sets one more more options in Opts struct @@ -51,25 +49,31 @@ func WithLogger(logger *slog.Logger) OptionFn { } } -// WithListenAddress sets the listening addresses for the APIServer -func WithListenAddress(addr []string) OptionFn { +// WithListen sets the listening addresses and webconfig path for the APIServer +func WithListen(addr []string, path string) OptionFn { return func(o *Opts) { - o.listenAddrs = addr + o.webConfig = &web.FlagConfig{ + WebListenAddresses: &addr, + WebConfigFile: &path, + } } } -func WithWebConfig(path string) OptionFn { +func WithWebConfig(Config *web.FlagConfig) OptionFn { return func(o *Opts) { - o.webCfgPath = path + o.webConfig = Config } } // DefaultOpts returns the default options func DefaultOpts() Opts { + TLSconfig := "" return Opts{ - logger: slog.Default(), - listenAddrs: []string{":28282"}, // Default HTTP Port - webCfgPath: "", // Not present by default + logger: slog.Default(), + webConfig: &web.FlagConfig{ + WebListenAddresses: &[]string{config.DefaultPort}, + WebConfigFile: &TLSconfig, + }, } } @@ -85,11 +89,10 @@ func NewAPIServer(applyOpts ...OptionFn) *APIServer { Handler: mux, } apiServer := &APIServer{ - logger: opts.logger.With("service", "api-server"), - listenAddrs: opts.listenAddrs, - mux: mux, - server: server, - webCfgPath: opts.webCfgPath, + logger: opts.logger.With("service", "api-server"), + mux: mux, + server: server, + webConfig: opts.webConfig, } return apiServer @@ -100,11 +103,7 @@ func (s *APIServer) Name() string { } func (s *APIServer) Init() error { - s.logger.Info("Initializing HTTP server", "listening-on", s.listenAddrs) - if len(s.listenAddrs) == 0 { - return fmt.Errorf("no listening address provided") - } - + s.logger.Info("Initializing kepler server") // create landing page that shows all available endpoints s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // Only respond to the root path @@ -134,23 +133,19 @@ func (s *APIServer) Init() error { } func (s *APIServer) Run(ctx context.Context) error { - s.logger.Info("Running HTTP server", "listening-on", s.listenAddrs) + s.logger.Info("Running kepler server") errCh := make(chan error) go func() { - webCfg := &web.FlagConfig{ - WebListenAddresses: &s.listenAddrs, - WebConfigFile: &s.webCfgPath, - } - errCh <- web.ListenAndServe(s.server, webCfg, s.logger) + errCh <- web.ListenAndServe(s.server, s.webConfig, s.logger) }() select { case <-ctx.Done(): - s.logger.Info("shutting down HTTP server on context done") + s.logger.Info("shutting down kepler server on context done") return nil case err := <-errCh: - s.logger.Error("HTTP server returned an error", "error", err) + s.logger.Error("kepler server returned an error", "error", err) return err } } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index a7aa70f07d..52732b4d64 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -60,14 +60,14 @@ func TestNewAPIServer(t *testing.T) { }, { name: "with custom listen address", opts: []OptionFn{ - WithListenAddress([]string{":8080", ":8081"}), + WithListen([]string{":8080", ":8081"}, ""), }, serviceName: "api-server", }, { name: "with multiple options", opts: []OptionFn{ WithLogger(slog.Default().With("test", "custom")), - WithListenAddress([]string{":9090"}), + WithListen([]string{":9090"}, ""), }, serviceName: "api-server", }} @@ -85,11 +85,10 @@ func TestNewAPIServer(t *testing.T) { // check listen address { server := NewAPIServer( - WithListenAddress([]string{":8080", ":8081"}), + WithListen([]string{":8080", ":8081"}, ""), ) assert.NotNil(t, server) - assert.Equal(t, []string{":8080", ":8081"}, server.listenAddrs) } } @@ -167,13 +166,6 @@ func TestAPIServer_Register(t *testing.T) { }) } -func TestAPIServer_InitWithNoListenAddr(t *testing.T) { - server := NewAPIServer(WithListenAddress([]string{})) - err := server.Init() - assert.Error(t, err, "Init should fail with no listen address") - assert.Contains(t, err.Error(), "no listening address provided") -} - func TestAPIServer_InitWithContextCancellation(t *testing.T) { server := NewAPIServer() @@ -247,7 +239,7 @@ func TestAPIServer_PortConflict(t *testing.T) { }) // Create our API server with the same port - apiServer := NewAPIServer(WithListenAddress([]string{addr})) + apiServer := NewAPIServer(WithListen([]string{addr}, "")) // Initing the API server on the same port should fail due to port conflict ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -281,7 +273,7 @@ func TestAPIServer_RootEndpoint(t *testing.T) { addr := fmt.Sprintf("127.0.0.1:%d", port) - server := NewAPIServer(WithListenAddress([]string{addr})) + server := NewAPIServer(WithListen([]string{addr}, "")) assert.NoError(t, server.Init()) testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/server/server_tls_test.go b/internal/server/server_tls_test.go index d9490da5ab..909045a068 100644 --- a/internal/server/server_tls_test.go +++ b/internal/server/server_tls_test.go @@ -227,8 +227,7 @@ tls_server_config: addr := fmt.Sprintf("127.0.0.1:%d", port) server := NewAPIServer( - WithListenAddress([]string{addr}), - WithWebConfig(webConfigFile)) + WithListen([]string{addr}, webConfigFile)) assert.NoError(t, server.Init()) testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -281,8 +280,7 @@ tls_server_config: addr := fmt.Sprintf("127.0.0.1:%d", port) server := NewAPIServer( - WithListenAddress([]string{addr}), - WithWebConfig(webConfigFile)) + WithListen([]string{addr}, webConfigFile)) assert.NoError(t, server.Init()) testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -332,8 +330,7 @@ tls_server_config: addr := fmt.Sprintf("127.0.0.1:%d", port) server := NewAPIServer( - WithListenAddress([]string{addr}), - WithWebConfig(webConfigFile)) + WithListen([]string{addr}, webConfigFile)) assert.NoError(t, server.Init()) testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -383,8 +380,7 @@ tls_server_config: addr := fmt.Sprintf("127.0.0.1:%d", port) server := NewAPIServer( - WithListenAddress([]string{addr}), - WithWebConfig(webConfigFile)) + WithListen([]string{addr}, webConfigFile)) assert.NoError(t, server.Init()) errCh := make(chan error, 1)