diff --git a/plugins/inputs/prometheus/target_allocator.go b/plugins/inputs/prometheus/target_allocator.go index 5c6ca123313..608e6640966 100644 --- a/plugins/inputs/prometheus/target_allocator.go +++ b/plugins/inputs/prometheus/target_allocator.go @@ -57,26 +57,30 @@ func isPodNameAvailable() bool { } return true } -func loadConfigFromFilename(filename string) (*otelpromreceiver.Config, error) { + +// loadConfigFromFilename reads and parses the prometheus config file. +// Returns the config, whether a target_allocator section was present, and any error. +func loadConfigFromFilename(filename string) (*otelpromreceiver.Config, bool, error) { yamlFile, err := os.ReadFile(filename) if err != nil { - return nil, err + return nil, true, err // can't determine; default to warn } var stringMap map[string]interface{} err = yaml.Unmarshal(yamlFile, &stringMap) if err != nil { - return nil, err + return nil, true, err // can't determine; default to warn } + _, hasTA := stringMap["target_allocator"] componentParser := confmap.NewFromStringMap(stringMap) if componentParser == nil { - return nil, fmt.Errorf("unable to parse config from filename %s", filename) + return nil, hasTA, fmt.Errorf("unable to parse config from filename %s", filename) } var cfg otelpromreceiver.Config err = componentParser.Unmarshal(&cfg) if err != nil { - return nil, err + return nil, hasTA, err } - return &cfg, nil + return &cfg, hasTA, nil } func createTargetAllocatorManager(filename string, logger *slog.Logger, logLevel *promslog.AllowedLevel, sm *scrape.Manager, dm *discovery.Manager) *TargetAllocatorManager { @@ -92,9 +96,13 @@ func createTargetAllocatorManager(filename string, logger *slog.Logger, logLevel reloadConfigHandler: nil, logger: logger, } - err := tam.loadConfig(filename) + hasTA, err := tam.loadConfig(filename) if err != nil { - logger.Warn("Could not load config for target allocator from file", "filename", filename, "err", err) + if hasTA { + logger.Warn("Could not load config for target allocator from file", "filename", filename, "err", err) + } else { + logger.Debug("Target allocator not configured; skipping target allocator config load", "filename", filename, "err", err) + } return &tam } if tam.config == nil { @@ -142,21 +150,21 @@ func createZapLogger(level *promslog.AllowedLevel) (*zap.Logger, error) { return zapLogger, err } -func (tam *TargetAllocatorManager) loadConfig(filename string) error { - config, err := loadConfigFromFilename(filename) +func (tam *TargetAllocatorManager) loadConfig(filename string) (bool, error) { + config, hasTA, err := loadConfigFromFilename(filename) if err != nil { - return err + return hasTA, err } tam.config = config if tam.config.TargetAllocator == nil { - return nil // no target allocator return + return hasTA, nil // no target allocator return } //has target allocator tam.config.TargetAllocator.TLSSetting.CAFile = DefaultTLSCaFilePath tam.config.TargetAllocator.TLSSetting.CertFile = DefaultTLSCertFilePath tam.config.TargetAllocator.TLSSetting.KeyFile = DefaultTLSKeyFilePath tam.config.TargetAllocator.TLSSetting.ReloadInterval = DEFAULT_TLS_RELOAD_INTERVAL_SECONDS - return nil + return hasTA, nil } func (tam *TargetAllocatorManager) Run() error { err := tam.manager.Start(context.Background(), tam.host, tam.sm, tam.dm) diff --git a/plugins/inputs/prometheus/target_allocator_test.go b/plugins/inputs/prometheus/target_allocator_test.go new file mode 100644 index 00000000000..83d3ab5727c --- /dev/null +++ b/plugins/inputs/prometheus/target_allocator_test.go @@ -0,0 +1,75 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package prometheus + +import ( + "bytes" + "log/slog" + "os" + "path/filepath" + "testing" + + "github.com/prometheus/common/promslog" + "github.com/stretchr/testify/assert" +) + +// newCapturingLogger returns a logger that captures all output for test assertions. +func newCapturingLogger() (*slog.Logger, *bytes.Buffer) { + buf := &bytes.Buffer{} + logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug})) + return logger, buf +} + +func infoLevel() *promslog.AllowedLevel { + lvl := &promslog.AllowedLevel{} + _ = lvl.Set("info") + return lvl +} + +// TestCreateTargetAllocatorManager_NoTASection_NoWarn verifies no Warn is logged for a non-TA config. +func TestCreateTargetAllocatorManager_NoTASection_NoWarn(t *testing.T) { + logger, buf := newCapturingLogger() + + tam := createTargetAllocatorManager("testdata/base-k8.yaml", logger, infoLevel(), nil, nil) + + assert.False(t, tam.enabled, "TA must be disabled") + + out := buf.String() + assert.NotContains(t, out, "level=WARN", "no Warn expected") + assert.NotContains(t, out, "Could not load config for target allocator", + "spurious TA warning must not appear") + + assert.Contains(t, out, "level=DEBUG") + assert.Contains(t, out, "Target allocator not configured") +} + +// TestCreateTargetAllocatorManager_MalformedTASection_Warns verifies a genuine TA config error still warns. +func TestCreateTargetAllocatorManager_MalformedTASection_Warns(t *testing.T) { + dir := t.TempDir() + badTA := filepath.Join(dir, "bad-ta.yaml") + // Has target_allocator section but an invalid key that fails strict unmarshal. + content := []byte("target_allocator:\n endpoint: http://target-allocator-service:80\n interval: 30s\nthis_key_is_invalid: true\n") + assert.NoError(t, os.WriteFile(badTA, content, 0o600)) + + logger, buf := newCapturingLogger() + + tam := createTargetAllocatorManager(badTA, logger, infoLevel(), nil, nil) + + assert.False(t, tam.enabled, "manager stays disabled") + out := buf.String() + assert.Contains(t, out, "level=WARN", "genuine TA error must still warn") + assert.Contains(t, out, "Could not load config for target allocator") +} + +// TestLoadConfigFromFilename_HasTA tests the TA section detection in loadConfigFromFilename. +func TestLoadConfigFromFilename_HasTA(t *testing.T) { + _, hasTA, _ := loadConfigFromFilename("testdata/base-k8.yaml") + assert.False(t, hasTA, "no TA section") + + _, hasTA, _ = loadConfigFromFilename("testdata/target_allocator.yaml") + assert.True(t, hasTA, "has TA section") + + _, hasTA, _ = loadConfigFromFilename("testdata/does-not-exist.yaml") + assert.True(t, hasTA, "missing file defaults to true (warn)") +}