/ @juliusv)
diff --git a/Makefile b/Makefile
index c4a9a06cfa..0dd8673af3 100644
--- a/Makefile
+++ b/Makefile
@@ -78,6 +78,17 @@ assets-tarball: assets
@echo '>> packaging assets'
scripts/package_assets.sh
+# We only want to generate the parser when there's changes to the grammar.
+.PHONY: parser
+parser:
+ @echo ">> running goyacc to generate the .go file."
+ifeq (, $(shell command -v goyacc > /dev/null))
+ @echo "goyacc not installed so skipping"
+ @echo "To install: go install golang.org/x/tools/cmd/goyacc@v0.6.0"
+else
+ goyacc -o promql/parser/generated_parser.y.go promql/parser/generated_parser.y
+endif
+
.PHONY: test
# If we only want to only test go code we have to change the test target
# which is called by all.
@@ -90,8 +101,10 @@ endif
.PHONY: npm_licenses
npm_licenses: ui-install
@echo ">> bundling npm licenses"
- rm -f $(REACT_APP_NPM_LICENSES_TARBALL)
- find $(UI_NODE_MODULES_PATH) -iname "license*" | tar cfj $(REACT_APP_NPM_LICENSES_TARBALL) --transform 's/^/npm_licenses\//' --files-from=-
+ rm -f $(REACT_APP_NPM_LICENSES_TARBALL) npm_licenses
+ ln -s . npm_licenses
+ find npm_licenses/$(UI_NODE_MODULES_PATH) -iname "license*" | tar cfj $(REACT_APP_NPM_LICENSES_TARBALL) --files-from=-
+ rm -f npm_licenses
.PHONY: tarball
tarball: npm_licenses common-tarball
@@ -107,7 +120,7 @@ plugins/plugins.go: plugins.yml plugins/generate.go
plugins: plugins/plugins.go
.PHONY: build
-build: assets npm_licenses assets-compress common-build plugins
+build: assets npm_licenses assets-compress plugins common-build
.PHONY: bench_tsdb
bench_tsdb: $(PROMU)
@@ -120,3 +133,8 @@ bench_tsdb: $(PROMU)
@$(GO) tool pprof --alloc_space -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/mem.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/memprof.alloc.svg
@$(GO) tool pprof -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/block.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/blockprof.svg
@$(GO) tool pprof -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/mutex.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/mutexprof.svg
+
+.PHONY: cli-documentation
+cli-documentation:
+ $(GO) run ./cmd/prometheus/ --write-documentation > docs/command-line/prometheus.md
+ $(GO) run ./cmd/promtool/ write-documentation > docs/command-line/promtool.md
diff --git a/Makefile.common b/Makefile.common
index 7642c4485c..0ce7ea4612 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -49,19 +49,19 @@ endif
GOTEST := $(GO) test
GOTEST_DIR :=
ifneq ($(CIRCLE_JOB),)
-ifneq ($(shell which gotestsum),)
+ifneq ($(shell command -v gotestsum > /dev/null),)
GOTEST_DIR := test-results
GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
endif
endif
-PROMU_VERSION ?= 0.13.0
+PROMU_VERSION ?= 0.15.0
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
SKIP_GOLANGCI_LINT :=
GOLANGCI_LINT :=
GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.49.0
+GOLANGCI_LINT_VERSION ?= v1.53.3
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
# windows isn't included here because of the path separator being different.
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
@@ -91,6 +91,8 @@ BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS))
PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS))
TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS))
+SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG))
+
ifeq ($(GOHOSTARCH),amd64)
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows))
# Only supported on amd64
@@ -176,7 +178,7 @@ endif
.PHONY: common-yamllint
common-yamllint:
@echo ">> running yamllint on all YAML files in the repository"
-ifeq (, $(shell which yamllint))
+ifeq (, $(shell command -v yamllint > /dev/null))
@echo "yamllint not installed so skipping"
else
yamllint .
@@ -205,7 +207,7 @@ common-tarball: promu
.PHONY: common-docker $(BUILD_DOCKER_ARCHS)
common-docker: $(BUILD_DOCKER_ARCHS)
$(BUILD_DOCKER_ARCHS): common-docker-%:
- docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \
+ docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \
-f $(DOCKERFILE_PATH) \
--build-arg ARCH="$*" \
--build-arg OS="linux" \
@@ -214,19 +216,19 @@ $(BUILD_DOCKER_ARCHS): common-docker-%:
.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
- docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
+ docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)"
DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
- docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
- docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
+ docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
+ docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
.PHONY: common-docker-manifest
common-docker-manifest:
- DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG))
- DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)"
+ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG))
+ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)"
.PHONY: promu
promu: $(PROMU)
diff --git a/README.md b/README.md
index 6ca98143cb..8b89bb01e5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
-# Prometheus
+
+ 
Prometheus
+
-[][circleci]
+Visit prometheus.io for the full documentation,
+examples and guides.
+
+
+
+[](https://github.com/prometheus/prometheus/actions/workflows/ci.yml)
[][quay]
[][hub]
[](https://goreportcard.com/report/github.com/prometheus/prometheus)
@@ -8,8 +15,7 @@
[](https://gitpod.io/#https://github.com/prometheus/prometheus)
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:prometheus)
-Visit [prometheus.io](https://prometheus.io) for the full documentation,
-examples and guides.
+
Prometheus, a [Cloud Native Computing Foundation](https://cncf.io/) project, is a systems and service monitoring system. It collects metrics
from configured targets at given intervals, evaluates rule expressions,
@@ -28,7 +34,7 @@ The features that distinguish Prometheus from other metrics and monitoring syste
## Architecture overview
-
+
## Install
@@ -132,8 +138,6 @@ make npm_licenses
make common-docker-amd64
```
-*NB* if you are on a Mac, you will need [gnu-tar](https://formulae.brew.sh/formula/gnu-tar).
-
## Using Prometheus as a Go Library
### Remote Write
@@ -172,7 +176,6 @@ For more information on building, running, and developing on the React-based UI,
## More information
* Godoc documentation is available via [pkg.go.dev](https://pkg.go.dev/github.com/prometheus/prometheus). Due to peculiarities of Go Modules, v2.x.y will be displayed as v0.x.y.
-* You will find a CircleCI configuration in [`.circleci/config.yml`](.circleci/config.yml).
* See the [Community page](https://prometheus.io/community) for how to reach the Prometheus developers and users on various communication channels.
## Contributing
@@ -184,5 +187,4 @@ Refer to [CONTRIBUTING.md](https://github.com/prometheus/prometheus/blob/main/CO
Apache License 2.0, see [LICENSE](https://github.com/prometheus/prometheus/blob/main/LICENSE).
[hub]: https://hub.docker.com/r/prom/prometheus/
-[circleci]: https://circleci.com/gh/prometheus/prometheus
[quay]: https://quay.io/repository/prometheus/prometheus
diff --git a/RELEASE.md b/RELEASE.md
index 5cf09e58e2..0d0918191b 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -44,8 +44,15 @@ Release cadence of first pre-releases being cut is 6 weeks.
| v2.37 LTS | 2022-06-29 | Julien Pivotto (GitHub: @roidelapluie) |
| v2.38 | 2022-08-10 | Julius Volz (GitHub: @juliusv) |
| v2.39 | 2022-09-21 | Ganesh Vernekar (GitHub: @codesome) |
-| v2.40 | 2022-11-02 | **searching for volunteer** |
-| v2.41 | 2022-12-14 | **searching for volunteer** |
+| v2.40 | 2022-11-02 | Ganesh Vernekar (GitHub: @codesome) |
+| v2.41 | 2022-12-14 | Julien Pivotto (GitHub: @roidelapluie) |
+| v2.42 | 2023-01-25 | Kemal Akkoyun (GitHub: @kakkoyun) |
+| v2.43 | 2023-03-08 | Julien Pivotto (GitHub: @roidelapluie) |
+| v2.44 | 2023-04-19 | Bryan Boreham (GitHub: @bboreham) |
+| v2.45 LTS | 2023-05-31 | Jesus Vazquez (Github: @jesusvazquez) |
+| v2.46 | 2023-07-12 | Julien Pivotto (GitHub: @roidelapluie) |
+| v2.47 | 2023-08-23 | **searching for volunteer** |
+| v2.48 | 2023-10-04 | **searching for volunteer** |
If you are interested in volunteering please create a pull request against the [prometheus/prometheus](https://github.com/prometheus/prometheus) repository and propose yourself for the release series of your choice.
diff --git a/VERSION b/VERSION
index cde8adf34d..e599014eab 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.39.0
+2.45.0
diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go
index 65fcf15238..debc0d3f9d 100644
--- a/cmd/prometheus/main.go
+++ b/cmd/prometheus/main.go
@@ -12,6 +12,7 @@
// limitations under the License.
// The main package for the Prometheus server executable.
+// nolint:revive // Many unsued function arguments in this file by design.
package main
import (
@@ -33,6 +34,7 @@ import (
"syscall"
"time"
+ "github.com/alecthomas/kingpin/v2"
"github.com/alecthomas/units"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
@@ -45,10 +47,8 @@ import (
promlogflag "github.com/prometheus/common/promlog/flag"
"github.com/prometheus/common/version"
toolkit_web "github.com/prometheus/exporter-toolkit/web"
- toolkit_webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
"go.uber.org/atomic"
"go.uber.org/automaxprocs/maxprocs"
- "gopkg.in/alecthomas/kingpin.v2"
"k8s.io/klog"
klogv2 "k8s.io/klog/v2"
@@ -57,6 +57,7 @@ import (
"github.com/prometheus/prometheus/discovery/legacymanager"
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/model/exemplar"
+ "github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/metadata"
"github.com/prometheus/prometheus/model/relabel"
@@ -70,9 +71,10 @@ import (
"github.com/prometheus/prometheus/tracing"
"github.com/prometheus/prometheus/tsdb"
"github.com/prometheus/prometheus/tsdb/agent"
+ "github.com/prometheus/prometheus/tsdb/wlog"
+ "github.com/prometheus/prometheus/util/documentcli"
"github.com/prometheus/prometheus/util/logging"
prom_runtime "github.com/prometheus/prometheus/util/runtime"
- "github.com/prometheus/prometheus/util/strutil"
"github.com/prometheus/prometheus/web"
)
@@ -195,6 +197,10 @@ func (c *flagConfig) setFeatureListOptions(logger log.Logger) error {
case "no-default-scrape-port":
c.scrape.NoDefaultPort = true
level.Info(logger).Log("msg", "No default port will be appended to scrape targets' addresses.")
+ case "native-histograms":
+ c.tsdb.EnableNativeHistograms = true
+ c.scrape.EnableProtobufNegotiation = true
+ level.Info(logger).Log("msg", "Experimental native histogram support enabled.")
case "":
continue
case "promql-at-modifier", "promql-negative-offset":
@@ -204,6 +210,12 @@ func (c *flagConfig) setFeatureListOptions(logger log.Logger) error {
}
}
}
+
+ if c.tsdb.EnableNativeHistograms && c.tsdb.EnableMemorySnapshotOnShutdown {
+ c.tsdb.EnableMemorySnapshotOnShutdown = false
+ level.Warn(logger).Log("msg", "memory-snapshot-on-shutdown has been disabled automatically because memory-snapshot-on-shutdown and native-histograms cannot be enabled at the same time.")
+ }
+
return nil
}
@@ -241,7 +253,10 @@ func main() {
a.Flag("web.listen-address", "Address to listen on for UI, API, and telemetry.").
Default("0.0.0.0:9090").StringVar(&cfg.web.ListenAddress)
- webConfig := toolkit_webflag.AddFlags(a)
+ webConfig := a.Flag(
+ "web.config.file",
+ "[EXPERIMENTAL] Path to configuration file that can enable TLS or authentication.",
+ ).Default("").String()
a.Flag("web.read-timeout",
"Maximum duration before timing out read of the request, and closing idle connections.").
@@ -320,9 +335,15 @@ func main() {
serverOnlyFlag(a, "storage.tsdb.wal-compression", "Compress the tsdb WAL.").
Hidden().Default("true").BoolVar(&cfg.tsdb.WALCompression)
+ serverOnlyFlag(a, "storage.tsdb.wal-compression-type", "Compression algorithm for the tsdb WAL.").
+ Hidden().Default(string(wlog.CompressionSnappy)).EnumVar(&cfg.tsdb.WALCompressionType, string(wlog.CompressionSnappy), string(wlog.CompressionZstd))
+
serverOnlyFlag(a, "storage.tsdb.head-chunks-write-queue-size", "Size of the queue through which head chunks are written to the disk to be m-mapped, 0 disables the queue completely. Experimental.").
Default("0").IntVar(&cfg.tsdb.HeadChunksWriteQueueSize)
+ serverOnlyFlag(a, "storage.tsdb.samples-per-chunk", "Target number of samples per chunk.").
+ Default("120").Hidden().IntVar(&cfg.tsdb.SamplesPerChunk)
+
agentOnlyFlag(a, "storage.agent.path", "Base path for metrics storage.").
Default("data-agent/").StringVar(&cfg.agentStoragePath)
@@ -333,6 +354,9 @@ func main() {
agentOnlyFlag(a, "storage.agent.wal-compression", "Compress the agent WAL.").
Default("true").BoolVar(&cfg.agent.WALCompression)
+ agentOnlyFlag(a, "storage.agent.wal-compression-type", "Compression algorithm for the agent WAL.").
+ Hidden().Default(string(wlog.CompressionSnappy)).EnumVar(&cfg.agent.WALCompressionType, string(wlog.CompressionSnappy), string(wlog.CompressionZstd))
+
agentOnlyFlag(a, "storage.agent.wal-truncate-frequency",
"The frequency at which to truncate the WAL and remove old data.").
Hidden().PlaceHolder("").SetValue(&cfg.agent.TruncateFrequency)
@@ -396,14 +420,23 @@ func main() {
a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates.").
Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval)
- a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
+ a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
Default("").StringsVar(&cfg.featureList)
promlogflag.AddFlags(a, &cfg.promlogConfig)
+ a.Flag("write-documentation", "Generate command line documentation. Internal use.").Hidden().Action(func(ctx *kingpin.ParseContext) error {
+ if err := documentcli.GenerateMarkdown(a.Model(), os.Stdout); err != nil {
+ os.Exit(1)
+ return err
+ }
+ os.Exit(0)
+ return nil
+ }).Bool()
+
_, err := a.Parse(os.Args[1:])
if err != nil {
- fmt.Fprintln(os.Stderr, fmt.Errorf("Error parsing commandline arguments: %w", err))
+ fmt.Fprintln(os.Stderr, fmt.Errorf("Error parsing command line arguments: %w", err))
a.Usage(os.Args[1:])
os.Exit(2)
}
@@ -456,11 +489,19 @@ func main() {
level.Error(logger).Log("msg", fmt.Sprintf("Error loading config (--config.file=%s)", cfg.configFile), "file", absPath, "err", err)
os.Exit(2)
}
+ if _, err := cfgFile.GetScrapeConfigs(); err != nil {
+ absPath, pathErr := filepath.Abs(cfg.configFile)
+ if pathErr != nil {
+ absPath = cfg.configFile
+ }
+ level.Error(logger).Log("msg", fmt.Sprintf("Error loading scrape config files from config (--config.file=%q)", cfg.configFile), "file", absPath, "err", err)
+ os.Exit(2)
+ }
if cfg.tsdb.EnableExemplarStorage {
if cfgFile.StorageConfig.ExemplarsConfig == nil {
cfgFile.StorageConfig.ExemplarsConfig = &config.DefaultExemplarsConfig
}
- cfg.tsdb.MaxExemplars = int64(cfgFile.StorageConfig.ExemplarsConfig.MaxExemplars)
+ cfg.tsdb.MaxExemplars = cfgFile.StorageConfig.ExemplarsConfig.MaxExemplars
}
if cfgFile.StorageConfig.TSDBConfig != nil {
cfg.tsdb.OutOfOrderTimeWindow = cfgFile.StorageConfig.TSDBConfig.OutOfOrderTimeWindow
@@ -619,7 +660,7 @@ func main() {
Appendable: fanoutStorage,
Queryable: localStorage,
QueryFunc: rules.EngineQueryFunc(queryEngine, fanoutStorage),
- NotifyFunc: sendAlerts(notifierManager, cfg.web.ExternalURL.String()),
+ NotifyFunc: rules.SendAlerts(notifierManager, cfg.web.ExternalURL.String()),
Context: ctxRule,
ExternalURL: cfg.web.ExternalURL,
Registerer: prometheus.DefaultRegisterer,
@@ -718,7 +759,11 @@ func main() {
name: "scrape_sd",
reloader: func(cfg *config.Config) error {
c := make(map[string]discovery.Configs)
- for _, v := range cfg.ScrapeConfigs {
+ scfgs, err := cfg.GetScrapeConfigs()
+ if err != nil {
+ return err
+ }
+ for _, v := range scfgs {
c[v.JobName] = v.ServiceDiscoveryConfigs
}
return discoveryManagerScrape.ApplyConfig(c)
@@ -1015,6 +1060,7 @@ func main() {
startTimeMargin := int64(2 * time.Duration(cfg.tsdb.MinBlockDuration).Seconds() * 1000)
localStorage.Set(db, startTimeMargin)
+ db.SetWriteNotified(remoteStorage)
close(dbOpen)
<-cancel
return nil
@@ -1270,36 +1316,6 @@ func computeExternalURL(u, listenAddr string) (*url.URL, error) {
return eu, nil
}
-type sender interface {
- Send(alerts ...*notifier.Alert)
-}
-
-// sendAlerts implements the rules.NotifyFunc for a Notifier.
-func sendAlerts(s sender, externalURL string) rules.NotifyFunc {
- return func(ctx context.Context, expr string, alerts ...*rules.Alert) {
- var res []*notifier.Alert
-
- for _, alert := range alerts {
- a := ¬ifier.Alert{
- StartsAt: alert.FiredAt,
- Labels: alert.Labels,
- Annotations: alert.Annotations,
- GeneratorURL: externalURL + strutil.TableLinkForExpression(expr),
- }
- if !alert.ResolvedAt.IsZero() {
- a.EndsAt = alert.ResolvedAt
- } else {
- a.EndsAt = alert.ValidUntil
- }
- res = append(res, a)
- }
-
- if len(alerts) > 0 {
- s.Send(res...)
- }
- }
-}
-
// readyStorage implements the Storage interface while allowing to set the actual
// storage at a later point in time.
type readyStorage struct {
@@ -1411,6 +1427,10 @@ func (n notReadyAppender) AppendExemplar(ref storage.SeriesRef, l labels.Labels,
return 0, tsdb.ErrNotReady
}
+func (n notReadyAppender) AppendHistogram(ref storage.SeriesRef, l labels.Labels, t int64, h *histogram.Histogram, fh *histogram.FloatHistogram) (storage.SeriesRef, error) {
+ return 0, tsdb.ErrNotReady
+}
+
func (n notReadyAppender) UpdateMetadata(ref storage.SeriesRef, l labels.Labels, m metadata.Metadata) (storage.SeriesRef, error) {
return 0, tsdb.ErrNotReady
}
@@ -1473,11 +1493,11 @@ func (s *readyStorage) Snapshot(dir string, withHead bool) error {
}
// Stats implements the api_v1.TSDBAdminStats interface.
-func (s *readyStorage) Stats(statsByLabelName string) (*tsdb.Stats, error) {
+func (s *readyStorage) Stats(statsByLabelName string, limit int) (*tsdb.Stats, error) {
if x := s.get(); x != nil {
switch db := x.(type) {
case *tsdb.DB:
- return db.Head().Stats(statsByLabelName), nil
+ return db.Head().Stats(statsByLabelName, limit), nil
case *agent.DB:
return nil, agent.ErrUnsupported
default:
@@ -1533,7 +1553,9 @@ type tsdbOptions struct {
MaxBytes units.Base2Bytes
NoLockfile bool
WALCompression bool
+ WALCompressionType string
HeadChunksWriteQueueSize int
+ SamplesPerChunk int
StripeSize int
MinBlockDuration model.Duration
MaxBlockDuration model.Duration
@@ -1541,6 +1563,7 @@ type tsdbOptions struct {
EnableExemplarStorage bool
MaxExemplars int64
EnableMemorySnapshotOnShutdown bool
+ EnableNativeHistograms bool
}
func (opts tsdbOptions) ToTSDBOptions() tsdb.Options {
@@ -1551,14 +1574,16 @@ func (opts tsdbOptions) ToTSDBOptions() tsdb.Options {
MaxBytes: int64(opts.MaxBytes),
NoLockfile: opts.NoLockfile,
AllowOverlappingCompaction: true,
- WALCompression: opts.WALCompression,
+ WALCompression: wlog.ParseCompressionType(opts.WALCompression, opts.WALCompressionType),
HeadChunksWriteQueueSize: opts.HeadChunksWriteQueueSize,
+ SamplesPerChunk: opts.SamplesPerChunk,
StripeSize: opts.StripeSize,
MinBlockDuration: int64(time.Duration(opts.MinBlockDuration) / time.Millisecond),
MaxBlockDuration: int64(time.Duration(opts.MaxBlockDuration) / time.Millisecond),
EnableExemplarStorage: opts.EnableExemplarStorage,
MaxExemplars: opts.MaxExemplars,
EnableMemorySnapshotOnShutdown: opts.EnableMemorySnapshotOnShutdown,
+ EnableNativeHistograms: opts.EnableNativeHistograms,
OutOfOrderTimeWindow: opts.OutOfOrderTimeWindow,
}
}
@@ -1568,6 +1593,7 @@ func (opts tsdbOptions) ToTSDBOptions() tsdb.Options {
type agentOptions struct {
WALSegmentSize units.Base2Bytes
WALCompression bool
+ WALCompressionType string
StripeSize int
TruncateFrequency model.Duration
MinWALTime, MaxWALTime model.Duration
@@ -1577,7 +1603,7 @@ type agentOptions struct {
func (opts agentOptions) ToAgentOptions() agent.Options {
return agent.Options{
WALSegmentSize: int(opts.WALSegmentSize),
- WALCompression: opts.WALCompression,
+ WALCompression: wlog.ParseCompressionType(opts.WALCompression, opts.WALCompressionType),
StripeSize: opts.StripeSize,
TruncateFrequency: time.Duration(opts.TruncateFrequency),
MinWALTime: durationToInt64Millis(time.Duration(opts.MinWALTime)),
diff --git a/cmd/prometheus/main_test.go b/cmd/prometheus/main_test.go
index 7dec5b9a59..21447d0369 100644
--- a/cmd/prometheus/main_test.go
+++ b/cmd/prometheus/main_test.go
@@ -23,6 +23,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "runtime"
"strings"
"syscall"
"testing"
@@ -120,7 +121,7 @@ func TestFailedStartupExitCode(t *testing.T) {
fakeInputFile := "fake-input-file"
expectedExitStatus := 2
- prom := exec.Command(promPath, "-test.main", "--config.file="+fakeInputFile)
+ prom := exec.Command(promPath, "-test.main", "--web.listen-address=0.0.0.0:0", "--config.file="+fakeInputFile)
err := prom.Run()
require.Error(t, err)
@@ -198,7 +199,7 @@ func TestSendAlerts(t *testing.T) {
}
require.Equal(t, tc.exp, alerts)
})
- sendAlerts(senderFunc, "http://localhost:9090")(context.TODO(), "up", tc.in...)
+ rules.SendAlerts(senderFunc, "http://localhost:9090")(context.TODO(), "up", tc.in...)
})
}
}
@@ -357,7 +358,7 @@ func getCurrentGaugeValuesFor(t *testing.T, reg prometheus.Gatherer, metricNames
}
func TestAgentSuccessfulStartup(t *testing.T) {
- prom := exec.Command(promPath, "-test.main", "--enable-feature=agent", "--config.file="+agentConfig)
+ prom := exec.Command(promPath, "-test.main", "--enable-feature=agent", "--web.listen-address=0.0.0.0:0", "--config.file="+agentConfig)
require.NoError(t, prom.Start())
actualExitStatus := 0
@@ -375,7 +376,7 @@ func TestAgentSuccessfulStartup(t *testing.T) {
}
func TestAgentFailedStartupWithServerFlag(t *testing.T) {
- prom := exec.Command(promPath, "-test.main", "--enable-feature=agent", "--storage.tsdb.path=.", "--config.file="+promConfig)
+ prom := exec.Command(promPath, "-test.main", "--enable-feature=agent", "--storage.tsdb.path=.", "--web.listen-address=0.0.0.0:0", "--config.file="+promConfig)
output := bytes.Buffer{}
prom.Stderr = &output
@@ -402,7 +403,7 @@ func TestAgentFailedStartupWithServerFlag(t *testing.T) {
}
func TestAgentFailedStartupWithInvalidConfig(t *testing.T) {
- prom := exec.Command(promPath, "-test.main", "--enable-feature=agent", "--config.file="+promConfig)
+ prom := exec.Command(promPath, "-test.main", "--enable-feature=agent", "--web.listen-address=0.0.0.0:0", "--config.file="+promConfig)
require.NoError(t, prom.Start())
actualExitStatus := 0
@@ -437,7 +438,7 @@ func TestModeSpecificFlags(t *testing.T) {
for _, tc := range testcases {
t.Run(fmt.Sprintf("%s mode with option %s", tc.mode, tc.arg), func(t *testing.T) {
- args := []string{"-test.main", tc.arg, t.TempDir()}
+ args := []string{"-test.main", tc.arg, t.TempDir(), "--web.listen-address=0.0.0.0:0"}
if tc.mode == "agent" {
args = append(args, "--enable-feature=agent", "--config.file="+agentConfig)
@@ -483,3 +484,31 @@ func TestModeSpecificFlags(t *testing.T) {
})
}
}
+
+func TestDocumentation(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.SkipNow()
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ cmd := exec.CommandContext(ctx, promPath, "-test.main", "--write-documentation")
+
+ var stdout bytes.Buffer
+ cmd.Stdout = &stdout
+
+ if err := cmd.Run(); err != nil {
+ if exitError, ok := err.(*exec.ExitError); ok {
+ if exitError.ExitCode() != 0 {
+ fmt.Println("Command failed with non-zero exit code")
+ }
+ }
+ }
+
+ generatedContent := strings.ReplaceAll(stdout.String(), filepath.Base(promPath), strings.TrimSuffix(filepath.Base(promPath), ".test"))
+
+ expectedContent, err := os.ReadFile(filepath.Join("..", "..", "docs", "command-line", "prometheus.md"))
+ require.NoError(t, err)
+
+ require.Equal(t, string(expectedContent), generatedContent, "Generated content does not match documentation. Hint: run `make cli-documentation`.")
+}
diff --git a/cmd/prometheus/main_unix_test.go b/cmd/prometheus/main_unix_test.go
index b49110ea91..7224e25d70 100644
--- a/cmd/prometheus/main_unix_test.go
+++ b/cmd/prometheus/main_unix_test.go
@@ -72,9 +72,11 @@ Loop:
if !startedOk {
t.Fatal("prometheus didn't start in the specified timeout")
}
- if err := prom.Process.Kill(); err == nil {
+ switch err := prom.Process.Kill(); {
+ case err == nil:
t.Errorf("prometheus didn't shutdown gracefully after sending the Interrupt signal")
- } else if stoppedErr != nil && stoppedErr.Error() != "signal: interrupt" { // TODO - find a better way to detect when the process didn't exit as expected!
+ case stoppedErr != nil && stoppedErr.Error() != "signal: interrupt":
+ // TODO: find a better way to detect when the process didn't exit as expected!
t.Errorf("prometheus exited with an unexpected error: %v", stoppedErr)
}
}
diff --git a/cmd/prometheus/query_log_test.go b/cmd/prometheus/query_log_test.go
index d5dfbea509..f20f2a22c0 100644
--- a/cmd/prometheus/query_log_test.go
+++ b/cmd/prometheus/query_log_test.go
@@ -193,7 +193,7 @@ func (p *queryLogTest) String() string {
}
name = name + ", " + p.host + ":" + strconv.Itoa(p.port)
if p.enabledAtStart {
- name = name + ", enabled at start"
+ name += ", enabled at start"
}
if p.prefix != "" {
name = name + ", with prefix " + p.prefix
diff --git a/cmd/promtool/backfill.go b/cmd/promtool/backfill.go
index 3c23d2c037..39410881b2 100644
--- a/cmd/promtool/backfill.go
+++ b/cmd/promtool/backfill.go
@@ -101,7 +101,7 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn
nextSampleTs int64 = math.MaxInt64
)
- for t := mint; t <= maxt; t = t + blockDuration {
+ for t := mint; t <= maxt; t += blockDuration {
tsUpper := t + blockDuration
if nextSampleTs != math.MaxInt64 && nextSampleTs >= tsUpper {
// The next sample is not in this timerange, we can avoid parsing
diff --git a/cmd/promtool/backfill_test.go b/cmd/promtool/backfill_test.go
index c4e6c6b7c0..e6f7cad31b 100644
--- a/cmd/promtool/backfill_test.go
+++ b/cmd/promtool/backfill_test.go
@@ -25,6 +25,7 @@ import (
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb"
+ "github.com/prometheus/prometheus/tsdb/chunkenc"
)
type backfillSample struct {
@@ -43,14 +44,14 @@ func sortSamples(samples []backfillSample) {
})
}
-func queryAllSeries(t testing.TB, q storage.Querier, expectedMinTime, expectedMaxTime int64) []backfillSample {
+func queryAllSeries(t testing.TB, q storage.Querier, expectedMinTime, expectedMaxTime int64) []backfillSample { // nolint:revive
ss := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "", ".*"))
samples := []backfillSample{}
for ss.Next() {
series := ss.At()
- it := series.Iterator()
+ it := series.Iterator(nil)
require.NoError(t, it.Err())
- for it.Next() {
+ for it.Next() == chunkenc.ValFloat {
ts, v := it.At()
samples = append(samples, backfillSample{Timestamp: ts, Value: v, Labels: series.Labels()})
}
diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go
index a2f710d147..f94be8b27f 100644
--- a/cmd/promtool/main.go
+++ b/cmd/promtool/main.go
@@ -31,6 +31,7 @@ import (
"text/tabwriter"
"time"
+ "github.com/alecthomas/kingpin/v2"
"github.com/go-kit/log"
"github.com/google/pprof/profile"
"github.com/prometheus/client_golang/api"
@@ -41,10 +42,10 @@ import (
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
"github.com/prometheus/exporter-toolkit/web"
- "gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/yaml.v2"
dto "github.com/prometheus/client_model/go"
+ promconfig "github.com/prometheus/common/config"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/prometheus/config"
@@ -58,6 +59,7 @@ import (
_ "github.com/prometheus/prometheus/plugins" // Register plugins.
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/scrape"
+ "github.com/prometheus/prometheus/util/documentcli"
)
const (
@@ -69,11 +71,20 @@ const (
lintOptionAll = "all"
lintOptionDuplicateRules = "duplicate-rules"
lintOptionNone = "none"
+ checkHealth = "/-/healthy"
+ checkReadiness = "/-/ready"
)
var lintOptions = []string{lintOptionAll, lintOptionDuplicateRules, lintOptionNone}
func main() {
+ var (
+ httpRoundTripper = api.DefaultRoundTripper
+ serverURL *url.URL
+ remoteWriteURL *url.URL
+ httpConfigFilePath string
+ )
+
app := kingpin.New(filepath.Base(os.Args[0]), "Tooling for the Prometheus monitoring system.").UsageWriter(os.Stdout)
app.Version(version.Print("promtool"))
app.HelpFlag.Short('h')
@@ -105,11 +116,19 @@ func main() {
"The config files to check.",
).Required().ExistingFiles()
+ checkServerHealthCmd := checkCmd.Command("healthy", "Check if the Prometheus server is healthy.")
+ checkServerHealthCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("").ExistingFileVar(&httpConfigFilePath)
+ checkServerHealthCmd.Flag("url", "The URL for the Prometheus server.").Default("http://localhost:9090").URLVar(&serverURL)
+
+ checkServerReadyCmd := checkCmd.Command("ready", "Check if the Prometheus server is ready.")
+ checkServerReadyCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("").ExistingFileVar(&httpConfigFilePath)
+ checkServerReadyCmd.Flag("url", "The URL for the Prometheus server.").Default("http://localhost:9090").URLVar(&serverURL)
+
checkRulesCmd := checkCmd.Command("rules", "Check if the rule files are valid or not.")
ruleFiles := checkRulesCmd.Arg(
"rule-files",
- "The rule files to check.",
- ).Required().ExistingFiles()
+ "The rule files to check, default is read from standard input.",
+ ).ExistingFiles()
checkRulesLint := checkRulesCmd.Flag(
"lint",
"Linting checks to apply. Available options are: "+strings.Join(lintOptions, ", ")+". Use --lint=none to disable linting",
@@ -124,14 +143,15 @@ func main() {
queryCmd := app.Command("query", "Run query against a Prometheus server.")
queryCmdFmt := queryCmd.Flag("format", "Output format of the query.").Short('o').Default("promql").Enum("promql", "json")
+ queryCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("").ExistingFileVar(&httpConfigFilePath)
queryInstantCmd := queryCmd.Command("instant", "Run instant query.")
- queryInstantServer := queryInstantCmd.Arg("server", "Prometheus server to query.").Required().URL()
+ queryInstantCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL)
queryInstantExpr := queryInstantCmd.Arg("expr", "PromQL query expression.").Required().String()
queryInstantTime := queryInstantCmd.Flag("time", "Query evaluation time (RFC3339 or Unix timestamp).").String()
queryRangeCmd := queryCmd.Command("range", "Run range query.")
- queryRangeServer := queryRangeCmd.Arg("server", "Prometheus server to query.").Required().URL()
+ queryRangeCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL)
queryRangeExpr := queryRangeCmd.Arg("expr", "PromQL query expression.").Required().String()
queryRangeHeaders := queryRangeCmd.Flag("header", "Extra headers to send to server.").StringMap()
queryRangeBegin := queryRangeCmd.Flag("start", "Query range start time (RFC3339 or Unix timestamp).").String()
@@ -139,7 +159,7 @@ func main() {
queryRangeStep := queryRangeCmd.Flag("step", "Query step size (duration).").Duration()
querySeriesCmd := queryCmd.Command("series", "Run series query.")
- querySeriesServer := querySeriesCmd.Arg("server", "Prometheus server to query.").Required().URL()
+ querySeriesCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL)
querySeriesMatch := querySeriesCmd.Flag("match", "Series selector. Can be specified multiple times.").Required().Strings()
querySeriesBegin := querySeriesCmd.Flag("start", "Start time (RFC3339 or Unix timestamp).").String()
querySeriesEnd := querySeriesCmd.Flag("end", "End time (RFC3339 or Unix timestamp).").String()
@@ -153,12 +173,24 @@ func main() {
debugAllServer := debugAllCmd.Arg("server", "Prometheus server to get all debug information from.").Required().String()
queryLabelsCmd := queryCmd.Command("labels", "Run labels query.")
- queryLabelsServer := queryLabelsCmd.Arg("server", "Prometheus server to query.").Required().URL()
+ queryLabelsCmd.Arg("server", "Prometheus server to query.").Required().URLVar(&serverURL)
queryLabelsName := queryLabelsCmd.Arg("name", "Label name to provide label values for.").Required().String()
queryLabelsBegin := queryLabelsCmd.Flag("start", "Start time (RFC3339 or Unix timestamp).").String()
queryLabelsEnd := queryLabelsCmd.Flag("end", "End time (RFC3339 or Unix timestamp).").String()
queryLabelsMatch := queryLabelsCmd.Flag("match", "Series selector. Can be specified multiple times.").Strings()
+ pushCmd := app.Command("push", "Push to a Prometheus server.")
+ pushCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("").ExistingFileVar(&httpConfigFilePath)
+ pushMetricsCmd := pushCmd.Command("metrics", "Push metrics to a prometheus remote write (for testing purpose only).")
+ pushMetricsCmd.Arg("remote-write-url", "Prometheus remote write url to push metrics.").Required().URLVar(&remoteWriteURL)
+ metricFiles := pushMetricsCmd.Arg(
+ "metric-files",
+ "The metric files to push, default is read from standard input.",
+ ).ExistingFiles()
+ pushMetricsLabels := pushMetricsCmd.Flag("label", "Label to attach to metrics. Can be specified multiple times.").Default("job=promtool").StringMap()
+ pushMetricsTimeout := pushMetricsCmd.Flag("timeout", "The time to wait for pushing metrics.").Default("30s").Duration()
+ pushMetricsHeaders := pushMetricsCmd.Flag("header", "Prometheus remote write header.").StringMap()
+
testCmd := app.Command("test", "Unit testing.")
testRulesCmd := testCmd.Command("rules", "Unit tests for rules.")
testRulesFiles := testRulesCmd.Arg(
@@ -190,6 +222,7 @@ func main() {
dumpPath := tsdbDumpCmd.Arg("db path", "Database path (default is "+defaultDBPath+").").Default(defaultDBPath).String()
dumpMinTime := tsdbDumpCmd.Flag("min-time", "Minimum timestamp to dump.").Default(strconv.FormatInt(math.MinInt64, 10)).Int64()
dumpMaxTime := tsdbDumpCmd.Flag("max-time", "Maximum timestamp to dump.").Default(strconv.FormatInt(math.MaxInt64, 10)).Int64()
+ dumpMatch := tsdbDumpCmd.Flag("match", "Series selector.").Default("{__name__=~'(?s:.*)'}").String()
importCmd := tsdbCmd.Command("create-blocks-from", "[Experimental] Import samples from input and produce TSDB blocks. Please refer to the storage docs for more details.")
importHumanReadable := importCmd.Flag("human-readable", "Print human readable values.").Short('r').Bool()
@@ -199,7 +232,8 @@ func main() {
importFilePath := openMetricsImportCmd.Arg("input file", "OpenMetrics file to read samples from.").Required().String()
importDBPath := openMetricsImportCmd.Arg("output directory", "Output directory for generated blocks.").Default(defaultDBPath).String()
importRulesCmd := importCmd.Command("rules", "Create blocks of data for new recording rules.")
- importRulesURL := importRulesCmd.Flag("url", "The URL for the Prometheus API with the data where the rule will be backfilled from.").Default("http://localhost:9090").URL()
+ importRulesCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("").ExistingFileVar(&httpConfigFilePath)
+ importRulesCmd.Flag("url", "The URL for the Prometheus API with the data where the rule will be backfilled from.").Default("http://localhost:9090").URLVar(&serverURL)
importRulesStart := importRulesCmd.Flag("start", "The time to start backfilling the new rule from. Must be a RFC3339 formatted date or Unix timestamp. Required.").
Required().String()
importRulesEnd := importRulesCmd.Flag("end", "If an end time is provided, all recording rules in the rule files provided will be backfilled to the end time. Default will backfill up to 3 hours ago. Must be a RFC3339 formatted date or Unix timestamp.").String()
@@ -213,6 +247,8 @@ func main() {
featureList := app.Flag("enable-feature", "Comma separated feature names to enable (only PromQL related and no-default-scrape-port). See https://prometheus.io/docs/prometheus/latest/feature_flags/ for the options and more details.").Default("").Strings()
+ documentationCmd := app.Command("write-documentation", "Generate command line documentation. Internal use.").Hidden()
+
parsedCmd := kingpin.MustParse(app.Parse(os.Args[1:]))
var p printer
@@ -223,6 +259,22 @@ func main() {
p = &promqlPrinter{}
}
+ if httpConfigFilePath != "" {
+ if serverURL != nil && serverURL.User.Username() != "" {
+ kingpin.Fatalf("Cannot set base auth in the server URL and use a http.config.file at the same time")
+ }
+ var err error
+ httpConfig, _, err := config_util.LoadHTTPConfigFile(httpConfigFilePath)
+ if err != nil {
+ kingpin.Fatalf("Failed to load HTTP config file: %v", err)
+ }
+
+ httpRoundTripper, err = promconfig.NewRoundTripperFromConfig(*httpConfig, "promtool", config_util.WithUserAgent("promtool/"+version.Version))
+ if err != nil {
+ kingpin.Fatalf("Failed to create a new HTTP round tripper: %v", err)
+ }
+ }
+
var noDefaultScrapePort bool
for _, f := range *featureList {
opts := strings.Split(f, ",")
@@ -247,6 +299,12 @@ func main() {
case checkConfigCmd.FullCommand():
os.Exit(CheckConfig(*agentMode, *checkConfigSyntaxOnly, newLintConfig(*checkConfigLint, *checkConfigLintFatal), *configFiles...))
+ case checkServerHealthCmd.FullCommand():
+ os.Exit(checkErr(CheckServerStatus(serverURL, checkHealth, httpRoundTripper)))
+
+ case checkServerReadyCmd.FullCommand():
+ os.Exit(checkErr(CheckServerStatus(serverURL, checkReadiness, httpRoundTripper)))
+
case checkWebConfigCmd.FullCommand():
os.Exit(CheckWebConfig(*webConfigFiles...))
@@ -256,14 +314,17 @@ func main() {
case checkMetricsCmd.FullCommand():
os.Exit(CheckMetrics(*checkMetricsExtended))
+ case pushMetricsCmd.FullCommand():
+ os.Exit(PushMetrics(remoteWriteURL, httpRoundTripper, *pushMetricsHeaders, *pushMetricsTimeout, *pushMetricsLabels, *metricFiles...))
+
case queryInstantCmd.FullCommand():
- os.Exit(QueryInstant(*queryInstantServer, *queryInstantExpr, *queryInstantTime, p))
+ os.Exit(QueryInstant(serverURL, httpRoundTripper, *queryInstantExpr, *queryInstantTime, p))
case queryRangeCmd.FullCommand():
- os.Exit(QueryRange(*queryRangeServer, *queryRangeHeaders, *queryRangeExpr, *queryRangeBegin, *queryRangeEnd, *queryRangeStep, p))
+ os.Exit(QueryRange(serverURL, httpRoundTripper, *queryRangeHeaders, *queryRangeExpr, *queryRangeBegin, *queryRangeEnd, *queryRangeStep, p))
case querySeriesCmd.FullCommand():
- os.Exit(QuerySeries(*querySeriesServer, *querySeriesMatch, *querySeriesBegin, *querySeriesEnd, p))
+ os.Exit(QuerySeries(serverURL, httpRoundTripper, *querySeriesMatch, *querySeriesBegin, *querySeriesEnd, p))
case debugPprofCmd.FullCommand():
os.Exit(debugPprof(*debugPprofServer))
@@ -275,7 +336,7 @@ func main() {
os.Exit(debugAll(*debugAllServer))
case queryLabelsCmd.FullCommand():
- os.Exit(QueryLabels(*queryLabelsServer, *queryLabelsMatch, *queryLabelsName, *queryLabelsBegin, *queryLabelsEnd, p))
+ os.Exit(QueryLabels(serverURL, httpRoundTripper, *queryLabelsMatch, *queryLabelsName, *queryLabelsBegin, *queryLabelsEnd, p))
case testRulesCmd.FullCommand():
os.Exit(RulesUnitTest(
@@ -296,13 +357,15 @@ func main() {
os.Exit(checkErr(listBlocks(*listPath, *listHumanReadable)))
case tsdbDumpCmd.FullCommand():
- os.Exit(checkErr(dumpSamples(*dumpPath, *dumpMinTime, *dumpMaxTime)))
+ os.Exit(checkErr(dumpSamples(*dumpPath, *dumpMinTime, *dumpMaxTime, *dumpMatch)))
// TODO(aSquare14): Work on adding support for custom block size.
case openMetricsImportCmd.FullCommand():
os.Exit(backfillOpenMetrics(*importFilePath, *importDBPath, *importHumanReadable, *importQuiet, *maxBlockDuration))
case importRulesCmd.FullCommand():
- os.Exit(checkErr(importRules(*importRulesURL, *importRulesStart, *importRulesEnd, *importRulesOutputDir, *importRulesEvalInterval, *maxBlockDuration, *importRulesFiles...)))
+ os.Exit(checkErr(importRules(serverURL, httpRoundTripper, *importRulesStart, *importRulesEnd, *importRulesOutputDir, *importRulesEvalInterval, *maxBlockDuration, *importRulesFiles...)))
+ case documentationCmd.FullCommand():
+ os.Exit(checkErr(documentcli.GenerateMarkdown(app.Model(), os.Stdout)))
}
}
@@ -338,6 +401,43 @@ func (ls lintConfig) lintDuplicateRules() bool {
return ls.all || ls.duplicateRules
}
+// Check server status - healthy & ready.
+func CheckServerStatus(serverURL *url.URL, checkEndpoint string, roundTripper http.RoundTripper) error {
+ if serverURL.Scheme == "" {
+ serverURL.Scheme = "http"
+ }
+
+ config := api.Config{
+ Address: serverURL.String() + checkEndpoint,
+ RoundTripper: roundTripper,
+ }
+
+ // Create new client.
+ c, err := api.NewClient(config)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "error creating API client:", err)
+ return err
+ }
+
+ request, err := http.NewRequest("GET", config.Address, nil)
+ if err != nil {
+ return err
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ response, dataBytes, err := c.Do(ctx, request)
+ if err != nil {
+ return err
+ }
+
+ if response.StatusCode != http.StatusOK {
+ return fmt.Errorf("check failed: URL=%s, status=%d", serverURL, response.StatusCode)
+ }
+
+ fmt.Fprintln(os.Stderr, " SUCCESS: ", string(dataBytes))
+ return nil
+}
+
// CheckConfig validates configuration files.
func CheckConfig(agentMode, checkSyntaxOnly bool, lintSettings lintConfig, files ...string) int {
failed := false
@@ -357,20 +457,12 @@ func CheckConfig(agentMode, checkSyntaxOnly bool, lintSettings lintConfig, files
}
fmt.Println()
- for _, rf := range ruleFiles {
- if n, errs := checkRules(rf, lintSettings); len(errs) > 0 {
- fmt.Fprintln(os.Stderr, " FAILED:")
- for _, err := range errs {
- fmt.Fprintln(os.Stderr, " ", err)
- }
- failed = true
- for _, err := range errs {
- hasErrors = hasErrors || !errors.Is(err, lintError)
- }
- } else {
- fmt.Printf(" SUCCESS: %d rules found\n", n)
- }
- fmt.Println()
+ rulesFailed, rulesHasErrors := checkRules(ruleFiles, lintSettings)
+ if rulesFailed {
+ failed = rulesFailed
+ }
+ if rulesHasErrors {
+ hasErrors = rulesHasErrors
}
}
if failed && hasErrors {
@@ -437,7 +529,18 @@ func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]strin
}
}
- for _, scfg := range cfg.ScrapeConfigs {
+ var scfgs []*config.ScrapeConfig
+ if checkSyntaxOnly {
+ scfgs = cfg.ScrapeConfigs
+ } else {
+ var err error
+ scfgs, err = cfg.GetScrapeConfigs()
+ if err != nil {
+ return nil, fmt.Errorf("error loading scrape configs: %w", err)
+ }
+ }
+
+ for _, scfg := range scfgs {
if !checkSyntaxOnly && scfg.HTTPClientConfig.Authorization != nil {
if err := checkFileExists(scfg.HTTPClientConfig.Authorization.CredentialsFile); err != nil {
return nil, fmt.Errorf("error checking authorization credentials or bearer token file %q: %w", scfg.HTTPClientConfig.Authorization.CredentialsFile, err)
@@ -587,9 +690,57 @@ func checkSDFile(filename string) ([]*targetgroup.Group, error) {
func CheckRules(ls lintConfig, files ...string) int {
failed := false
hasErrors := false
+ if len(files) == 0 {
+ fmt.Println("Checking standard input")
+ data, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, " FAILED:", err)
+ return failureExitCode
+ }
+ rgs, errs := rulefmt.Parse(data)
+ for _, e := range errs {
+ fmt.Fprintln(os.Stderr, e.Error())
+ return failureExitCode
+ }
+ if n, errs := checkRuleGroups(rgs, ls); errs != nil {
+ fmt.Fprintln(os.Stderr, " FAILED:")
+ for _, e := range errs {
+ fmt.Fprintln(os.Stderr, e.Error())
+ }
+ failed = true
+ for _, err := range errs {
+ hasErrors = hasErrors || !errors.Is(err, lintError)
+ }
+ } else {
+ fmt.Printf(" SUCCESS: %d rules found\n", n)
+ }
+ fmt.Println()
+ } else {
+ failed, hasErrors = checkRules(files, ls)
+ }
+ if failed && hasErrors {
+ return failureExitCode
+ }
+ if failed && ls.fatal {
+ return lintErrExitCode
+ }
+
+ return successExitCode
+}
+
+// checkRules validates rule files.
+func checkRules(files []string, ls lintConfig) (bool, bool) {
+ failed := false
+ hasErrors := false
for _, f := range files {
- if n, errs := checkRules(f, ls); errs != nil {
+ fmt.Println("Checking", f)
+ rgs, errs := rulefmt.ParseFile(f)
+ if errs != nil {
+ failed = true
+ continue
+ }
+ if n, errs := checkRuleGroups(rgs, ls); errs != nil {
fmt.Fprintln(os.Stderr, " FAILED:")
for _, e := range errs {
fmt.Fprintln(os.Stderr, e.Error())
@@ -603,23 +754,10 @@ func CheckRules(ls lintConfig, files ...string) int {
}
fmt.Println()
}
- if failed && hasErrors {
- return failureExitCode
- }
- if failed && ls.fatal {
- return lintErrExitCode
- }
- return successExitCode
+ return failed, hasErrors
}
-func checkRules(filename string, lintSettings lintConfig) (int, []error) {
- fmt.Println("Checking", filename)
-
- rgs, errs := rulefmt.ParseFile(filename)
- if errs != nil {
- return successExitCode, errs
- }
-
+func checkRuleGroups(rgs *rulefmt.RuleGroups, lintSettings lintConfig) (int, []error) {
numRules := 0
for _, rg := range rgs.Groups {
numRules += len(rg.Rules)
@@ -631,9 +769,9 @@ func checkRules(filename string, lintSettings lintConfig) (int, []error) {
errMessage := fmt.Sprintf("%d duplicate rule(s) found.\n", len(dRules))
for _, n := range dRules {
errMessage += fmt.Sprintf("Metric: %s\nLabel(s):\n", n.metric)
- for _, l := range n.label {
+ n.label.Range(func(l labels.Label) {
errMessage += fmt.Sprintf("\t%s: %s\n", l.Name, l.Value)
- }
+ })
}
errMessage += "Might cause inconsistency while recording expressions"
return 0, []error{fmt.Errorf("%w %s", lintError, errMessage)}
@@ -794,12 +932,13 @@ func checkMetricsExtended(r io.Reader) ([]metricStat, int, error) {
}
// QueryInstant performs an instant query against a Prometheus server.
-func QueryInstant(url *url.URL, query, evalTime string, p printer) int {
+func QueryInstant(url *url.URL, roundTripper http.RoundTripper, query, evalTime string, p printer) int {
if url.Scheme == "" {
url.Scheme = "http"
}
config := api.Config{
- Address: url.String(),
+ Address: url.String(),
+ RoundTripper: roundTripper,
}
// Create new client.
@@ -834,12 +973,13 @@ func QueryInstant(url *url.URL, query, evalTime string, p printer) int {
}
// QueryRange performs a range query against a Prometheus server.
-func QueryRange(url *url.URL, headers map[string]string, query, start, end string, step time.Duration, p printer) int {
+func QueryRange(url *url.URL, roundTripper http.RoundTripper, headers map[string]string, query, start, end string, step time.Duration, p printer) int {
if url.Scheme == "" {
url.Scheme = "http"
}
config := api.Config{
- Address: url.String(),
+ Address: url.String(),
+ RoundTripper: roundTripper,
}
if len(headers) > 0 {
@@ -847,7 +987,7 @@ func QueryRange(url *url.URL, headers map[string]string, query, start, end strin
for key, value := range headers {
req.Header.Add(key, value)
}
- return http.DefaultTransport.RoundTrip(req)
+ return roundTripper.RoundTrip(req)
})
}
@@ -907,12 +1047,13 @@ func QueryRange(url *url.URL, headers map[string]string, query, start, end strin
}
// QuerySeries queries for a series against a Prometheus server.
-func QuerySeries(url *url.URL, matchers []string, start, end string, p printer) int {
+func QuerySeries(url *url.URL, roundTripper http.RoundTripper, matchers []string, start, end string, p printer) int {
if url.Scheme == "" {
url.Scheme = "http"
}
config := api.Config{
- Address: url.String(),
+ Address: url.String(),
+ RoundTripper: roundTripper,
}
// Create new client.
@@ -943,12 +1084,13 @@ func QuerySeries(url *url.URL, matchers []string, start, end string, p printer)
}
// QueryLabels queries for label values against a Prometheus server.
-func QueryLabels(url *url.URL, matchers []string, name, start, end string, p printer) int {
+func QueryLabels(url *url.URL, roundTripper http.RoundTripper, matchers []string, name, start, end string, p printer) int {
if url.Scheme == "" {
url.Scheme = "http"
}
config := api.Config{
- Address: url.String(),
+ Address: url.String(),
+ RoundTripper: roundTripper,
}
// Create new client.
@@ -1153,7 +1295,7 @@ func (j *jsonPrinter) printLabelValues(v model.LabelValues) {
// importRules backfills recording rules from the files provided. The output are blocks of data
// at the outputDir location.
-func importRules(url *url.URL, start, end, outputDir string, evalInterval, maxBlockDuration time.Duration, files ...string) error {
+func importRules(url *url.URL, roundTripper http.RoundTripper, start, end, outputDir string, evalInterval, maxBlockDuration time.Duration, files ...string) error {
ctx := context.Background()
var stime, etime time.Time
var err error
@@ -1183,7 +1325,8 @@ func importRules(url *url.URL, start, end, outputDir string, evalInterval, maxBl
maxBlockDuration: maxBlockDuration,
}
client, err := api.NewClient(api.Config{
- Address: url.String(),
+ Address: url.String(),
+ RoundTripper: roundTripper,
})
if err != nil {
return fmt.Errorf("new api client error: %w", err)
@@ -1219,8 +1362,11 @@ func checkTargetGroupsForAlertmanager(targetGroups []*targetgroup.Group, amcfg *
}
func checkTargetGroupsForScrapeConfig(targetGroups []*targetgroup.Group, scfg *config.ScrapeConfig) error {
+ var targets []*scrape.Target
+ lb := labels.NewBuilder(labels.EmptyLabels())
for _, tg := range targetGroups {
- _, failures := scrape.TargetsFromGroup(tg, scfg, false)
+ var failures []error
+ targets, failures = scrape.TargetsFromGroup(tg, scfg, false, targets, lb)
if len(failures) > 0 {
first := failures[0]
return first
diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go
index 40df3e5248..6cfa48798e 100644
--- a/cmd/promtool/main_test.go
+++ b/cmd/promtool/main_test.go
@@ -14,6 +14,8 @@
package main
import (
+ "bytes"
+ "context"
"errors"
"fmt"
"net/http"
@@ -21,6 +23,7 @@ import (
"net/url"
"os"
"os/exec"
+ "path/filepath"
"runtime"
"strings"
"syscall"
@@ -56,14 +59,14 @@ func TestQueryRange(t *testing.T) {
require.Equal(t, nil, err)
p := &promqlPrinter{}
- exitCode := QueryRange(urlObject, map[string]string{}, "up", "0", "300", 0, p)
+ exitCode := QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", "0", "300", 0, p)
require.Equal(t, "/api/v1/query_range", getRequest().URL.Path)
form := getRequest().Form
require.Equal(t, "up", form.Get("query"))
require.Equal(t, "1", form.Get("step"))
require.Equal(t, 0, exitCode)
- exitCode = QueryRange(urlObject, map[string]string{}, "up", "0", "300", 10*time.Millisecond, p)
+ exitCode = QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", "0", "300", 10*time.Millisecond, p)
require.Equal(t, "/api/v1/query_range", getRequest().URL.Path)
form = getRequest().Form
require.Equal(t, "up", form.Get("query"))
@@ -79,7 +82,7 @@ func TestQueryInstant(t *testing.T) {
require.Equal(t, nil, err)
p := &promqlPrinter{}
- exitCode := QueryInstant(urlObject, "up", "300", p)
+ exitCode := QueryInstant(urlObject, http.DefaultTransport, "up", "300", p)
require.Equal(t, "/api/v1/query", getRequest().URL.Path)
form := getRequest().Form
require.Equal(t, "up", form.Get("query"))
@@ -433,3 +436,31 @@ func TestExitCodes(t *testing.T) {
})
}
}
+
+func TestDocumentation(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.SkipNow()
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ cmd := exec.CommandContext(ctx, promtoolPath, "-test.main", "write-documentation")
+
+ var stdout bytes.Buffer
+ cmd.Stdout = &stdout
+
+ if err := cmd.Run(); err != nil {
+ if exitError, ok := err.(*exec.ExitError); ok {
+ if exitError.ExitCode() != 0 {
+ fmt.Println("Command failed with non-zero exit code")
+ }
+ }
+ }
+
+ generatedContent := strings.ReplaceAll(stdout.String(), filepath.Base(promtoolPath), strings.TrimSuffix(filepath.Base(promtoolPath), ".test"))
+
+ expectedContent, err := os.ReadFile(filepath.Join("..", "..", "docs", "command-line", "promtool.md"))
+ require.NoError(t, err)
+
+ require.Equal(t, string(expectedContent), generatedContent, "Generated content does not match documentation. Hint: run `make cli-documentation`.")
+}
diff --git a/cmd/promtool/metrics.go b/cmd/promtool/metrics.go
new file mode 100644
index 0000000000..2bc2237e2f
--- /dev/null
+++ b/cmd/promtool/metrics.go
@@ -0,0 +1,138 @@
+// Copyright 2023 The Prometheus Authors
+// Licensed 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 main
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "time"
+
+ "github.com/golang/snappy"
+ config_util "github.com/prometheus/common/config"
+ "github.com/prometheus/common/model"
+
+ "github.com/prometheus/prometheus/storage/remote"
+ "github.com/prometheus/prometheus/util/fmtutil"
+)
+
+// Push metrics to a prometheus remote write (for testing purpose only).
+func PushMetrics(url *url.URL, roundTripper http.RoundTripper, headers map[string]string, timeout time.Duration, labels map[string]string, files ...string) int {
+ addressURL, err := url.Parse(url.String())
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ return failureExitCode
+ }
+
+ // build remote write client
+ writeClient, err := remote.NewWriteClient("remote-write", &remote.ClientConfig{
+ URL: &config_util.URL{URL: addressURL},
+ Timeout: model.Duration(timeout),
+ })
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ return failureExitCode
+ }
+
+ // set custom tls config from httpConfigFilePath
+ // set custom headers to every request
+ client, ok := writeClient.(*remote.Client)
+ if !ok {
+ fmt.Fprintln(os.Stderr, fmt.Errorf("unexpected type %T", writeClient))
+ return failureExitCode
+ }
+ client.Client.Transport = &setHeadersTransport{
+ RoundTripper: roundTripper,
+ headers: headers,
+ }
+
+ var data []byte
+ var failed bool
+
+ if len(files) == 0 {
+ data, err = io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, " FAILED:", err)
+ return failureExitCode
+ }
+ fmt.Printf("Parsing standard input\n")
+ if parseAndPushMetrics(client, data, labels) {
+ fmt.Printf(" SUCCESS: metrics pushed to remote write.\n")
+ return successExitCode
+ }
+ return failureExitCode
+ }
+
+ for _, file := range files {
+ data, err = os.ReadFile(file)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, " FAILED:", err)
+ failed = true
+ continue
+ }
+
+ fmt.Printf("Parsing metrics file %s\n", file)
+ if parseAndPushMetrics(client, data, labels) {
+ fmt.Printf(" SUCCESS: metrics file %s pushed to remote write.\n", file)
+ continue
+ }
+ failed = true
+ }
+
+ if failed {
+ return failureExitCode
+ }
+
+ return successExitCode
+}
+
+func parseAndPushMetrics(client *remote.Client, data []byte, labels map[string]string) bool {
+ metricsData, err := fmtutil.MetricTextToWriteRequest(bytes.NewReader(data), labels)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, " FAILED:", err)
+ return false
+ }
+
+ raw, err := metricsData.Marshal()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, " FAILED:", err)
+ return false
+ }
+
+ // Encode the request body into snappy encoding.
+ compressed := snappy.Encode(nil, raw)
+ err = client.Store(context.Background(), compressed)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, " FAILED:", err)
+ return false
+ }
+
+ return true
+}
+
+type setHeadersTransport struct {
+ http.RoundTripper
+ headers map[string]string
+}
+
+func (s *setHeadersTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ for key, value := range s.headers {
+ req.Header.Set(key, value)
+ }
+ return s.RoundTripper.RoundTrip(req)
+}
diff --git a/cmd/promtool/rules.go b/cmd/promtool/rules.go
index 4dbef34ebb..d8d6bb83e1 100644
--- a/cmd/promtool/rules.go
+++ b/cmd/promtool/rules.go
@@ -68,7 +68,7 @@ func newRuleImporter(logger log.Logger, config ruleImporterConfig, apiClient que
}
// loadGroups parses groups from a list of recording rule files.
-func (importer *ruleImporter) loadGroups(ctx context.Context, filenames []string) (errs []error) {
+func (importer *ruleImporter) loadGroups(_ context.Context, filenames []string) (errs []error) {
groups, errs := importer.ruleManager.LoadGroups(importer.config.evalInterval, labels.Labels{}, "", nil, filenames...)
if errs != nil {
return errs
@@ -100,7 +100,7 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName
startInMs := start.Unix() * int64(time.Second/time.Millisecond)
endInMs := end.Unix() * int64(time.Second/time.Millisecond)
- for startOfBlock := blockDuration * (startInMs / blockDuration); startOfBlock <= endInMs; startOfBlock = startOfBlock + blockDuration {
+ for startOfBlock := blockDuration * (startInMs / blockDuration); startOfBlock <= endInMs; startOfBlock += blockDuration {
endOfBlock := startOfBlock + blockDuration - 1
currStart := max(startOfBlock/int64(time.Second/time.Millisecond), start.Unix())
@@ -158,14 +158,15 @@ func (importer *ruleImporter) importRule(ctx context.Context, ruleExpr, ruleName
// Setting the rule labels after the output of the query,
// so they can override query output.
- for _, l := range ruleLabels {
+ ruleLabels.Range(func(l labels.Label) {
lb.Set(l.Name, l.Value)
- }
+ })
lb.Set(labels.MetricName, ruleName)
+ lbls := lb.Labels()
for _, value := range sample.Values {
- if err := app.add(ctx, lb.Labels(nil), timestamp.FromTime(value.Timestamp.Time()), float64(value.Value)); err != nil {
+ if err := app.add(ctx, lbls, timestamp.FromTime(value.Timestamp.Time()), float64(value.Value)); err != nil {
return fmt.Errorf("add: %w", err)
}
}
diff --git a/cmd/promtool/rules_test.go b/cmd/promtool/rules_test.go
index c7e9933fb4..213b7d2a01 100644
--- a/cmd/promtool/rules_test.go
+++ b/cmd/promtool/rules_test.go
@@ -28,13 +28,14 @@ import (
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/tsdb"
+ "github.com/prometheus/prometheus/tsdb/chunkenc"
)
type mockQueryRangeAPI struct {
samples model.Matrix
}
-func (mockAPI mockQueryRangeAPI) QueryRange(ctx context.Context, query string, r v1.Range, opts ...v1.Option) (model.Value, v1.Warnings, error) {
+func (mockAPI mockQueryRangeAPI) QueryRange(_ context.Context, query string, r v1.Range, opts ...v1.Option) (model.Value, v1.Warnings, error) { // nolint:revive
return mockAPI.samples, v1.Warnings{}, nil
}
@@ -99,7 +100,7 @@ func TestBackfillRuleIntegration(t *testing.T) {
require.Equal(t, 1, len(gRules))
require.Equal(t, "rule1", gRules[0].Name())
require.Equal(t, "ruleExpr", gRules[0].Query().String())
- require.Equal(t, 1, len(gRules[0].Labels()))
+ require.Equal(t, 1, gRules[0].Labels().Len())
group2 := ruleImporter.groups[path2+";group2"]
require.NotNil(t, group2)
@@ -108,7 +109,7 @@ func TestBackfillRuleIntegration(t *testing.T) {
require.Equal(t, 2, len(g2Rules))
require.Equal(t, "grp2_rule1", g2Rules[0].Name())
require.Equal(t, "grp2_rule1_expr", g2Rules[0].Query().String())
- require.Equal(t, 0, len(g2Rules[0].Labels()))
+ require.Equal(t, 0, g2Rules[0].Labels().Len())
// Backfill all recording rules then check the blocks to confirm the correct data was created.
errs = ruleImporter.importAll(ctx)
@@ -131,15 +132,15 @@ func TestBackfillRuleIntegration(t *testing.T) {
for selectedSeries.Next() {
seriesCount++
series := selectedSeries.At()
- if len(series.Labels()) != 3 {
- require.Equal(t, 2, len(series.Labels()))
+ if series.Labels().Len() != 3 {
+ require.Equal(t, 2, series.Labels().Len())
x := labels.FromStrings("__name__", "grp2_rule1", "name1", "val1")
require.Equal(t, x, series.Labels())
} else {
- require.Equal(t, 3, len(series.Labels()))
+ require.Equal(t, 3, series.Labels().Len())
}
- it := series.Iterator()
- for it.Next() {
+ it := series.Iterator(nil)
+ for it.Next() == chunkenc.ValFloat {
samplesCount++
ts, v := it.At()
if v == testValue {
@@ -160,7 +161,7 @@ func TestBackfillRuleIntegration(t *testing.T) {
}
}
-func newTestRuleImporter(ctx context.Context, start time.Time, tmpDir string, testSamples model.Matrix, maxBlockDuration time.Duration) (*ruleImporter, error) {
+func newTestRuleImporter(_ context.Context, start time.Time, tmpDir string, testSamples model.Matrix, maxBlockDuration time.Duration) (*ruleImporter, error) {
logger := log.NewNopLogger()
cfg := ruleImporterConfig{
outputDir: tmpDir,
diff --git a/cmd/promtool/sd.go b/cmd/promtool/sd.go
index 981a67af19..7c5ae70365 100644
--- a/cmd/promtool/sd.go
+++ b/cmd/promtool/sd.go
@@ -47,9 +47,15 @@ func CheckSD(sdConfigFiles, sdJobName string, sdTimeout time.Duration, noDefault
}
var scrapeConfig *config.ScrapeConfig
+ scfgs, err := cfg.GetScrapeConfigs()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Cannot load scrape configs", err)
+ return failureExitCode
+ }
+
jobs := []string{}
jobMatched := false
- for _, v := range cfg.ScrapeConfigs {
+ for _, v := range scfgs {
jobs = append(jobs, v.JobName)
if v.JobName == sdJobName {
jobMatched = true
@@ -109,22 +115,22 @@ outerLoop:
func getSDCheckResult(targetGroups []*targetgroup.Group, scrapeConfig *config.ScrapeConfig, noDefaultScrapePort bool) []sdCheckResult {
sdCheckResults := []sdCheckResult{}
+ lb := labels.NewBuilder(labels.EmptyLabels())
for _, targetGroup := range targetGroups {
for _, target := range targetGroup.Targets {
- labelSlice := make([]labels.Label, 0, len(target)+len(targetGroup.Labels))
+ lb.Reset(labels.EmptyLabels())
for name, value := range target {
- labelSlice = append(labelSlice, labels.Label{Name: string(name), Value: string(value)})
+ lb.Set(string(name), string(value))
}
for name, value := range targetGroup.Labels {
if _, ok := target[name]; !ok {
- labelSlice = append(labelSlice, labels.Label{Name: string(name), Value: string(value)})
+ lb.Set(string(name), string(value))
}
}
- targetLabels := labels.New(labelSlice...)
- res, orig, err := scrape.PopulateLabels(targetLabels, scrapeConfig, noDefaultScrapePort)
+ res, orig, err := scrape.PopulateLabels(lb, scrapeConfig, noDefaultScrapePort)
result := sdCheckResult{
DiscoveredLabels: orig,
Labels: res,
diff --git a/cmd/promtool/tsdb.go b/cmd/promtool/tsdb.go
index 7c7c8f6ec0..4e27f69c05 100644
--- a/cmd/promtool/tsdb.go
+++ b/cmd/promtool/tsdb.go
@@ -23,24 +23,25 @@ import (
"path/filepath"
"runtime"
"runtime/pprof"
- "sort"
"strconv"
"strings"
"sync"
"text/tabwriter"
"time"
- "github.com/prometheus/prometheus/storage"
- "github.com/prometheus/prometheus/tsdb/index"
-
"github.com/alecthomas/units"
"github.com/go-kit/log"
+ "golang.org/x/exp/slices"
"github.com/prometheus/prometheus/model/labels"
+ "github.com/prometheus/prometheus/promql/parser"
+ "github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb"
+ "github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/chunks"
tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
"github.com/prometheus/prometheus/tsdb/fileutil"
+ "github.com/prometheus/prometheus/tsdb/index"
)
const timeDelta = 30000
@@ -314,7 +315,7 @@ func readPrometheusLabels(r io.Reader, n int) ([]labels.Labels, error) {
i := 0
for scanner.Scan() && i < n {
- m := make(labels.Labels, 0, 10)
+ m := make([]labels.Label, 0, 10)
r := strings.NewReplacer("\"", "", "{", "", "}", "")
s := r.Replace(scanner.Text())
@@ -324,13 +325,12 @@ func readPrometheusLabels(r io.Reader, n int) ([]labels.Labels, error) {
split := strings.Split(labelChunk, ":")
m = append(m, labels.Label{Name: split[0], Value: split[1]})
}
- // Order of the k/v labels matters, don't assume we'll always receive them already sorted.
- sort.Sort(m)
- h := m.Hash()
+ ml := labels.New(m...) // This sorts by name - order of the k/v labels matters, don't assume we'll always receive them already sorted.
+ h := ml.Hash()
if _, ok := hashes[h]; ok {
continue
}
- mets = append(mets, m)
+ mets = append(mets, ml)
hashes[h] = struct{}{}
i++
}
@@ -397,25 +397,20 @@ func openBlock(path, blockID string) (*tsdb.DBReadOnly, tsdb.BlockReader, error)
if err != nil {
return nil, nil, err
}
- blocks, err := db.Blocks()
+
+ if blockID == "" {
+ blockID, err = db.LastBlockID()
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ b, err := db.Block(blockID)
if err != nil {
return nil, nil, err
}
- var block tsdb.BlockReader
- if blockID != "" {
- for _, b := range blocks {
- if b.Meta().ULID.String() == blockID {
- block = b
- break
- }
- }
- } else if len(blocks) > 0 {
- block = blocks[len(blocks)-1]
- }
- if block == nil {
- return nil, nil, fmt.Errorf("block %s not found", blockID)
- }
- return db, block, nil
+
+ return db, b, nil
}
func analyzeBlock(path, blockID string, limit int, runExtended bool) error {
@@ -451,7 +446,7 @@ func analyzeBlock(path, blockID string, limit int, runExtended bool) error {
postingInfos := []postingInfo{}
printInfo := func(postingInfos []postingInfo) {
- sort.Slice(postingInfos, func(i, j int) bool { return postingInfos[i].metric > postingInfos[j].metric })
+ slices.SortFunc(postingInfos, func(a, b postingInfo) bool { return a.metric > b.metric })
for i, pc := range postingInfos {
if i >= limit {
@@ -469,21 +464,21 @@ func analyzeBlock(path, blockID string, limit int, runExtended bool) error {
if err != nil {
return err
}
- lbls := labels.Labels{}
chks := []chunks.Meta{}
+ builder := labels.ScratchBuilder{}
for p.Next() {
- if err = ir.Series(p.At(), &lbls, &chks); err != nil {
+ if err = ir.Series(p.At(), &builder, &chks); err != nil {
return err
}
// Amount of the block time range not covered by this series.
uncovered := uint64(meta.MaxTime-meta.MinTime) - uint64(chks[len(chks)-1].MaxTime-chks[0].MinTime)
- for _, lbl := range lbls {
+ builder.Labels().Range(func(lbl labels.Label) {
key := lbl.Name + "=" + lbl.Value
labelsUncovered[lbl.Name] += uncovered
labelpairsUncovered[key] += uncovered
labelpairsCount[key]++
entries++
- }
+ })
}
if p.Err() != nil {
return p.Err()
@@ -588,10 +583,10 @@ func analyzeCompaction(block tsdb.BlockReader, indexr tsdb.IndexReader) (err err
nBuckets := 10
histogram := make([]int, nBuckets)
totalChunks := 0
+ var builder labels.ScratchBuilder
for postingsr.Next() {
- lbsl := labels.Labels{}
var chks []chunks.Meta
- if err := indexr.Series(postingsr.At(), &lbsl, &chks); err != nil {
+ if err := indexr.Series(postingsr.At(), &builder, &chks); err != nil {
return err
}
@@ -624,7 +619,7 @@ func analyzeCompaction(block tsdb.BlockReader, indexr tsdb.IndexReader) (err err
return nil
}
-func dumpSamples(path string, mint, maxt int64) (err error) {
+func dumpSamples(path string, mint, maxt int64, match string) (err error) {
db, err := tsdb.OpenDBReadOnly(path, nil)
if err != nil {
return err
@@ -638,13 +633,17 @@ func dumpSamples(path string, mint, maxt int64) (err error) {
}
defer q.Close()
- ss := q.Select(false, nil, labels.MustNewMatcher(labels.MatchRegexp, "", ".*"))
+ matchers, err := parser.ParseMetricSelector(match)
+ if err != nil {
+ return err
+ }
+ ss := q.Select(false, nil, matchers...)
for ss.Next() {
series := ss.At()
lbs := series.Labels()
- it := series.Iterator()
- for it.Next() {
+ it := series.Iterator(nil)
+ for it.Next() == chunkenc.ValFloat {
ts, val := it.At()
fmt.Printf("%s %g %d\n", lbs, val, ts)
}
diff --git a/cmd/promtool/unittest.go b/cmd/promtool/unittest.go
index 744235ddae..e934f37c85 100644
--- a/cmd/promtool/unittest.go
+++ b/cmd/promtool/unittest.go
@@ -130,7 +130,7 @@ func resolveAndGlobFilepaths(baseDir string, utf *unitTestFile) error {
if err != nil {
return err
}
- if len(m) <= 0 {
+ if len(m) == 0 {
fmt.Fprintln(os.Stderr, " WARNING: no file match pattern", rf)
}
globbedFiles = append(globbedFiles, m...)
@@ -284,8 +284,8 @@ func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]i
for _, a := range ar.ActiveAlerts() {
if a.State == rules.StateFiring {
alerts = append(alerts, labelAndAnnotation{
- Labels: append(labels.Labels{}, a.Labels...),
- Annotations: append(labels.Labels{}, a.Annotations...),
+ Labels: a.Labels.Copy(),
+ Annotations: a.Annotations.Copy(),
})
}
}
@@ -347,7 +347,7 @@ Outer:
for _, s := range got {
gotSamples = append(gotSamples, parsedSample{
Labels: s.Metric.Copy(),
- Value: s.V,
+ Value: s.F,
})
}
@@ -434,7 +434,7 @@ func (tg *testGroup) maxEvalTime() time.Duration {
}
func query(ctx context.Context, qs string, t time.Time, engine *promql.Engine, qu storage.Queryable) (promql.Vector, error) {
- q, err := engine.NewInstantQuery(qu, nil, qs, t)
+ q, err := engine.NewInstantQuery(ctx, qu, nil, qs, t)
if err != nil {
return nil, err
}
@@ -447,7 +447,8 @@ func query(ctx context.Context, qs string, t time.Time, engine *promql.Engine, q
return v, nil
case promql.Scalar:
return promql.Vector{promql.Sample{
- Point: promql.Point(v),
+ T: v.T,
+ F: v.V,
Metric: labels.Labels{},
}}, nil
default:
diff --git a/config/config.go b/config/config.go
index a13f397f81..d32fcc33c9 100644
--- a/config/config.go
+++ b/config/config.go
@@ -34,6 +34,7 @@ import (
"github.com/prometheus/prometheus/discovery"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/relabel"
+ "github.com/prometheus/prometheus/storage/remote/azuread"
)
var (
@@ -80,7 +81,8 @@ func Load(s string, expandExternalLabels bool, logger log.Logger) (*Config, erro
return cfg, nil
}
- for i, v := range cfg.GlobalConfig.ExternalLabels {
+ b := labels.ScratchBuilder{}
+ cfg.GlobalConfig.ExternalLabels.Range(func(v labels.Label) {
newV := os.Expand(v.Value, func(s string) string {
if s == "$" {
return "$"
@@ -93,10 +95,10 @@ func Load(s string, expandExternalLabels bool, logger log.Logger) (*Config, erro
})
if newV != v.Value {
level.Debug(logger).Log("msg", "External label replaced", "label", v.Name, "input", v.Value, "output", newV)
- v.Value = newV
- cfg.GlobalConfig.ExternalLabels[i] = v
}
- }
+ b.Add(v.Name, newV)
+ })
+ cfg.GlobalConfig.ExternalLabels = b.Labels()
return cfg, nil
}
@@ -112,10 +114,6 @@ func LoadFile(filename string, agentMode, expandExternalLabels bool, logger log.
}
if agentMode {
- if len(cfg.RemoteWriteConfigs) == 0 {
- return nil, errors.New("at least one remote_write target must be specified in agent mode")
- }
-
if len(cfg.AlertingConfig.AlertmanagerConfigs) > 0 || len(cfg.AlertingConfig.AlertRelabelConfigs) > 0 {
return nil, errors.New("field alerting is not allowed in agent mode")
}
@@ -149,13 +147,14 @@ var (
// DefaultScrapeConfig is the default scrape configuration.
DefaultScrapeConfig = ScrapeConfig{
- // ScrapeTimeout and ScrapeInterval default to the
- // configured globals.
- MetricsPath: "/metrics",
- Scheme: "http",
- HonorLabels: false,
- HonorTimestamps: true,
- HTTPClientConfig: config.DefaultHTTPClientConfig,
+ // ScrapeTimeout and ScrapeInterval default to the configured
+ // globals.
+ ScrapeClassicHistograms: false,
+ MetricsPath: "/metrics",
+ Scheme: "http",
+ HonorLabels: false,
+ HonorTimestamps: true,
+ HTTPClientConfig: config.DefaultHTTPClientConfig,
}
// DefaultAlertmanagerConfig is the default alertmanager configuration.
@@ -176,16 +175,16 @@ var (
// DefaultQueueConfig is the default remote queue configuration.
DefaultQueueConfig = QueueConfig{
- // With a maximum of 200 shards, assuming an average of 100ms remote write
- // time and 500 samples per batch, we will be able to push 1M samples/s.
- MaxShards: 200,
+ // With a maximum of 50 shards, assuming an average of 100ms remote write
+ // time and 2000 samples per batch, we will be able to push 1M samples/s.
+ MaxShards: 50,
MinShards: 1,
- MaxSamplesPerSend: 500,
+ MaxSamplesPerSend: 2000,
- // Each shard will have a max of 2500 samples pending in its channel, plus the pending
- // samples that have been enqueued. Theoretically we should only ever have about 3000 samples
- // per shard pending. At 200 shards that's 600k.
- Capacity: 2500,
+ // Each shard will have a max of 10,000 samples pending in its channel, plus the pending
+ // samples that have been enqueued. Theoretically we should only ever have about 12,000 samples
+ // per shard pending. At 50 shards that's 600k.
+ Capacity: 10000,
BatchSendDeadline: model.Duration(5 * time.Second),
// Backoff times for retrying a batch of samples on recoverable errors.
@@ -197,7 +196,7 @@ var (
DefaultMetadataConfig = MetadataConfig{
Send: true,
SendInterval: model.Duration(1 * time.Minute),
- MaxSamplesPerSend: 500,
+ MaxSamplesPerSend: 2000,
}
// DefaultRemoteReadConfig is the default remote read configuration.
@@ -219,12 +218,13 @@ var (
// Config is the top-level configuration for Prometheus's config files.
type Config struct {
- GlobalConfig GlobalConfig `yaml:"global"`
- AlertingConfig AlertingConfig `yaml:"alerting,omitempty"`
- RuleFiles []string `yaml:"rule_files,omitempty"`
- ScrapeConfigs []*ScrapeConfig `yaml:"scrape_configs,omitempty"`
- StorageConfig StorageConfig `yaml:"storage,omitempty"`
- TracingConfig TracingConfig `yaml:"tracing,omitempty"`
+ GlobalConfig GlobalConfig `yaml:"global"`
+ AlertingConfig AlertingConfig `yaml:"alerting,omitempty"`
+ RuleFiles []string `yaml:"rule_files,omitempty"`
+ ScrapeConfigFiles []string `yaml:"scrape_config_files,omitempty"`
+ ScrapeConfigs []*ScrapeConfig `yaml:"scrape_configs,omitempty"`
+ StorageConfig StorageConfig `yaml:"storage,omitempty"`
+ TracingConfig TracingConfig `yaml:"tracing,omitempty"`
RemoteWriteConfigs []*RemoteWriteConfig `yaml:"remote_write,omitempty"`
RemoteReadConfigs []*RemoteReadConfig `yaml:"remote_read,omitempty"`
@@ -238,6 +238,9 @@ func (c *Config) SetDirectory(dir string) {
for i, file := range c.RuleFiles {
c.RuleFiles[i] = config.JoinDir(dir, file)
}
+ for i, file := range c.ScrapeConfigFiles {
+ c.ScrapeConfigFiles[i] = config.JoinDir(dir, file)
+ }
for _, c := range c.ScrapeConfigs {
c.SetDirectory(dir)
}
@@ -257,6 +260,58 @@ func (c Config) String() string {
return string(b)
}
+// ScrapeConfigs returns the scrape configurations.
+func (c *Config) GetScrapeConfigs() ([]*ScrapeConfig, error) {
+ scfgs := make([]*ScrapeConfig, len(c.ScrapeConfigs))
+
+ jobNames := map[string]string{}
+ for i, scfg := range c.ScrapeConfigs {
+ // We do these checks for library users that would not call Validate in
+ // Unmarshal.
+ if err := scfg.Validate(c.GlobalConfig); err != nil {
+ return nil, err
+ }
+
+ if _, ok := jobNames[scfg.JobName]; ok {
+ return nil, fmt.Errorf("found multiple scrape configs with job name %q", scfg.JobName)
+ }
+ jobNames[scfg.JobName] = "main config file"
+ scfgs[i] = scfg
+ }
+ for _, pat := range c.ScrapeConfigFiles {
+ fs, err := filepath.Glob(pat)
+ if err != nil {
+ // The only error can be a bad pattern.
+ return nil, fmt.Errorf("error retrieving scrape config files for %q: %w", pat, err)
+ }
+ for _, filename := range fs {
+ cfg := ScrapeConfigs{}
+ content, err := os.ReadFile(filename)
+ if err != nil {
+ return nil, fileErr(filename, err)
+ }
+ err = yaml.UnmarshalStrict(content, &cfg)
+ if err != nil {
+ return nil, fileErr(filename, err)
+ }
+ for _, scfg := range cfg.ScrapeConfigs {
+ if err := scfg.Validate(c.GlobalConfig); err != nil {
+ return nil, fileErr(filename, err)
+ }
+
+ if f, ok := jobNames[scfg.JobName]; ok {
+ return nil, fileErr(filename, fmt.Errorf("found multiple scrape configs with job name %q, first found in %s", scfg.JobName, f))
+ }
+ jobNames[scfg.JobName] = fmt.Sprintf("%q", filePath(filename))
+
+ scfg.SetDirectory(filepath.Dir(filename))
+ scfgs = append(scfgs, scfg)
+ }
+ }
+ }
+ return scfgs, nil
+}
+
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultConfig
@@ -279,26 +334,18 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
return fmt.Errorf("invalid rule file path %q", rf)
}
}
+
+ for _, sf := range c.ScrapeConfigFiles {
+ if !patRulePath.MatchString(sf) {
+ return fmt.Errorf("invalid scrape config file path %q", sf)
+ }
+ }
+
// Do global overrides and validate unique names.
jobNames := map[string]struct{}{}
for _, scfg := range c.ScrapeConfigs {
- if scfg == nil {
- return errors.New("empty or null scrape config section")
- }
- // First set the correct scrape interval, then check that the timeout
- // (inferred or explicit) is not greater than that.
- if scfg.ScrapeInterval == 0 {
- scfg.ScrapeInterval = c.GlobalConfig.ScrapeInterval
- }
- if scfg.ScrapeTimeout > scfg.ScrapeInterval {
- return fmt.Errorf("scrape timeout greater than scrape interval for scrape config with job name %q", scfg.JobName)
- }
- if scfg.ScrapeTimeout == 0 {
- if c.GlobalConfig.ScrapeTimeout > scfg.ScrapeInterval {
- scfg.ScrapeTimeout = scfg.ScrapeInterval
- } else {
- scfg.ScrapeTimeout = c.GlobalConfig.ScrapeTimeout
- }
+ if err := scfg.Validate(c.GlobalConfig); err != nil {
+ return err
}
if _, ok := jobNames[scfg.JobName]; ok {
@@ -344,6 +391,24 @@ type GlobalConfig struct {
QueryLogFile string `yaml:"query_log_file,omitempty"`
// The labels to add to any timeseries that this Prometheus instance scrapes.
ExternalLabels labels.Labels `yaml:"external_labels,omitempty"`
+ // An uncompressed response body larger than this many bytes will cause the
+ // scrape to fail. 0 means no limit.
+ BodySizeLimit units.Base2Bytes `yaml:"body_size_limit,omitempty"`
+ // More than this many samples post metric-relabeling will cause the scrape to
+ // fail. 0 means no limit.
+ SampleLimit uint `yaml:"sample_limit,omitempty"`
+ // More than this many targets after the target relabeling will cause the
+ // scrapes to fail. 0 means no limit.
+ TargetLimit uint `yaml:"target_limit,omitempty"`
+ // More than this many labels post metric-relabeling will cause the scrape to
+ // fail. 0 means no limit.
+ LabelLimit uint `yaml:"label_limit,omitempty"`
+ // More than this label name length post metric-relabeling will cause the
+ // scrape to fail. 0 means no limit.
+ LabelNameLengthLimit uint `yaml:"label_name_length_limit,omitempty"`
+ // More than this label value length post metric-relabeling will cause the
+ // scrape to fail. 0 means no limit.
+ LabelValueLengthLimit uint `yaml:"label_value_length_limit,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
@@ -361,13 +426,16 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
- for _, l := range gc.ExternalLabels {
+ if err := gc.ExternalLabels.Validate(func(l labels.Label) error {
if !model.LabelName(l.Name).IsValid() {
return fmt.Errorf("%q is not a valid label name", l.Name)
}
if !model.LabelValue(l.Value).IsValid() {
return fmt.Errorf("%q is not a valid label value", l.Value)
}
+ return nil
+ }); err != nil {
+ return err
}
// First set the correct scrape interval, then check that the timeout
@@ -394,13 +462,17 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
// isZero returns true iff the global config is the zero value.
func (c *GlobalConfig) isZero() bool {
- return c.ExternalLabels == nil &&
+ return c.ExternalLabels.IsEmpty() &&
c.ScrapeInterval == 0 &&
c.ScrapeTimeout == 0 &&
c.EvaluationInterval == 0 &&
c.QueryLogFile == ""
}
+type ScrapeConfigs struct {
+ ScrapeConfigs []*ScrapeConfig `yaml:"scrape_configs,omitempty"`
+}
+
// ScrapeConfig configures a scraping unit for Prometheus.
type ScrapeConfig struct {
// The job name to which the job label is set by default.
@@ -415,6 +487,8 @@ type ScrapeConfig struct {
ScrapeInterval model.Duration `yaml:"scrape_interval,omitempty"`
// The timeout for scraping targets of this config.
ScrapeTimeout model.Duration `yaml:"scrape_timeout,omitempty"`
+ // Whether to scrape a classic histogram that is also exposed as a native histogram.
+ ScrapeClassicHistograms bool `yaml:"scrape_classic_histograms,omitempty"`
// The HTTP resource path on which to fetch metrics from targets.
MetricsPath string `yaml:"metrics_path,omitempty"`
// The URL scheme with which to fetch metrics from targets.
@@ -423,20 +497,23 @@ type ScrapeConfig struct {
// scrape to fail. 0 means no limit.
BodySizeLimit units.Base2Bytes `yaml:"body_size_limit,omitempty"`
// More than this many samples post metric-relabeling will cause the scrape to
- // fail.
+ // fail. 0 means no limit.
SampleLimit uint `yaml:"sample_limit,omitempty"`
// More than this many targets after the target relabeling will cause the
- // scrapes to fail.
+ // scrapes to fail. 0 means no limit.
TargetLimit uint `yaml:"target_limit,omitempty"`
// More than this many labels post metric-relabeling will cause the scrape to
- // fail.
+ // fail. 0 means no limit.
LabelLimit uint `yaml:"label_limit,omitempty"`
// More than this label name length post metric-relabeling will cause the
- // scrape to fail.
+ // scrape to fail. 0 means no limit.
LabelNameLengthLimit uint `yaml:"label_name_length_limit,omitempty"`
// More than this label value length post metric-relabeling will cause the
- // scrape to fail.
+ // scrape to fail. 0 means no limit.
LabelValueLengthLimit uint `yaml:"label_value_length_limit,omitempty"`
+ // More than this many buckets in a native histogram will cause the scrape to
+ // fail.
+ NativeHistogramBucketLimit uint `yaml:"native_histogram_bucket_limit,omitempty"`
// We cannot do proper Go type embedding below as the parser will then parse
// values arbitrarily into the overflow maps of further-down types.
@@ -494,6 +571,47 @@ func (c *ScrapeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
+func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
+ if c == nil {
+ return errors.New("empty or null scrape config section")
+ }
+ // First set the correct scrape interval, then check that the timeout
+ // (inferred or explicit) is not greater than that.
+ if c.ScrapeInterval == 0 {
+ c.ScrapeInterval = globalConfig.ScrapeInterval
+ }
+ if c.ScrapeTimeout > c.ScrapeInterval {
+ return fmt.Errorf("scrape timeout greater than scrape interval for scrape config with job name %q", c.JobName)
+ }
+ if c.ScrapeTimeout == 0 {
+ if globalConfig.ScrapeTimeout > c.ScrapeInterval {
+ c.ScrapeTimeout = c.ScrapeInterval
+ } else {
+ c.ScrapeTimeout = globalConfig.ScrapeTimeout
+ }
+ }
+ if c.BodySizeLimit == 0 {
+ c.BodySizeLimit = globalConfig.BodySizeLimit
+ }
+ if c.SampleLimit == 0 {
+ c.SampleLimit = globalConfig.SampleLimit
+ }
+ if c.TargetLimit == 0 {
+ c.TargetLimit = globalConfig.TargetLimit
+ }
+ if c.LabelLimit == 0 {
+ c.LabelLimit = globalConfig.LabelLimit
+ }
+ if c.LabelNameLengthLimit == 0 {
+ c.LabelNameLengthLimit = globalConfig.LabelNameLengthLimit
+ }
+ if c.LabelValueLengthLimit == 0 {
+ c.LabelValueLengthLimit = globalConfig.LabelValueLengthLimit
+ }
+
+ return nil
+}
+
// MarshalYAML implements the yaml.Marshaler interface.
func (c *ScrapeConfig) MarshalYAML() (interface{}, error) {
return discovery.MarshalYAMLWithInlineConfigs(c)
@@ -776,12 +894,13 @@ func CheckTargetAddress(address model.LabelValue) error {
// RemoteWriteConfig is the configuration for writing to remote storage.
type RemoteWriteConfig struct {
- URL *config.URL `yaml:"url"`
- RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
- Headers map[string]string `yaml:"headers,omitempty"`
- WriteRelabelConfigs []*relabel.Config `yaml:"write_relabel_configs,omitempty"`
- Name string `yaml:"name,omitempty"`
- SendExemplars bool `yaml:"send_exemplars,omitempty"`
+ URL *config.URL `yaml:"url"`
+ RemoteTimeout model.Duration `yaml:"remote_timeout,omitempty"`
+ Headers map[string]string `yaml:"headers,omitempty"`
+ WriteRelabelConfigs []*relabel.Config `yaml:"write_relabel_configs,omitempty"`
+ Name string `yaml:"name,omitempty"`
+ SendExemplars bool `yaml:"send_exemplars,omitempty"`
+ SendNativeHistograms bool `yaml:"send_native_histograms,omitempty"`
// We cannot do proper Go type embedding below as the parser will then parse
// values arbitrarily into the overflow maps of further-down types.
@@ -789,6 +908,7 @@ type RemoteWriteConfig struct {
QueueConfig QueueConfig `yaml:"queue_config,omitempty"`
MetadataConfig MetadataConfig `yaml:"metadata_config,omitempty"`
SigV4Config *sigv4.SigV4Config `yaml:"sigv4,omitempty"`
+ AzureADConfig *azuread.AzureADConfig `yaml:"azuread,omitempty"`
}
// SetDirectory joins any relative file paths with dir.
@@ -825,8 +945,12 @@ func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
httpClientConfigAuthEnabled := c.HTTPClientConfig.BasicAuth != nil ||
c.HTTPClientConfig.Authorization != nil || c.HTTPClientConfig.OAuth2 != nil
- if httpClientConfigAuthEnabled && c.SigV4Config != nil {
- return fmt.Errorf("at most one of basic_auth, authorization, oauth2, & sigv4 must be configured")
+ if httpClientConfigAuthEnabled && (c.SigV4Config != nil || c.AzureADConfig != nil) {
+ return fmt.Errorf("at most one of basic_auth, authorization, oauth2, sigv4, & azuread must be configured")
+ }
+
+ if c.SigV4Config != nil && c.AzureADConfig != nil {
+ return fmt.Errorf("at most one of basic_auth, authorization, oauth2, sigv4, & azuread must be configured")
}
return nil
@@ -847,7 +971,7 @@ func validateHeadersForTracing(headers map[string]string) error {
func validateHeaders(headers map[string]string) error {
for header := range headers {
if strings.ToLower(header) == "authorization" {
- return errors.New("authorization header must be changed via the basic_auth, authorization, oauth2, or sigv4 parameter")
+ return errors.New("authorization header must be changed via the basic_auth, authorization, oauth2, sigv4, or azuread parameter")
}
if _, ok := reservedHeaders[strings.ToLower(header)]; ok {
return fmt.Errorf("%s is a reserved header. It must not be changed", header)
@@ -935,3 +1059,15 @@ func (c *RemoteReadConfig) UnmarshalYAML(unmarshal func(interface{}) error) erro
// Thus we just do its validation here.
return c.HTTPClientConfig.Validate()
}
+
+func filePath(filename string) string {
+ absPath, err := filepath.Abs(filename)
+ if err != nil {
+ return filename
+ }
+ return absPath
+}
+
+func fileErr(filename string, err error) error {
+ return fmt.Errorf("%q: %w", filePath(filename), err)
+}
diff --git a/config/config_test.go b/config/config_test.go
index 9cab2f9e05..d3288cc90d 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -47,6 +47,7 @@ import (
"github.com/prometheus/prometheus/discovery/moby"
"github.com/prometheus/prometheus/discovery/nomad"
"github.com/prometheus/prometheus/discovery/openstack"
+ "github.com/prometheus/prometheus/discovery/ovhcloud"
"github.com/prometheus/prometheus/discovery/puppetdb"
"github.com/prometheus/prometheus/discovery/scaleway"
"github.com/prometheus/prometheus/discovery/targetgroup"
@@ -67,6 +68,15 @@ func mustParseURL(u string) *config.URL {
return &config.URL{URL: parsed}
}
+const (
+ globBodySizeLimit = 15 * units.MiB
+ globSampleLimit = 1500
+ globTargetLimit = 30
+ globLabelLimit = 30
+ globLabelNameLengthLimit = 200
+ globLabelValueLengthLimit = 200
+)
+
var expectedConf = &Config{
GlobalConfig: GlobalConfig{
ScrapeInterval: model.Duration(15 * time.Second),
@@ -75,6 +85,13 @@ var expectedConf = &Config{
QueryLogFile: "",
ExternalLabels: labels.FromStrings("foo", "bar", "monitor", "codelab"),
+
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
},
RuleFiles: []string{
@@ -164,10 +181,16 @@ var expectedConf = &Config{
{
JobName: "prometheus",
- HonorLabels: true,
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorLabels: true,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -216,36 +239,59 @@ var expectedConf = &Config{
Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
Replacement: "foo-${1}",
Action: relabel.Replace,
- }, {
+ },
+ {
SourceLabels: model.LabelNames{"abc"},
TargetLabel: "cde",
Separator: ";",
Regex: relabel.DefaultRelabelConfig.Regex,
Replacement: relabel.DefaultRelabelConfig.Replacement,
Action: relabel.Replace,
- }, {
+ },
+ {
TargetLabel: "abc",
Separator: ";",
Regex: relabel.DefaultRelabelConfig.Regex,
Replacement: "static",
Action: relabel.Replace,
- }, {
+ },
+ {
TargetLabel: "abc",
Separator: ";",
Regex: relabel.MustNewRegexp(""),
Replacement: "static",
Action: relabel.Replace,
},
+ {
+ SourceLabels: model.LabelNames{"foo"},
+ TargetLabel: "abc",
+ Action: relabel.KeepEqual,
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Separator: relabel.DefaultRelabelConfig.Separator,
+ },
+ {
+ SourceLabels: model.LabelNames{"foo"},
+ TargetLabel: "abc",
+ Action: relabel.DropEqual,
+ Regex: relabel.DefaultRelabelConfig.Regex,
+ Replacement: relabel.DefaultRelabelConfig.Replacement,
+ Separator: relabel.DefaultRelabelConfig.Separator,
+ },
},
},
{
JobName: "service-x",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(50 * time.Second),
- ScrapeTimeout: model.Duration(5 * time.Second),
- BodySizeLimit: 10 * units.MiB,
- SampleLimit: 1000,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(50 * time.Second),
+ ScrapeTimeout: model.Duration(5 * time.Second),
+ BodySizeLimit: 10 * units.MiB,
+ SampleLimit: 1000,
+ TargetLimit: 35,
+ LabelLimit: 35,
+ LabelNameLengthLimit: 210,
+ LabelValueLengthLimit: 210,
HTTPClientConfig: config.HTTPClientConfig{
BasicAuth: &config.BasicAuth{
@@ -332,9 +378,15 @@ var expectedConf = &Config{
{
JobName: "service-y",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -343,6 +395,7 @@ var expectedConf = &Config{
ServiceDiscoveryConfigs: discovery.Configs{
&consul.SDConfig{
Server: "localhost:1234",
+ PathPrefix: "/consul",
Token: "mysecret",
Services: []string{"nginx", "cache", "mysql"},
ServiceTags: []string{"canary", "v1"},
@@ -378,9 +431,15 @@ var expectedConf = &Config{
{
JobName: "service-z",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: model.Duration(10 * time.Second),
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: model.Duration(10 * time.Second),
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: "/metrics",
Scheme: "http",
@@ -403,9 +462,15 @@ var expectedConf = &Config{
{
JobName: "service-kubernetes",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -434,9 +499,15 @@ var expectedConf = &Config{
{
JobName: "service-kubernetes-namespaces",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -465,9 +536,15 @@ var expectedConf = &Config{
{
JobName: "service-kuma",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -485,9 +562,15 @@ var expectedConf = &Config{
{
JobName: "service-marathon",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -514,9 +597,15 @@ var expectedConf = &Config{
{
JobName: "service-nomad",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -540,9 +629,15 @@ var expectedConf = &Config{
{
JobName: "service-ec2",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -566,15 +661,22 @@ var expectedConf = &Config{
Values: []string{"web", "db"},
},
},
+ HTTPClientConfig: config.DefaultHTTPClientConfig,
},
},
},
{
JobName: "service-lightsail",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -582,21 +684,28 @@ var expectedConf = &Config{
ServiceDiscoveryConfigs: discovery.Configs{
&aws.LightsailSDConfig{
- Region: "us-east-1",
- AccessKey: "access",
- SecretKey: "mysecret",
- Profile: "profile",
- RefreshInterval: model.Duration(60 * time.Second),
- Port: 80,
+ Region: "us-east-1",
+ AccessKey: "access",
+ SecretKey: "mysecret",
+ Profile: "profile",
+ RefreshInterval: model.Duration(60 * time.Second),
+ Port: 80,
+ HTTPClientConfig: config.DefaultHTTPClientConfig,
},
},
},
{
JobName: "service-azure",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -620,9 +729,15 @@ var expectedConf = &Config{
{
JobName: "service-nerve",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -639,9 +754,15 @@ var expectedConf = &Config{
{
JobName: "0123service-xxx",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -661,9 +782,15 @@ var expectedConf = &Config{
{
JobName: "badfederation",
- HonorTimestamps: false,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: false,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: "/federate",
Scheme: DefaultScrapeConfig.Scheme,
@@ -683,9 +810,15 @@ var expectedConf = &Config{
{
JobName: "測試",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -705,9 +838,15 @@ var expectedConf = &Config{
{
JobName: "httpsd",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -724,9 +863,15 @@ var expectedConf = &Config{
{
JobName: "service-triton",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -751,9 +896,15 @@ var expectedConf = &Config{
{
JobName: "digitalocean-droplets",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -777,9 +928,15 @@ var expectedConf = &Config{
{
JobName: "docker",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -799,9 +956,15 @@ var expectedConf = &Config{
{
JobName: "dockerswarm",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -821,9 +984,15 @@ var expectedConf = &Config{
{
JobName: "service-openstack",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -847,9 +1016,15 @@ var expectedConf = &Config{
{
JobName: "service-puppetdb",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -875,10 +1050,16 @@ var expectedConf = &Config{
},
},
{
- JobName: "hetzner",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ JobName: "hetzner",
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -924,9 +1105,15 @@ var expectedConf = &Config{
{
JobName: "service-eureka",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -940,12 +1127,55 @@ var expectedConf = &Config{
},
},
},
+ {
+ JobName: "ovhcloud",
+
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
+
+ HTTPClientConfig: config.DefaultHTTPClientConfig,
+ MetricsPath: DefaultScrapeConfig.MetricsPath,
+ Scheme: DefaultScrapeConfig.Scheme,
+
+ ServiceDiscoveryConfigs: discovery.Configs{
+ &ovhcloud.SDConfig{
+ Endpoint: "ovh-eu",
+ ApplicationKey: "testAppKey",
+ ApplicationSecret: "testAppSecret",
+ ConsumerKey: "testConsumerKey",
+ RefreshInterval: model.Duration(60 * time.Second),
+ Service: "vps",
+ },
+ &ovhcloud.SDConfig{
+ Endpoint: "ovh-eu",
+ ApplicationKey: "testAppKey",
+ ApplicationSecret: "testAppSecret",
+ ConsumerKey: "testConsumerKey",
+ RefreshInterval: model.Duration(60 * time.Second),
+ Service: "dedicated_server",
+ },
+ },
+ },
{
JobName: "scaleway",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
+
HTTPClientConfig: config.DefaultHTTPClientConfig,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -978,9 +1208,15 @@ var expectedConf = &Config{
{
JobName: "linode-instances",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -1005,9 +1241,16 @@ var expectedConf = &Config{
{
JobName: "uyuni",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
+
HTTPClientConfig: config.DefaultHTTPClientConfig,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -1024,10 +1267,16 @@ var expectedConf = &Config{
},
},
{
- JobName: "ionos",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ JobName: "ionos",
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -1049,9 +1298,15 @@ var expectedConf = &Config{
{
JobName: "vultr",
- HonorTimestamps: true,
- ScrapeInterval: model.Duration(15 * time.Second),
- ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ BodySizeLimit: globBodySizeLimit,
+ SampleLimit: globSampleLimit,
+ TargetLimit: globTargetLimit,
+ LabelLimit: globLabelLimit,
+ LabelNameLengthLimit: globLabelNameLengthLimit,
+ LabelValueLengthLimit: globLabelValueLengthLimit,
MetricsPath: DefaultScrapeConfig.MetricsPath,
Scheme: DefaultScrapeConfig.Scheme,
@@ -1175,7 +1430,7 @@ func TestElideSecrets(t *testing.T) {
yamlConfig := string(config)
matches := secretRe.FindAllStringIndex(yamlConfig, -1)
- require.Equal(t, 18, len(matches), "wrong number of secret matches found")
+ require.Equal(t, 22, len(matches), "wrong number of secret matches found")
require.NotContains(t, yamlConfig, "mysecret",
"yaml marshal reveals authentication credentials.")
}
@@ -1286,6 +1541,22 @@ var expectedErrors = []struct {
filename: "labeldrop5.bad.yml",
errMsg: "labeldrop action requires only 'regex', and no other fields",
},
+ {
+ filename: "dropequal.bad.yml",
+ errMsg: "relabel configuration for dropequal action requires 'target_label' value",
+ },
+ {
+ filename: "dropequal1.bad.yml",
+ errMsg: "dropequal action requires only 'source_labels' and `target_label`, and no other fields",
+ },
+ {
+ filename: "keepequal.bad.yml",
+ errMsg: "relabel configuration for keepequal action requires 'target_label' value",
+ },
+ {
+ filename: "keepequal1.bad.yml",
+ errMsg: "keepequal action requires only 'source_labels' and `target_label`, and no other fields",
+ },
{
filename: "labelmap.bad.yml",
errMsg: "\"l-$1\" is invalid 'replacement' for labelmap action",
@@ -1456,7 +1727,7 @@ var expectedErrors = []struct {
},
{
filename: "remote_write_authorization_header.bad.yml",
- errMsg: `authorization header must be changed via the basic_auth, authorization, oauth2, or sigv4 parameter`,
+ errMsg: `authorization header must be changed via the basic_auth, authorization, oauth2, sigv4, or azuread parameter`,
},
{
filename: "remote_write_url_missing.bad.yml",
@@ -1618,6 +1889,18 @@ var expectedErrors = []struct {
filename: "ionos_datacenter.bad.yml",
errMsg: "datacenter id can't be empty",
},
+ {
+ filename: "ovhcloud_no_secret.bad.yml",
+ errMsg: "application secret can not be empty",
+ },
+ {
+ filename: "ovhcloud_bad_service.bad.yml",
+ errMsg: "unknown service: fakeservice",
+ },
+ {
+ filename: "scrape_config_files_glob.bad.yml",
+ errMsg: `parsing YAML file testdata/scrape_config_files_glob.bad.yml: invalid scrape config file path "scrape_configs/*/*"`,
+ },
}
func TestBadConfigs(t *testing.T) {
@@ -1670,6 +1953,33 @@ func TestExpandExternalLabels(t *testing.T) {
require.Equal(t, labels.FromStrings("bar", "foo", "baz", "fooTestValuebar", "foo", "TestValue", "qux", "foo${TEST}", "xyz", "foo$bar"), c.GlobalConfig.ExternalLabels)
}
+func TestAgentMode(t *testing.T) {
+ _, err := LoadFile("testdata/agent_mode.with_alert_manager.yml", true, false, log.NewNopLogger())
+ require.ErrorContains(t, err, "field alerting is not allowed in agent mode")
+
+ _, err = LoadFile("testdata/agent_mode.with_alert_relabels.yml", true, false, log.NewNopLogger())
+ require.ErrorContains(t, err, "field alerting is not allowed in agent mode")
+
+ _, err = LoadFile("testdata/agent_mode.with_rule_files.yml", true, false, log.NewNopLogger())
+ require.ErrorContains(t, err, "field rule_files is not allowed in agent mode")
+
+ _, err = LoadFile("testdata/agent_mode.with_remote_reads.yml", true, false, log.NewNopLogger())
+ require.ErrorContains(t, err, "field remote_read is not allowed in agent mode")
+
+ c, err := LoadFile("testdata/agent_mode.without_remote_writes.yml", true, false, log.NewNopLogger())
+ require.NoError(t, err)
+ require.Len(t, c.RemoteWriteConfigs, 0)
+
+ c, err = LoadFile("testdata/agent_mode.good.yml", true, false, log.NewNopLogger())
+ require.NoError(t, err)
+ require.Len(t, c.RemoteWriteConfigs, 1)
+ require.Equal(
+ t,
+ "http://remote1/push",
+ c.RemoteWriteConfigs[0].URL.String(),
+ )
+}
+
func TestEmptyGlobalBlock(t *testing.T) {
c, err := Load("global:\n", false, log.NewNopLogger())
require.NoError(t, err)
@@ -1677,6 +1987,156 @@ func TestEmptyGlobalBlock(t *testing.T) {
require.Equal(t, exp, *c)
}
+func TestGetScrapeConfigs(t *testing.T) {
+ sc := func(jobName string, scrapeInterval, scrapeTimeout model.Duration) *ScrapeConfig {
+ return &ScrapeConfig{
+ JobName: jobName,
+ HonorTimestamps: true,
+ ScrapeInterval: scrapeInterval,
+ ScrapeTimeout: scrapeTimeout,
+ MetricsPath: "/metrics",
+ Scheme: "http",
+ HTTPClientConfig: config.DefaultHTTPClientConfig,
+ ServiceDiscoveryConfigs: discovery.Configs{
+ discovery.StaticConfig{
+ {
+ Targets: []model.LabelSet{
+ {
+ model.AddressLabel: "localhost:8080",
+ },
+ },
+ Source: "0",
+ },
+ },
+ },
+ }
+ }
+
+ testCases := []struct {
+ name string
+ configFile string
+ expectedResult []*ScrapeConfig
+ expectedError string
+ }{
+ {
+ name: "An included config file should be a valid global config.",
+ configFile: "testdata/scrape_config_files.good.yml",
+ expectedResult: []*ScrapeConfig{sc("prometheus", model.Duration(60*time.Second), model.Duration(10*time.Second))},
+ },
+ {
+ name: "An global config that only include a scrape config file.",
+ configFile: "testdata/scrape_config_files_only.good.yml",
+ expectedResult: []*ScrapeConfig{sc("prometheus", model.Duration(60*time.Second), model.Duration(10*time.Second))},
+ },
+ {
+ name: "An global config that combine scrape config files and scrape configs.",
+ configFile: "testdata/scrape_config_files_combined.good.yml",
+ expectedResult: []*ScrapeConfig{
+ sc("node", model.Duration(60*time.Second), model.Duration(10*time.Second)),
+ sc("prometheus", model.Duration(60*time.Second), model.Duration(10*time.Second)),
+ sc("alertmanager", model.Duration(60*time.Second), model.Duration(10*time.Second)),
+ },
+ },
+ {
+ name: "An global config that includes a scrape config file with globs",
+ configFile: "testdata/scrape_config_files_glob.good.yml",
+ expectedResult: []*ScrapeConfig{
+ {
+ JobName: "prometheus",
+
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(60 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+
+ MetricsPath: DefaultScrapeConfig.MetricsPath,
+ Scheme: DefaultScrapeConfig.Scheme,
+
+ HTTPClientConfig: config.HTTPClientConfig{
+ TLSConfig: config.TLSConfig{
+ CertFile: filepath.FromSlash("testdata/scrape_configs/valid_cert_file"),
+ KeyFile: filepath.FromSlash("testdata/scrape_configs/valid_key_file"),
+ },
+ FollowRedirects: true,
+ EnableHTTP2: true,
+ },
+
+ ServiceDiscoveryConfigs: discovery.Configs{
+ discovery.StaticConfig{
+ {
+ Targets: []model.LabelSet{
+ {model.AddressLabel: "localhost:8080"},
+ },
+ Source: "0",
+ },
+ },
+ },
+ },
+ {
+ JobName: "node",
+
+ HonorTimestamps: true,
+ ScrapeInterval: model.Duration(15 * time.Second),
+ ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
+ HTTPClientConfig: config.HTTPClientConfig{
+ TLSConfig: config.TLSConfig{
+ CertFile: filepath.FromSlash("testdata/valid_cert_file"),
+ KeyFile: filepath.FromSlash("testdata/valid_key_file"),
+ },
+ FollowRedirects: true,
+ EnableHTTP2: true,
+ },
+
+ MetricsPath: DefaultScrapeConfig.MetricsPath,
+ Scheme: DefaultScrapeConfig.Scheme,
+
+ ServiceDiscoveryConfigs: discovery.Configs{
+ &vultr.SDConfig{
+ HTTPClientConfig: config.HTTPClientConfig{
+ Authorization: &config.Authorization{
+ Type: "Bearer",
+ Credentials: "abcdef",
+ },
+ FollowRedirects: true,
+ EnableHTTP2: true,
+ },
+ Port: 80,
+ RefreshInterval: model.Duration(60 * time.Second),
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "An global config that includes twice the same scrape configs.",
+ configFile: "testdata/scrape_config_files_double_import.bad.yml",
+ expectedError: `found multiple scrape configs with job name "prometheus"`,
+ },
+ {
+ name: "An global config that includes a scrape config identical to a scrape config in the main file.",
+ configFile: "testdata/scrape_config_files_duplicate.bad.yml",
+ expectedError: `found multiple scrape configs with job name "prometheus"`,
+ },
+ {
+ name: "An global config that includes a scrape config file with errors.",
+ configFile: "testdata/scrape_config_files_global.bad.yml",
+ expectedError: `scrape timeout greater than scrape interval for scrape config with job name "prometheus"`,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ c, err := LoadFile(tc.configFile, false, false, log.NewNopLogger())
+ require.NoError(t, err)
+
+ scfgs, err := c.GetScrapeConfigs()
+ if len(tc.expectedError) > 0 {
+ require.ErrorContains(t, err, tc.expectedError)
+ }
+ require.Equal(t, tc.expectedResult, scfgs)
+ })
+ }
+}
+
func kubernetesSDHostURL() config.URL {
tURL, _ := url.Parse("https://localhost:1234")
return config.URL{URL: tURL}
diff --git a/config/testdata/agent_mode.good.yml b/config/testdata/agent_mode.good.yml
new file mode 100644
index 0000000000..a16612095c
--- /dev/null
+++ b/config/testdata/agent_mode.good.yml
@@ -0,0 +1,2 @@
+remote_write:
+ - url: http://remote1/push
diff --git a/config/testdata/agent_mode.with_alert_manager.yml b/config/testdata/agent_mode.with_alert_manager.yml
new file mode 100644
index 0000000000..9a3929957f
--- /dev/null
+++ b/config/testdata/agent_mode.with_alert_manager.yml
@@ -0,0 +1,6 @@
+alerting:
+ alertmanagers:
+ - scheme: https
+ static_configs:
+ - targets:
+ - "1.2.3.4:9093"
diff --git a/config/testdata/agent_mode.with_alert_relabels.yml b/config/testdata/agent_mode.with_alert_relabels.yml
new file mode 100644
index 0000000000..67e70fc7f4
--- /dev/null
+++ b/config/testdata/agent_mode.with_alert_relabels.yml
@@ -0,0 +1,5 @@
+alerting:
+ alert_relabel_configs:
+ - action: uppercase
+ source_labels: [instance]
+ target_label: instance
diff --git a/config/testdata/agent_mode.with_remote_reads.yml b/config/testdata/agent_mode.with_remote_reads.yml
new file mode 100644
index 0000000000..416676793b
--- /dev/null
+++ b/config/testdata/agent_mode.with_remote_reads.yml
@@ -0,0 +1,5 @@
+remote_read:
+ - url: http://remote1/read
+ read_recent: true
+ name: default
+ enable_http2: false
diff --git a/config/testdata/agent_mode.with_rule_files.yml b/config/testdata/agent_mode.with_rule_files.yml
new file mode 100644
index 0000000000..3aaa9ad471
--- /dev/null
+++ b/config/testdata/agent_mode.with_rule_files.yml
@@ -0,0 +1,3 @@
+rule_files:
+ - "first.rules"
+ - "my/*.rules"
diff --git a/config/testdata/agent_mode.without_remote_writes.yml b/config/testdata/agent_mode.without_remote_writes.yml
new file mode 100644
index 0000000000..1285bef674
--- /dev/null
+++ b/config/testdata/agent_mode.without_remote_writes.yml
@@ -0,0 +1,2 @@
+global:
+ scrape_interval: 15s
diff --git a/config/testdata/conf.good.yml b/config/testdata/conf.good.yml
index c19b7c1e6d..19cfe1eb5d 100644
--- a/config/testdata/conf.good.yml
+++ b/config/testdata/conf.good.yml
@@ -2,6 +2,12 @@
global:
scrape_interval: 15s
evaluation_interval: 30s
+ body_size_limit: 15MB
+ sample_limit: 1500
+ target_limit: 30
+ label_limit: 30
+ label_name_length_limit: 200
+ label_value_length_limit: 200
# scrape_timeout is set to the global default (10s).
external_labels:
@@ -87,6 +93,12 @@ scrape_configs:
- regex:
replacement: static
target_label: abc
+ - source_labels: [foo]
+ target_label: abc
+ action: keepequal
+ - source_labels: [foo]
+ target_label: abc
+ action: dropequal
authorization:
credentials_file: valid_token_file
@@ -105,6 +117,11 @@ scrape_configs:
body_size_limit: 10MB
sample_limit: 1000
+ target_limit: 35
+ label_limit: 35
+ label_name_length_limit: 210
+ label_value_length_limit: 210
+
metrics_path: /my_path
scheme: https
@@ -145,6 +162,7 @@ scrape_configs:
consul_sd_configs:
- server: "localhost:1234"
token: mysecret
+ path_prefix: /consul
services: ["nginx", "cache", "mysql"]
tags: ["canary", "v1"]
node_meta:
@@ -349,6 +367,21 @@ scrape_configs:
eureka_sd_configs:
- server: "http://eureka.example.com:8761/eureka"
+ - job_name: ovhcloud
+ ovhcloud_sd_configs:
+ - service: vps
+ endpoint: ovh-eu
+ application_key: testAppKey
+ application_secret: testAppSecret
+ consumer_key: testConsumerKey
+ refresh_interval: 1m
+ - service: dedicated_server
+ endpoint: ovh-eu
+ application_key: testAppKey
+ application_secret: testAppSecret
+ consumer_key: testConsumerKey
+ refresh_interval: 1m
+
- job_name: scaleway
scaleway_sd_configs:
- role: instance
diff --git a/config/testdata/dropequal.bad.yml b/config/testdata/dropequal.bad.yml
new file mode 100644
index 0000000000..32bd1a9641
--- /dev/null
+++ b/config/testdata/dropequal.bad.yml
@@ -0,0 +1,5 @@
+scrape_configs:
+ - job_name: prometheus
+ relabel_configs:
+ - source_labels: [abcdef]
+ action: dropequal
diff --git a/config/testdata/dropequal1.bad.yml b/config/testdata/dropequal1.bad.yml
new file mode 100644
index 0000000000..b648c0db26
--- /dev/null
+++ b/config/testdata/dropequal1.bad.yml
@@ -0,0 +1,7 @@
+scrape_configs:
+ - job_name: prometheus
+ relabel_configs:
+ - source_labels: [abcdef]
+ action: dropequal
+ regex: foo
+ target_label: bar
diff --git a/config/testdata/keepequal.bad.yml b/config/testdata/keepequal.bad.yml
new file mode 100644
index 0000000000..5f5662177a
--- /dev/null
+++ b/config/testdata/keepequal.bad.yml
@@ -0,0 +1,5 @@
+scrape_configs:
+ - job_name: prometheus
+ relabel_configs:
+ - source_labels: [abcdef]
+ action: keepequal
diff --git a/config/testdata/keepequal1.bad.yml b/config/testdata/keepequal1.bad.yml
new file mode 100644
index 0000000000..c4628314c6
--- /dev/null
+++ b/config/testdata/keepequal1.bad.yml
@@ -0,0 +1,7 @@
+scrape_configs:
+ - job_name: prometheus
+ relabel_configs:
+ - source_labels: [abcdef]
+ action: keepequal
+ regex: foo
+ target_label: bar
diff --git a/config/testdata/ovhcloud_bad_service.bad.yml b/config/testdata/ovhcloud_bad_service.bad.yml
new file mode 100644
index 0000000000..3c2fa03cc8
--- /dev/null
+++ b/config/testdata/ovhcloud_bad_service.bad.yml
@@ -0,0 +1,8 @@
+scrape_configs:
+ - ovhcloud_sd_configs:
+ - service: fakeservice
+ endpoint: ovh-eu
+ application_key: testAppKey
+ application_secret: testAppSecret
+ consumer_key: testConsumerKey
+ refresh_interval: 1m
diff --git a/config/testdata/ovhcloud_no_secret.bad.yml b/config/testdata/ovhcloud_no_secret.bad.yml
new file mode 100644
index 0000000000..1959bee870
--- /dev/null
+++ b/config/testdata/ovhcloud_no_secret.bad.yml
@@ -0,0 +1,7 @@
+scrape_configs:
+ - ovhcloud_sd_configs:
+ - service: dedicated_server
+ endpoint: ovh-eu
+ application_key: testAppKey
+ consumer_key: testConsumerKey
+ refresh_interval: 1m
diff --git a/config/testdata/scrape_config_files.bad.yml b/config/testdata/scrape_config_files.bad.yml
new file mode 100644
index 0000000000..1b7690899b
--- /dev/null
+++ b/config/testdata/scrape_config_files.bad.yml
@@ -0,0 +1,6 @@
+scrape_configs:
+ - job_name: prometheus
+ scrape_interval: 10s
+ scrape_timeout: 20s
+ static_configs:
+ - targets: ['localhost:8080']
diff --git a/config/testdata/scrape_config_files.good.yml b/config/testdata/scrape_config_files.good.yml
new file mode 100644
index 0000000000..5a0c2df9d6
--- /dev/null
+++ b/config/testdata/scrape_config_files.good.yml
@@ -0,0 +1,4 @@
+scrape_configs:
+ - job_name: prometheus
+ static_configs:
+ - targets: ['localhost:8080']
diff --git a/config/testdata/scrape_config_files2.good.yml b/config/testdata/scrape_config_files2.good.yml
new file mode 100644
index 0000000000..f10bdf06ee
--- /dev/null
+++ b/config/testdata/scrape_config_files2.good.yml
@@ -0,0 +1,4 @@
+scrape_configs:
+ - job_name: alertmanager
+ static_configs:
+ - targets: ['localhost:8080']
diff --git a/config/testdata/scrape_config_files_combined.good.yml b/config/testdata/scrape_config_files_combined.good.yml
new file mode 100644
index 0000000000..ee3b0909c1
--- /dev/null
+++ b/config/testdata/scrape_config_files_combined.good.yml
@@ -0,0 +1,7 @@
+scrape_config_files:
+ - scrape_config_files.good.yml
+ - scrape_config_files2.good.yml
+scrape_configs:
+ - job_name: node
+ static_configs:
+ - targets: ['localhost:8080']
diff --git a/config/testdata/scrape_config_files_double_import.bad.yml b/config/testdata/scrape_config_files_double_import.bad.yml
new file mode 100644
index 0000000000..b153d7f4bd
--- /dev/null
+++ b/config/testdata/scrape_config_files_double_import.bad.yml
@@ -0,0 +1,3 @@
+scrape_config_files:
+ - scrape_config_files.good.yml
+ - scrape_config_files.good.yml
diff --git a/config/testdata/scrape_config_files_duplicate.bad.yml b/config/testdata/scrape_config_files_duplicate.bad.yml
new file mode 100644
index 0000000000..ceac36eaa8
--- /dev/null
+++ b/config/testdata/scrape_config_files_duplicate.bad.yml
@@ -0,0 +1,6 @@
+scrape_config_files:
+ - scrape_config_files.good.yml
+scrape_configs:
+ - job_name: prometheus
+ static_configs:
+ - targets: ['localhost:8080']
diff --git a/config/testdata/scrape_config_files_glob.bad.yml b/config/testdata/scrape_config_files_glob.bad.yml
new file mode 100644
index 0000000000..f3cd636764
--- /dev/null
+++ b/config/testdata/scrape_config_files_glob.bad.yml
@@ -0,0 +1,6 @@
+scrape_config_files:
+ - scrape_configs/*/*
+scrape_configs:
+ - job_name: node
+ static_configs:
+ - targets: ['localhost:8080']
diff --git a/config/testdata/scrape_config_files_glob.good.yml b/config/testdata/scrape_config_files_glob.good.yml
new file mode 100644
index 0000000000..d724045030
--- /dev/null
+++ b/config/testdata/scrape_config_files_glob.good.yml
@@ -0,0 +1,2 @@
+scrape_config_files:
+ - scrape_configs/*.yml
diff --git a/config/testdata/scrape_config_files_global.bad.yml b/config/testdata/scrape_config_files_global.bad.yml
new file mode 100644
index 0000000000..b825fa62c8
--- /dev/null
+++ b/config/testdata/scrape_config_files_global.bad.yml
@@ -0,0 +1,2 @@
+scrape_config_files:
+ - scrape_config_files.bad.yml
diff --git a/config/testdata/scrape_config_files_global_duplicate.bad.yml b/config/testdata/scrape_config_files_global_duplicate.bad.yml
new file mode 100644
index 0000000000..b6ec1a7fdb
--- /dev/null
+++ b/config/testdata/scrape_config_files_global_duplicate.bad.yml
@@ -0,0 +1,11 @@
+global:
+ scrape_interval: 15s
+
+scrape_config_files:
+ - scrape_config_files.good.yml
+ - scrape_config_files.good.yml
+
+scrape_configs:
+ - job_name: prometheus
+ static_configs:
+ - targets: ['localhost:8080']
diff --git a/config/testdata/scrape_config_files_only.good.yml b/config/testdata/scrape_config_files_only.good.yml
new file mode 100644
index 0000000000..9f2711d51f
--- /dev/null
+++ b/config/testdata/scrape_config_files_only.good.yml
@@ -0,0 +1,2 @@
+scrape_config_files:
+ - scrape_config_files.good.yml
diff --git a/config/testdata/scrape_configs/scrape_config_files1.good.yml b/config/testdata/scrape_configs/scrape_config_files1.good.yml
new file mode 100644
index 0000000000..b0a05c532e
--- /dev/null
+++ b/config/testdata/scrape_configs/scrape_config_files1.good.yml
@@ -0,0 +1,7 @@
+scrape_configs:
+ - job_name: prometheus
+ static_configs:
+ - targets: ['localhost:8080']
+ tls_config:
+ cert_file: valid_cert_file
+ key_file: valid_key_file
diff --git a/config/testdata/scrape_configs/scrape_config_files2.good.yml b/config/testdata/scrape_configs/scrape_config_files2.good.yml
new file mode 100644
index 0000000000..468d022965
--- /dev/null
+++ b/config/testdata/scrape_configs/scrape_config_files2.good.yml
@@ -0,0 +1,9 @@
+scrape_configs:
+ - job_name: node
+ scrape_interval: 15s
+ tls_config:
+ cert_file: ../valid_cert_file
+ key_file: ../valid_key_file
+ vultr_sd_configs:
+ - authorization:
+ credentials: abcdef
diff --git a/discovery/aws/ec2.go b/discovery/aws/ec2.go
index 7519f58da1..86d76627e1 100644
--- a/discovery/aws/ec2.go
+++ b/discovery/aws/ec2.go
@@ -66,8 +66,9 @@ const (
// DefaultEC2SDConfig is the default EC2 SD configuration.
var DefaultEC2SDConfig = EC2SDConfig{
- Port: 80,
- RefreshInterval: model.Duration(60 * time.Second),
+ Port: 80,
+ RefreshInterval: model.Duration(60 * time.Second),
+ HTTPClientConfig: config.DefaultHTTPClientConfig,
}
func init() {
@@ -91,6 +92,8 @@ type EC2SDConfig struct {
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
Port int `yaml:"port"`
Filters []*EC2Filter `yaml:"filters"`
+
+ HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
}
// Name returns the name of the EC2 Config.
@@ -161,7 +164,7 @@ func NewEC2Discovery(conf *EC2SDConfig, logger log.Logger) *EC2Discovery {
return d
}
-func (d *EC2Discovery) ec2Client(ctx context.Context) (*ec2.EC2, error) {
+func (d *EC2Discovery) ec2Client(context.Context) (*ec2.EC2, error) {
if d.ec2 != nil {
return d.ec2, nil
}
@@ -171,11 +174,17 @@ func (d *EC2Discovery) ec2Client(ctx context.Context) (*ec2.EC2, error) {
creds = nil
}
+ client, err := config.NewClientFromConfig(d.cfg.HTTPClientConfig, "ec2_sd")
+ if err != nil {
+ return nil, err
+ }
+
sess, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Endpoint: &d.cfg.Endpoint,
Region: &d.cfg.Region,
Credentials: creds,
+ HTTPClient: client,
},
Profile: d.cfg.Profile,
})
diff --git a/discovery/aws/lightsail.go b/discovery/aws/lightsail.go
index 016d78a67f..e671769ca3 100644
--- a/discovery/aws/lightsail.go
+++ b/discovery/aws/lightsail.go
@@ -56,8 +56,9 @@ const (
// DefaultLightsailSDConfig is the default Lightsail SD configuration.
var DefaultLightsailSDConfig = LightsailSDConfig{
- Port: 80,
- RefreshInterval: model.Duration(60 * time.Second),
+ Port: 80,
+ RefreshInterval: model.Duration(60 * time.Second),
+ HTTPClientConfig: config.DefaultHTTPClientConfig,
}
func init() {
@@ -74,6 +75,8 @@ type LightsailSDConfig struct {
RoleARN string `yaml:"role_arn,omitempty"`
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
Port int `yaml:"port"`
+
+ HTTPClientConfig config.HTTPClientConfig `yaml:",inline"`
}
// Name returns the name of the Lightsail Config.
@@ -144,11 +147,17 @@ func (d *LightsailDiscovery) lightsailClient() (*lightsail.Lightsail, error) {
creds = nil
}
+ client, err := config.NewClientFromConfig(d.cfg.HTTPClientConfig, "lightsail_sd")
+ if err != nil {
+ return nil, err
+ }
+
sess, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Endpoint: &d.cfg.Endpoint,
Region: &d.cfg.Region,
Credentials: creds,
+ HTTPClient: client,
},
Profile: d.cfg.Profile,
})
diff --git a/discovery/azure/azure.go b/discovery/azure/azure.go
index 44576c79b9..098fbb4c5f 100644
--- a/discovery/azure/azure.go
+++ b/discovery/azure/azure.go
@@ -55,6 +55,7 @@ const (
azureLabelMachinePublicIP = azureLabel + "machine_public_ip"
azureLabelMachineTag = azureLabel + "machine_tag_"
azureLabelMachineScaleSet = azureLabel + "machine_scale_set"
+ azureLabelMachineSize = azureLabel + "machine_size"
authMethodOAuth = "OAuth"
authMethodManagedIdentity = "ManagedIdentity"
@@ -261,6 +262,7 @@ type virtualMachine struct {
ScaleSet string
Tags map[string]*string
NetworkInterfaces []string
+ Size string
}
// Create a new azureResource object from an ID string.
@@ -343,6 +345,7 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
azureLabelMachineOSType: model.LabelValue(vm.OsType),
azureLabelMachineLocation: model.LabelValue(vm.Location),
azureLabelMachineResourceGroup: model.LabelValue(r.ResourceGroup),
+ azureLabelMachineSize: model.LabelValue(vm.Size),
}
if vm.ScaleSet != "" {
@@ -514,6 +517,7 @@ func mapFromVM(vm compute.VirtualMachine) virtualMachine {
tags := map[string]*string{}
networkInterfaces := []string{}
var computerName string
+ var size string
if vm.Tags != nil {
tags = vm.Tags
@@ -525,10 +529,13 @@ func mapFromVM(vm compute.VirtualMachine) virtualMachine {
}
}
- if vm.VirtualMachineProperties != nil &&
- vm.VirtualMachineProperties.OsProfile != nil &&
- vm.VirtualMachineProperties.OsProfile.ComputerName != nil {
- computerName = *(vm.VirtualMachineProperties.OsProfile.ComputerName)
+ if vm.VirtualMachineProperties != nil {
+ if vm.VirtualMachineProperties.OsProfile != nil && vm.VirtualMachineProperties.OsProfile.ComputerName != nil {
+ computerName = *(vm.VirtualMachineProperties.OsProfile.ComputerName)
+ }
+ if vm.VirtualMachineProperties.HardwareProfile != nil {
+ size = string(vm.VirtualMachineProperties.HardwareProfile.VMSize)
+ }
}
return virtualMachine{
@@ -541,6 +548,7 @@ func mapFromVM(vm compute.VirtualMachine) virtualMachine {
ScaleSet: "",
Tags: tags,
NetworkInterfaces: networkInterfaces,
+ Size: size,
}
}
@@ -549,6 +557,7 @@ func mapFromVMScaleSetVM(vm compute.VirtualMachineScaleSetVM, scaleSetName strin
tags := map[string]*string{}
networkInterfaces := []string{}
var computerName string
+ var size string
if vm.Tags != nil {
tags = vm.Tags
@@ -560,8 +569,13 @@ func mapFromVMScaleSetVM(vm compute.VirtualMachineScaleSetVM, scaleSetName strin
}
}
- if vm.VirtualMachineScaleSetVMProperties != nil && vm.VirtualMachineScaleSetVMProperties.OsProfile != nil {
- computerName = *(vm.VirtualMachineScaleSetVMProperties.OsProfile.ComputerName)
+ if vm.VirtualMachineScaleSetVMProperties != nil {
+ if vm.VirtualMachineScaleSetVMProperties.OsProfile != nil && vm.VirtualMachineScaleSetVMProperties.OsProfile.ComputerName != nil {
+ computerName = *(vm.VirtualMachineScaleSetVMProperties.OsProfile.ComputerName)
+ }
+ if vm.VirtualMachineScaleSetVMProperties.HardwareProfile != nil {
+ size = string(vm.VirtualMachineScaleSetVMProperties.HardwareProfile.VMSize)
+ }
}
return virtualMachine{
@@ -574,6 +588,7 @@ func mapFromVMScaleSetVM(vm compute.VirtualMachineScaleSetVM, scaleSetName strin
ScaleSet: scaleSetName,
Tags: tags,
NetworkInterfaces: networkInterfaces,
+ Size: size,
}
}
diff --git a/discovery/azure/azure_test.go b/discovery/azure/azure_test.go
index 744d182de7..179b97ba61 100644
--- a/discovery/azure/azure_test.go
+++ b/discovery/azure/azure_test.go
@@ -28,6 +28,7 @@ func TestMain(m *testing.M) {
func TestMapFromVMWithEmptyTags(t *testing.T) {
id := "test"
name := "name"
+ size := "size"
vmType := "type"
location := "westeurope"
computerName := "computer_name"
@@ -44,6 +45,9 @@ func TestMapFromVMWithEmptyTags(t *testing.T) {
},
},
NetworkProfile: &networkProfile,
+ HardwareProfile: &compute.HardwareProfile{
+ VMSize: compute.VirtualMachineSizeTypes(size),
+ },
}
testVM := compute.VirtualMachine{
@@ -64,6 +68,7 @@ func TestMapFromVMWithEmptyTags(t *testing.T) {
OsType: "Linux",
Tags: map[string]*string{},
NetworkInterfaces: []string{},
+ Size: size,
}
actualVM := mapFromVM(testVM)
@@ -74,6 +79,7 @@ func TestMapFromVMWithEmptyTags(t *testing.T) {
func TestMapFromVMWithTags(t *testing.T) {
id := "test"
name := "name"
+ size := "size"
vmType := "type"
location := "westeurope"
computerName := "computer_name"
@@ -93,6 +99,9 @@ func TestMapFromVMWithTags(t *testing.T) {
},
},
NetworkProfile: &networkProfile,
+ HardwareProfile: &compute.HardwareProfile{
+ VMSize: compute.VirtualMachineSizeTypes(size),
+ },
}
testVM := compute.VirtualMachine{
@@ -113,6 +122,7 @@ func TestMapFromVMWithTags(t *testing.T) {
OsType: "Linux",
Tags: tags,
NetworkInterfaces: []string{},
+ Size: size,
}
actualVM := mapFromVM(testVM)
@@ -123,6 +133,7 @@ func TestMapFromVMWithTags(t *testing.T) {
func TestMapFromVMScaleSetVMWithEmptyTags(t *testing.T) {
id := "test"
name := "name"
+ size := "size"
vmType := "type"
location := "westeurope"
computerName := "computer_name"
@@ -139,6 +150,9 @@ func TestMapFromVMScaleSetVMWithEmptyTags(t *testing.T) {
},
},
NetworkProfile: &networkProfile,
+ HardwareProfile: &compute.HardwareProfile{
+ VMSize: compute.VirtualMachineSizeTypes(size),
+ },
}
testVM := compute.VirtualMachineScaleSetVM{
@@ -161,6 +175,7 @@ func TestMapFromVMScaleSetVMWithEmptyTags(t *testing.T) {
Tags: map[string]*string{},
NetworkInterfaces: []string{},
ScaleSet: scaleSet,
+ Size: size,
}
actualVM := mapFromVMScaleSetVM(testVM, scaleSet)
@@ -171,6 +186,7 @@ func TestMapFromVMScaleSetVMWithEmptyTags(t *testing.T) {
func TestMapFromVMScaleSetVMWithTags(t *testing.T) {
id := "test"
name := "name"
+ size := "size"
vmType := "type"
location := "westeurope"
computerName := "computer_name"
@@ -190,6 +206,9 @@ func TestMapFromVMScaleSetVMWithTags(t *testing.T) {
},
},
NetworkProfile: &networkProfile,
+ HardwareProfile: &compute.HardwareProfile{
+ VMSize: compute.VirtualMachineSizeTypes(size),
+ },
}
testVM := compute.VirtualMachineScaleSetVM{
@@ -212,6 +231,7 @@ func TestMapFromVMScaleSetVMWithTags(t *testing.T) {
Tags: tags,
NetworkInterfaces: []string{},
ScaleSet: scaleSet,
+ Size: size,
}
actualVM := mapFromVMScaleSetVM(testVM, scaleSet)
diff --git a/discovery/consul/consul.go b/discovery/consul/consul.go
index 8d0a1fea48..99ea396b99 100644
--- a/discovery/consul/consul.go
+++ b/discovery/consul/consul.go
@@ -60,6 +60,8 @@ const (
datacenterLabel = model.MetaLabelPrefix + "consul_dc"
// namespaceLabel is the name of the label containing the namespace (Consul Enterprise only).
namespaceLabel = model.MetaLabelPrefix + "consul_namespace"
+ // partitionLabel is the name of the label containing the Admin Partition (Consul Enterprise only).
+ partitionLabel = model.MetaLabelPrefix + "consul_partition"
// taggedAddressesLabel is the prefix for the labels mapping to a target's tagged addresses.
taggedAddressesLabel = model.MetaLabelPrefix + "consul_tagged_address_"
// serviceIDLabel is the name of the label containing the service ID.
@@ -109,9 +111,11 @@ func init() {
// SDConfig is the configuration for Consul service discovery.
type SDConfig struct {
Server string `yaml:"server,omitempty"`
+ PathPrefix string `yaml:"path_prefix,omitempty"`
Token config.Secret `yaml:"token,omitempty"`
Datacenter string `yaml:"datacenter,omitempty"`
Namespace string `yaml:"namespace,omitempty"`
+ Partition string `yaml:"partition,omitempty"`
TagSeparator string `yaml:"tag_separator,omitempty"`
Scheme string `yaml:"scheme,omitempty"`
Username string `yaml:"username,omitempty"`
@@ -183,6 +187,7 @@ type Discovery struct {
client *consul.Client
clientDatacenter string
clientNamespace string
+ clientPartition string
tagSeparator string
watchedServices []string // Set of services which will be discovered.
watchedTags []string // Tags used to filter instances of a service.
@@ -207,9 +212,11 @@ func NewDiscovery(conf *SDConfig, logger log.Logger) (*Discovery, error) {
clientConf := &consul.Config{
Address: conf.Server,
+ PathPrefix: conf.PathPrefix,
Scheme: conf.Scheme,
Datacenter: conf.Datacenter,
Namespace: conf.Namespace,
+ Partition: conf.Partition,
Token: string(conf.Token),
HttpClient: wrapper,
}
@@ -227,6 +234,7 @@ func NewDiscovery(conf *SDConfig, logger log.Logger) (*Discovery, error) {
refreshInterval: time.Duration(conf.RefreshInterval),
clientDatacenter: conf.Datacenter,
clientNamespace: conf.Namespace,
+ clientPartition: conf.Partition,
finalizer: wrapper.CloseIdleConnections,
logger: logger,
}
@@ -547,6 +555,7 @@ func (srv *consulService) watch(ctx context.Context, ch chan<- []*targetgroup.Gr
addressLabel: model.LabelValue(serviceNode.Node.Address),
nodeLabel: model.LabelValue(serviceNode.Node.Node),
namespaceLabel: model.LabelValue(serviceNode.Service.Namespace),
+ partitionLabel: model.LabelValue(serviceNode.Service.Partition),
tagsLabel: model.LabelValue(tags),
serviceAddressLabel: model.LabelValue(serviceNode.Service.Address),
servicePortLabel: model.LabelValue(strconv.Itoa(serviceNode.Service.Port)),
diff --git a/discovery/consul/consul_test.go b/discovery/consul/consul_test.go
index 9dc2d660bb..c929601638 100644
--- a/discovery/consul/consul_test.go
+++ b/discovery/consul/consul_test.go
@@ -365,7 +365,7 @@ func TestGetDatacenterShouldReturnError(t *testing.T) {
{
// Define a handler that will return status 500.
handler: func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(500)
+ w.WriteHeader(http.StatusInternalServerError)
},
errMessage: "Unexpected response code: 500 ()",
},
diff --git a/discovery/dns/dns.go b/discovery/dns/dns.go
index 2b11c242ab..96e07254f0 100644
--- a/discovery/dns/dns.go
+++ b/discovery/dns/dns.go
@@ -285,21 +285,22 @@ func lookupWithSearchPath(name string, qtype uint16, logger log.Logger) (*dns.Ms
for _, lname := range conf.NameList(name) {
response, err := lookupFromAnyServer(lname, qtype, conf, logger)
- if err != nil {
+ switch {
+ case err != nil:
// We can't go home yet, because a later name
// may give us a valid, successful answer. However
// we can no longer say "this name definitely doesn't
// exist", because we did not get that answer for
// at least one name.
allResponsesValid = false
- } else if response.Rcode == dns.RcodeSuccess {
+ case response.Rcode == dns.RcodeSuccess:
// Outcome 1: GOLD!
return response, nil
}
}
if allResponsesValid {
- // Outcome 2: everyone says NXDOMAIN, that's good enough for me
+ // Outcome 2: everyone says NXDOMAIN, that's good enough for me.
return &dns.Msg{}, nil
}
// Outcome 3: boned.
diff --git a/discovery/file/file.go b/discovery/file/file.go
index 5aa7f2e5f4..60b63350f5 100644
--- a/discovery/file/file.go
+++ b/discovery/file/file.go
@@ -39,18 +39,23 @@ import (
)
var (
+ fileSDReadErrorsCount = prometheus.NewCounter(
+ prometheus.CounterOpts{
+ Name: "prometheus_sd_file_read_errors_total",
+ Help: "The number of File-SD read errors.",
+ })
fileSDScanDuration = prometheus.NewSummary(
prometheus.SummaryOpts{
Name: "prometheus_sd_file_scan_duration_seconds",
Help: "The duration of the File-SD scan in seconds.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
})
- fileSDReadErrorsCount = prometheus.NewCounter(
+ fileSDTimeStamp = NewTimestampCollector()
+ fileWatcherErrorsCount = prometheus.NewCounter(
prometheus.CounterOpts{
- Name: "prometheus_sd_file_read_errors_total",
- Help: "The number of File-SD read errors.",
+ Name: "prometheus_sd_file_watcher_errors_total",
+ Help: "The number of File-SD errors caused by filesystem watch failures.",
})
- fileSDTimeStamp = NewTimestampCollector()
patFileSDName = regexp.MustCompile(`^[^*]*(\*[^/]*)?\.(json|yml|yaml|JSON|YML|YAML)$`)
@@ -62,7 +67,7 @@ var (
func init() {
discovery.RegisterConfig(&SDConfig{})
- prometheus.MustRegister(fileSDScanDuration, fileSDReadErrorsCount, fileSDTimeStamp)
+ prometheus.MustRegister(fileSDReadErrorsCount, fileSDScanDuration, fileSDTimeStamp, fileWatcherErrorsCount)
}
// SDConfig is the configuration for file based discovery.
@@ -221,8 +226,8 @@ func (d *Discovery) watchFiles() {
panic("no watcher configured")
}
for _, p := range d.paths {
- if idx := strings.LastIndex(p, "/"); idx > -1 {
- p = p[:idx]
+ if dir, _ := filepath.Split(p); dir != "" {
+ p = dir
} else {
p = "./"
}
@@ -237,6 +242,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
level.Error(d.logger).Log("msg", "Error adding file watcher", "err", err)
+ fileWatcherErrorsCount.Inc()
return
}
d.watcher = watcher
diff --git a/discovery/hetzner/hcloud.go b/discovery/hetzner/hcloud.go
index aa406a1a7a..50afdc1ec3 100644
--- a/discovery/hetzner/hcloud.go
+++ b/discovery/hetzner/hcloud.go
@@ -59,7 +59,7 @@ type hcloudDiscovery struct {
}
// newHcloudDiscovery returns a new hcloudDiscovery which periodically refreshes its targets.
-func newHcloudDiscovery(conf *SDConfig, logger log.Logger) (*hcloudDiscovery, error) {
+func newHcloudDiscovery(conf *SDConfig, _ log.Logger) (*hcloudDiscovery, error) {
d := &hcloudDiscovery{
port: conf.Port,
}
diff --git a/discovery/hetzner/robot.go b/discovery/hetzner/robot.go
index 4b7abaf77f..4960880289 100644
--- a/discovery/hetzner/robot.go
+++ b/discovery/hetzner/robot.go
@@ -51,7 +51,7 @@ type robotDiscovery struct {
}
// newRobotDiscovery returns a new robotDiscovery which periodically refreshes its targets.
-func newRobotDiscovery(conf *SDConfig, logger log.Logger) (*robotDiscovery, error) {
+func newRobotDiscovery(conf *SDConfig, _ log.Logger) (*robotDiscovery, error) {
d := &robotDiscovery{
port: conf.Port,
endpoint: conf.robotEndpoint,
@@ -69,7 +69,7 @@ func newRobotDiscovery(conf *SDConfig, logger log.Logger) (*robotDiscovery, erro
return d, nil
}
-func (d *robotDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
+func (d *robotDiscovery) refresh(context.Context) ([]*targetgroup.Group, error) {
req, err := http.NewRequest("GET", d.endpoint+"/server", nil)
if err != nil {
return nil, err
diff --git a/discovery/install/install.go b/discovery/install/install.go
index 36e13948d3..f090076b7f 100644
--- a/discovery/install/install.go
+++ b/discovery/install/install.go
@@ -33,6 +33,7 @@ import (
_ "github.com/prometheus/prometheus/discovery/moby" // register moby
_ "github.com/prometheus/prometheus/discovery/nomad" // register nomad
_ "github.com/prometheus/prometheus/discovery/openstack" // register openstack
+ _ "github.com/prometheus/prometheus/discovery/ovhcloud" // register ovhcloud
_ "github.com/prometheus/prometheus/discovery/puppetdb" // register puppetdb
_ "github.com/prometheus/prometheus/discovery/scaleway" // register scaleway
_ "github.com/prometheus/prometheus/discovery/triton" // register triton
diff --git a/discovery/ionos/server.go b/discovery/ionos/server.go
index 8ac3639705..a850fbbfb4 100644
--- a/discovery/ionos/server.go
+++ b/discovery/ionos/server.go
@@ -60,7 +60,7 @@ type serverDiscovery struct {
datacenterID string
}
-func newServerDiscovery(conf *SDConfig, logger log.Logger) (*serverDiscovery, error) {
+func newServerDiscovery(conf *SDConfig, _ log.Logger) (*serverDiscovery, error) {
d := &serverDiscovery{
port: conf.Port,
datacenterID: conf.DatacenterID,
diff --git a/discovery/kubernetes/client_metrics.go b/discovery/kubernetes/client_metrics.go
index 3a33e3e8d5..b316f7d885 100644
--- a/discovery/kubernetes/client_metrics.go
+++ b/discovery/kubernetes/client_metrics.go
@@ -122,11 +122,11 @@ func (f *clientGoRequestMetricAdapter) Register(registerer prometheus.Registerer
)
}
-func (clientGoRequestMetricAdapter) Increment(ctx context.Context, code, method, host string) {
+func (clientGoRequestMetricAdapter) Increment(_ context.Context, code, _, _ string) {
clientGoRequestResultMetricVec.WithLabelValues(code).Inc()
}
-func (clientGoRequestMetricAdapter) Observe(ctx context.Context, verb string, u url.URL, latency time.Duration) {
+func (clientGoRequestMetricAdapter) Observe(_ context.Context, _ string, u url.URL, latency time.Duration) {
clientGoRequestLatencyMetricVec.WithLabelValues(u.EscapedPath()).Observe(latency.Seconds())
}
@@ -169,7 +169,7 @@ func (f *clientGoWorkqueueMetricsProvider) NewLongestRunningProcessorSecondsMetr
return clientGoWorkqueueLongestRunningProcessorMetricVec.WithLabelValues(name)
}
-func (clientGoWorkqueueMetricsProvider) NewRetriesMetric(name string) workqueue.CounterMetric {
+func (clientGoWorkqueueMetricsProvider) NewRetriesMetric(string) workqueue.CounterMetric {
// Retries are not used so the metric is omitted.
return noopMetric{}
}
diff --git a/discovery/kubernetes/endpoints.go b/discovery/kubernetes/endpoints.go
index 1f39c23e71..3078cefde1 100644
--- a/discovery/kubernetes/endpoints.go
+++ b/discovery/kubernetes/endpoints.go
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// nolint:revive // Many legitimately empty blocks in this file.
package kubernetes
import (
@@ -72,7 +73,7 @@ func NewEndpoints(l log.Logger, eps cache.SharedIndexInformer, svc, pod, node ca
queue: workqueue.NewNamed("endpoints"),
}
- e.endpointsInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err := e.endpointsInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
epAddCount.Inc()
e.enqueue(o)
@@ -86,6 +87,9 @@ func NewEndpoints(l log.Logger, eps cache.SharedIndexInformer, svc, pod, node ca
e.enqueue(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding endpoints event handler.", "err", err)
+ }
serviceUpdate := func(o interface{}) {
svc, err := convertToService(o)
@@ -106,7 +110,7 @@ func NewEndpoints(l log.Logger, eps cache.SharedIndexInformer, svc, pod, node ca
level.Error(e.logger).Log("msg", "retrieving endpoints failed", "err", err)
}
}
- e.serviceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err = e.serviceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
// TODO(fabxc): potentially remove add and delete event handlers. Those should
// be triggered via the endpoint handlers already.
AddFunc: func(o interface{}) {
@@ -122,8 +126,11 @@ func NewEndpoints(l log.Logger, eps cache.SharedIndexInformer, svc, pod, node ca
serviceUpdate(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding services event handler.", "err", err)
+ }
if e.withNodeMetadata {
- e.nodeInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err = e.nodeInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
node := o.(*apiv1.Node)
e.enqueueNode(node.Name)
@@ -137,6 +144,9 @@ func NewEndpoints(l log.Logger, eps cache.SharedIndexInformer, svc, pod, node ca
e.enqueueNode(node.Name)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding nodes event handler.", "err", err)
+ }
}
return e
@@ -295,7 +305,11 @@ func (e *Endpoints) buildEndpoints(eps *apiv1.Endpoints) *targetgroup.Group {
}
if e.withNodeMetadata {
- target = addNodeLabels(target, e.nodeInf, e.logger, addr.NodeName)
+ if addr.NodeName != nil {
+ target = addNodeLabels(target, e.nodeInf, e.logger, addr.NodeName)
+ } else if addr.TargetRef != nil && addr.TargetRef.Kind == "Node" {
+ target = addNodeLabels(target, e.nodeInf, e.logger, &addr.TargetRef.Name)
+ }
}
pod := e.resolvePodRef(addr.TargetRef)
@@ -375,18 +389,21 @@ func (e *Endpoints) buildEndpoints(eps *apiv1.Endpoints) *targetgroup.Group {
continue
}
- a := net.JoinHostPort(pe.pod.Status.PodIP, strconv.FormatUint(uint64(cport.ContainerPort), 10))
- ports := strconv.FormatUint(uint64(cport.ContainerPort), 10)
+ // PodIP can be empty when a pod is starting or has been evicted.
+ if len(pe.pod.Status.PodIP) != 0 {
+ a := net.JoinHostPort(pe.pod.Status.PodIP, strconv.FormatUint(uint64(cport.ContainerPort), 10))
+ ports := strconv.FormatUint(uint64(cport.ContainerPort), 10)
- target := model.LabelSet{
- model.AddressLabel: lv(a),
- podContainerNameLabel: lv(c.Name),
- podContainerImageLabel: lv(c.Image),
- podContainerPortNameLabel: lv(cport.Name),
- podContainerPortNumberLabel: lv(ports),
- podContainerPortProtocolLabel: lv(string(cport.Protocol)),
+ target := model.LabelSet{
+ model.AddressLabel: lv(a),
+ podContainerNameLabel: lv(c.Name),
+ podContainerImageLabel: lv(c.Image),
+ podContainerPortNameLabel: lv(cport.Name),
+ podContainerPortNumberLabel: lv(ports),
+ podContainerPortProtocolLabel: lv(string(cport.Protocol)),
+ }
+ tg.Targets = append(tg.Targets, target.Merge(podLabels(pe.pod)))
}
- tg.Targets = append(tg.Targets, target.Merge(podLabels(pe.pod)))
}
}
}
@@ -456,5 +473,6 @@ func addNodeLabels(tg model.LabelSet, nodeInf cache.SharedInformer, logger log.L
nodeLabelset[model.LabelName(nodeLabelPrefix+ln)] = lv(v)
nodeLabelset[model.LabelName(nodeLabelPresentPrefix+ln)] = presentValue
}
+
return tg.Merge(nodeLabelset)
}
diff --git a/discovery/kubernetes/endpoints_test.go b/discovery/kubernetes/endpoints_test.go
index 91b1b0c676..256ffce1b8 100644
--- a/discovery/kubernetes/endpoints_test.go
+++ b/discovery/kubernetes/endpoints_test.go
@@ -69,6 +69,24 @@ func makeEndpoints() *v1.Endpoints {
},
},
},
+ {
+ Addresses: []v1.EndpointAddress{
+ {
+ IP: "6.7.8.9",
+ TargetRef: &v1.ObjectReference{
+ Kind: "Node",
+ Name: "barbaz",
+ },
+ },
+ },
+ Ports: []v1.EndpointPort{
+ {
+ Name: "testport",
+ Port: 9002,
+ Protocol: v1.ProtocolTCP,
+ },
+ },
+ },
},
}
}
@@ -106,6 +124,14 @@ func TestEndpointsDiscoveryBeforeRun(t *testing.T) {
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "false",
},
+ {
+ "__address__": "6.7.8.9:9002",
+ "__meta_kubernetes_endpoint_address_target_kind": "Node",
+ "__meta_kubernetes_endpoint_address_target_name": "barbaz",
+ "__meta_kubernetes_endpoint_port_name": "testport",
+ "__meta_kubernetes_endpoint_port_protocol": "TCP",
+ "__meta_kubernetes_endpoint_ready": "true",
+ },
},
Labels: model.LabelSet{
"__meta_kubernetes_namespace": "default",
@@ -398,6 +424,14 @@ func TestEndpointsDiscoveryWithService(t *testing.T) {
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "false",
},
+ {
+ "__address__": "6.7.8.9:9002",
+ "__meta_kubernetes_endpoint_address_target_kind": "Node",
+ "__meta_kubernetes_endpoint_address_target_name": "barbaz",
+ "__meta_kubernetes_endpoint_port_name": "testport",
+ "__meta_kubernetes_endpoint_port_protocol": "TCP",
+ "__meta_kubernetes_endpoint_ready": "true",
+ },
},
Labels: model.LabelSet{
"__meta_kubernetes_namespace": "default",
@@ -466,6 +500,14 @@ func TestEndpointsDiscoveryWithServiceUpdate(t *testing.T) {
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "false",
},
+ {
+ "__address__": "6.7.8.9:9002",
+ "__meta_kubernetes_endpoint_address_target_kind": "Node",
+ "__meta_kubernetes_endpoint_address_target_name": "barbaz",
+ "__meta_kubernetes_endpoint_port_name": "testport",
+ "__meta_kubernetes_endpoint_port_protocol": "TCP",
+ "__meta_kubernetes_endpoint_ready": "true",
+ },
},
Labels: model.LabelSet{
"__meta_kubernetes_namespace": "default",
@@ -484,8 +526,10 @@ func TestEndpointsDiscoveryWithServiceUpdate(t *testing.T) {
func TestEndpointsDiscoveryWithNodeMetadata(t *testing.T) {
metadataConfig := AttachMetadataConfig{Node: true}
- nodeLabels := map[string]string{"az": "us-east1"}
- node := makeNode("foobar", "", "", nodeLabels, nil)
+ nodeLabels1 := map[string]string{"az": "us-east1"}
+ nodeLabels2 := map[string]string{"az": "us-west2"}
+ node1 := makeNode("foobar", "", "", nodeLabels1, nil)
+ node2 := makeNode("barbaz", "", "", nodeLabels2, nil)
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "testendpoints",
@@ -495,7 +539,7 @@ func TestEndpointsDiscoveryWithNodeMetadata(t *testing.T) {
},
},
}
- n, _ := makeDiscoveryWithMetadata(RoleEndpoint, NamespaceDiscovery{}, metadataConfig, makeEndpoints(), svc, node)
+ n, _ := makeDiscoveryWithMetadata(RoleEndpoint, NamespaceDiscovery{}, metadataConfig, makeEndpoints(), svc, node1, node2)
k8sDiscoveryTest{
discovery: n,
@@ -526,6 +570,17 @@ func TestEndpointsDiscoveryWithNodeMetadata(t *testing.T) {
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "false",
},
+ {
+ "__address__": "6.7.8.9:9002",
+ "__meta_kubernetes_endpoint_address_target_kind": "Node",
+ "__meta_kubernetes_endpoint_address_target_name": "barbaz",
+ "__meta_kubernetes_endpoint_port_name": "testport",
+ "__meta_kubernetes_endpoint_port_protocol": "TCP",
+ "__meta_kubernetes_endpoint_ready": "true",
+ "__meta_kubernetes_node_label_az": "us-west2",
+ "__meta_kubernetes_node_labelpresent_az": "true",
+ "__meta_kubernetes_node_name": "barbaz",
+ },
},
Labels: model.LabelSet{
"__meta_kubernetes_namespace": "default",
@@ -541,8 +596,10 @@ func TestEndpointsDiscoveryWithNodeMetadata(t *testing.T) {
}
func TestEndpointsDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
- nodeLabels := map[string]string{"az": "us-east1"}
- nodes := makeNode("foobar", "", "", nodeLabels, nil)
+ nodeLabels1 := map[string]string{"az": "us-east1"}
+ nodeLabels2 := map[string]string{"az": "us-west2"}
+ node1 := makeNode("foobar", "", "", nodeLabels1, nil)
+ node2 := makeNode("barbaz", "", "", nodeLabels2, nil)
metadataConfig := AttachMetadataConfig{Node: true}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
@@ -553,13 +610,13 @@ func TestEndpointsDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
},
},
}
- n, c := makeDiscoveryWithMetadata(RoleEndpoint, NamespaceDiscovery{}, metadataConfig, makeEndpoints(), nodes, svc)
+ n, c := makeDiscoveryWithMetadata(RoleEndpoint, NamespaceDiscovery{}, metadataConfig, makeEndpoints(), node1, node2, svc)
k8sDiscoveryTest{
discovery: n,
afterStart: func() {
- nodes.Labels["az"] = "eu-central1"
- c.CoreV1().Nodes().Update(context.Background(), nodes, metav1.UpdateOptions{})
+ node1.Labels["az"] = "eu-central1"
+ c.CoreV1().Nodes().Update(context.Background(), node1, metav1.UpdateOptions{})
},
expectedMaxItems: 2,
expectedRes: map[string]*targetgroup.Group{
@@ -572,7 +629,7 @@ func TestEndpointsDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
"__meta_kubernetes_endpoint_port_name": "testport",
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "true",
- "__meta_kubernetes_node_label_az": "eu-central1",
+ "__meta_kubernetes_node_label_az": "us-east1",
"__meta_kubernetes_node_labelpresent_az": "true",
"__meta_kubernetes_node_name": "foobar",
},
@@ -588,6 +645,17 @@ func TestEndpointsDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "false",
},
+ {
+ "__address__": "6.7.8.9:9002",
+ "__meta_kubernetes_endpoint_address_target_kind": "Node",
+ "__meta_kubernetes_endpoint_address_target_name": "barbaz",
+ "__meta_kubernetes_endpoint_port_name": "testport",
+ "__meta_kubernetes_endpoint_port_protocol": "TCP",
+ "__meta_kubernetes_endpoint_ready": "true",
+ "__meta_kubernetes_node_label_az": "us-west2",
+ "__meta_kubernetes_node_labelpresent_az": "true",
+ "__meta_kubernetes_node_name": "barbaz",
+ },
},
Labels: model.LabelSet{
"__meta_kubernetes_namespace": "default",
@@ -699,6 +767,14 @@ func TestEndpointsDiscoveryNamespaces(t *testing.T) {
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "false",
},
+ {
+ "__address__": "6.7.8.9:9002",
+ "__meta_kubernetes_endpoint_address_target_kind": "Node",
+ "__meta_kubernetes_endpoint_address_target_name": "barbaz",
+ "__meta_kubernetes_endpoint_port_name": "testport",
+ "__meta_kubernetes_endpoint_port_protocol": "TCP",
+ "__meta_kubernetes_endpoint_ready": "true",
+ },
},
Labels: model.LabelSet{
"__meta_kubernetes_namespace": "ns1",
@@ -815,6 +891,14 @@ func TestEndpointsDiscoveryOwnNamespace(t *testing.T) {
"__meta_kubernetes_endpoint_port_protocol": "TCP",
"__meta_kubernetes_endpoint_ready": "false",
},
+ {
+ "__address__": "6.7.8.9:9002",
+ "__meta_kubernetes_endpoint_address_target_kind": "Node",
+ "__meta_kubernetes_endpoint_address_target_name": "barbaz",
+ "__meta_kubernetes_endpoint_port_name": "testport",
+ "__meta_kubernetes_endpoint_port_protocol": "TCP",
+ "__meta_kubernetes_endpoint_ready": "true",
+ },
},
Labels: model.LabelSet{
"__meta_kubernetes_namespace": "own-ns",
@@ -825,3 +909,46 @@ func TestEndpointsDiscoveryOwnNamespace(t *testing.T) {
},
}.Run(t)
}
+
+func TestEndpointsDiscoveryEmptyPodStatus(t *testing.T) {
+ ep := makeEndpoints()
+ ep.Namespace = "ns"
+
+ pod := &v1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "testpod",
+ Namespace: "ns",
+ UID: types.UID("deadbeef"),
+ },
+ Spec: v1.PodSpec{
+ NodeName: "testnode",
+ Containers: []v1.Container{
+ {
+ Name: "p1",
+ Image: "p1:latest",
+ Ports: []v1.ContainerPort{
+ {
+ Name: "mainport",
+ ContainerPort: 9000,
+ Protocol: v1.ProtocolTCP,
+ },
+ },
+ },
+ },
+ },
+ Status: v1.PodStatus{},
+ }
+
+ objs := []runtime.Object{
+ ep,
+ pod,
+ }
+
+ n, _ := makeDiscovery(RoleEndpoint, NamespaceDiscovery{IncludeOwnNamespace: true}, objs...)
+
+ k8sDiscoveryTest{
+ discovery: n,
+ expectedMaxItems: 0,
+ expectedRes: map[string]*targetgroup.Group{},
+ }.Run(t)
+}
diff --git a/discovery/kubernetes/endpointslice.go b/discovery/kubernetes/endpointslice.go
index 594759f454..1eb3fc1807 100644
--- a/discovery/kubernetes/endpointslice.go
+++ b/discovery/kubernetes/endpointslice.go
@@ -73,7 +73,7 @@ func NewEndpointSlice(l log.Logger, eps cache.SharedIndexInformer, svc, pod, nod
queue: workqueue.NewNamed("endpointSlice"),
}
- e.endpointSliceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err := e.endpointSliceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
epslAddCount.Inc()
e.enqueue(o)
@@ -87,6 +87,9 @@ func NewEndpointSlice(l log.Logger, eps cache.SharedIndexInformer, svc, pod, nod
e.enqueue(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding endpoint slices event handler.", "err", err)
+ }
serviceUpdate := func(o interface{}) {
svc, err := convertToService(o)
@@ -109,7 +112,7 @@ func NewEndpointSlice(l log.Logger, eps cache.SharedIndexInformer, svc, pod, nod
}
}
}
- e.serviceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err = e.serviceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
svcAddCount.Inc()
serviceUpdate(o)
@@ -123,9 +126,12 @@ func NewEndpointSlice(l log.Logger, eps cache.SharedIndexInformer, svc, pod, nod
serviceUpdate(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding services event handler.", "err", err)
+ }
if e.withNodeMetadata {
- e.nodeInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err = e.nodeInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
node := o.(*apiv1.Node)
e.enqueueNode(node.Name)
@@ -139,6 +145,9 @@ func NewEndpointSlice(l log.Logger, eps cache.SharedIndexInformer, svc, pod, nod
e.enqueueNode(node.Name)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding nodes event handler.", "err", err)
+ }
}
return e
@@ -181,7 +190,7 @@ func (e *EndpointSlice) Run(ctx context.Context, ch chan<- []*targetgroup.Group)
}
go func() {
- for e.process(ctx, ch) {
+ for e.process(ctx, ch) { // nolint:revive
}
}()
@@ -250,6 +259,8 @@ const (
endpointSlicePortLabel = metaLabelPrefix + "endpointslice_port"
endpointSlicePortAppProtocol = metaLabelPrefix + "endpointslice_port_app_protocol"
endpointSliceEndpointConditionsReadyLabel = metaLabelPrefix + "endpointslice_endpoint_conditions_ready"
+ endpointSliceEndpointConditionsServingLabel = metaLabelPrefix + "endpointslice_endpoint_conditions_serving"
+ endpointSliceEndpointConditionsTerminatingLabel = metaLabelPrefix + "endpointslice_endpoint_conditions_terminating"
endpointSliceEndpointHostnameLabel = metaLabelPrefix + "endpointslice_endpoint_hostname"
endpointSliceAddressTargetKindLabel = metaLabelPrefix + "endpointslice_address_target_kind"
endpointSliceAddressTargetNameLabel = metaLabelPrefix + "endpointslice_address_target_name"
@@ -289,7 +300,7 @@ func (e *EndpointSlice) buildEndpointSlice(eps endpointSliceAdaptor) *targetgrou
}
if port.protocol() != nil {
- target[endpointSlicePortProtocolLabel] = lv(string(*port.protocol()))
+ target[endpointSlicePortProtocolLabel] = lv(*port.protocol())
}
if port.port() != nil {
@@ -304,6 +315,14 @@ func (e *EndpointSlice) buildEndpointSlice(eps endpointSliceAdaptor) *targetgrou
target[endpointSliceEndpointConditionsReadyLabel] = lv(strconv.FormatBool(*ep.conditions().ready()))
}
+ if ep.conditions().serving() != nil {
+ target[endpointSliceEndpointConditionsServingLabel] = lv(strconv.FormatBool(*ep.conditions().serving()))
+ }
+
+ if ep.conditions().terminating() != nil {
+ target[endpointSliceEndpointConditionsTerminatingLabel] = lv(strconv.FormatBool(*ep.conditions().terminating()))
+ }
+
if ep.hostname() != nil {
target[endpointSliceEndpointHostnameLabel] = lv(*ep.hostname())
}
@@ -320,7 +339,11 @@ func (e *EndpointSlice) buildEndpointSlice(eps endpointSliceAdaptor) *targetgrou
}
if e.withNodeMetadata {
- target = addNodeLabels(target, e.nodeInf, e.logger, ep.nodename())
+ if ep.targetRef() != nil && ep.targetRef().Kind == "Node" {
+ target = addNodeLabels(target, e.nodeInf, e.logger, &ep.targetRef().Name)
+ } else {
+ target = addNodeLabels(target, e.nodeInf, e.logger, ep.nodename())
+ }
}
pod := e.resolvePodRef(ep.targetRef())
@@ -393,18 +416,21 @@ func (e *EndpointSlice) buildEndpointSlice(eps endpointSliceAdaptor) *targetgrou
continue
}
- a := net.JoinHostPort(pe.pod.Status.PodIP, strconv.FormatUint(uint64(cport.ContainerPort), 10))
- ports := strconv.FormatUint(uint64(cport.ContainerPort), 10)
+ // PodIP can be empty when a pod is starting or has been evicted.
+ if len(pe.pod.Status.PodIP) != 0 {
+ a := net.JoinHostPort(pe.pod.Status.PodIP, strconv.FormatUint(uint64(cport.ContainerPort), 10))
+ ports := strconv.FormatUint(uint64(cport.ContainerPort), 10)
- target := model.LabelSet{
- model.AddressLabel: lv(a),
- podContainerNameLabel: lv(c.Name),
- podContainerImageLabel: lv(c.Image),
- podContainerPortNameLabel: lv(cport.Name),
- podContainerPortNumberLabel: lv(ports),
- podContainerPortProtocolLabel: lv(string(cport.Protocol)),
+ target := model.LabelSet{
+ model.AddressLabel: lv(a),
+ podContainerNameLabel: lv(c.Name),
+ podContainerImageLabel: lv(c.Image),
+ podContainerPortNameLabel: lv(cport.Name),
+ podContainerPortNumberLabel: lv(ports),
+ podContainerPortProtocolLabel: lv(string(cport.Protocol)),
+ }
+ tg.Targets = append(tg.Targets, target.Merge(podLabels(pe.pod)))
}
- tg.Targets = append(tg.Targets, target.Merge(podLabels(pe.pod)))
}
}
}
diff --git a/discovery/kubernetes/endpointslice_adaptor.go b/discovery/kubernetes/endpointslice_adaptor.go
index 87484b06fd..5a21f1b899 100644
--- a/discovery/kubernetes/endpointslice_adaptor.go
+++ b/discovery/kubernetes/endpointslice_adaptor.go
@@ -49,6 +49,8 @@ type endpointSliceEndpointAdaptor interface {
type endpointSliceEndpointConditionsAdaptor interface {
ready() *bool
+ serving() *bool
+ terminating() *bool
}
// Adaptor for k8s.io/api/discovery/v1
@@ -193,6 +195,14 @@ func (e *endpointSliceEndpointConditionsAdaptorV1) ready() *bool {
return e.endpointConditions.Ready
}
+func (e *endpointSliceEndpointConditionsAdaptorV1) serving() *bool {
+ return e.endpointConditions.Serving
+}
+
+func (e *endpointSliceEndpointConditionsAdaptorV1) terminating() *bool {
+ return e.endpointConditions.Terminating
+}
+
type endpointSliceEndpointAdaptorV1beta1 struct {
endpoint v1beta1.Endpoint
}
@@ -237,6 +247,14 @@ func (e *endpointSliceEndpointConditionsAdaptorV1beta1) ready() *bool {
return e.endpointConditions.Ready
}
+func (e *endpointSliceEndpointConditionsAdaptorV1beta1) serving() *bool {
+ return e.endpointConditions.Serving
+}
+
+func (e *endpointSliceEndpointConditionsAdaptorV1beta1) terminating() *bool {
+ return e.endpointConditions.Terminating
+}
+
type endpointSlicePortAdaptorV1 struct {
endpointPort v1.EndpointPort
}
diff --git a/discovery/kubernetes/endpointslice_adaptor_test.go b/discovery/kubernetes/endpointslice_adaptor_test.go
index 9a2a003123..e564910936 100644
--- a/discovery/kubernetes/endpointslice_adaptor_test.go
+++ b/discovery/kubernetes/endpointslice_adaptor_test.go
@@ -35,6 +35,8 @@ func Test_EndpointSliceAdaptor_v1(t *testing.T) {
require.Equal(t, endpointSlice.Endpoints[i].Addresses, endpointAdaptor.addresses())
require.Equal(t, endpointSlice.Endpoints[i].Hostname, endpointAdaptor.hostname())
require.Equal(t, endpointSlice.Endpoints[i].Conditions.Ready, endpointAdaptor.conditions().ready())
+ require.Equal(t, endpointSlice.Endpoints[i].Conditions.Serving, endpointAdaptor.conditions().serving())
+ require.Equal(t, endpointSlice.Endpoints[i].Conditions.Terminating, endpointAdaptor.conditions().terminating())
require.Equal(t, endpointSlice.Endpoints[i].TargetRef, endpointAdaptor.targetRef())
require.Equal(t, endpointSlice.Endpoints[i].DeprecatedTopology, endpointAdaptor.topology())
}
@@ -61,6 +63,8 @@ func Test_EndpointSliceAdaptor_v1beta1(t *testing.T) {
require.Equal(t, endpointSlice.Endpoints[i].Addresses, endpointAdaptor.addresses())
require.Equal(t, endpointSlice.Endpoints[i].Hostname, endpointAdaptor.hostname())
require.Equal(t, endpointSlice.Endpoints[i].Conditions.Ready, endpointAdaptor.conditions().ready())
+ require.Equal(t, endpointSlice.Endpoints[i].Conditions.Serving, endpointAdaptor.conditions().serving())
+ require.Equal(t, endpointSlice.Endpoints[i].Conditions.Terminating, endpointAdaptor.conditions().terminating())
require.Equal(t, endpointSlice.Endpoints[i].TargetRef, endpointAdaptor.targetRef())
require.Equal(t, endpointSlice.Endpoints[i].Topology, endpointAdaptor.topology())
}
diff --git a/discovery/kubernetes/endpointslice_test.go b/discovery/kubernetes/endpointslice_test.go
index a0ae543fc9..0c31147a93 100644
--- a/discovery/kubernetes/endpointslice_test.go
+++ b/discovery/kubernetes/endpointslice_test.go
@@ -64,23 +64,42 @@ func makeEndpointSliceV1() *v1.EndpointSlice {
},
Endpoints: []v1.Endpoint{
{
- Addresses: []string{"1.2.3.4"},
- Conditions: v1.EndpointConditions{Ready: boolptr(true)},
- Hostname: strptr("testendpoint1"),
- TargetRef: &corev1.ObjectReference{},
- NodeName: strptr("foobar"),
+ Addresses: []string{"1.2.3.4"},
+ Conditions: v1.EndpointConditions{
+ Ready: boolptr(true),
+ Serving: boolptr(true),
+ Terminating: boolptr(false),
+ },
+ Hostname: strptr("testendpoint1"),
+ TargetRef: &corev1.ObjectReference{},
+ NodeName: strptr("foobar"),
DeprecatedTopology: map[string]string{
"topology": "value",
},
}, {
Addresses: []string{"2.3.4.5"},
Conditions: v1.EndpointConditions{
- Ready: boolptr(true),
+ Ready: boolptr(true),
+ Serving: boolptr(true),
+ Terminating: boolptr(false),
},
}, {
Addresses: []string{"3.4.5.6"},
Conditions: v1.EndpointConditions{
- Ready: boolptr(false),
+ Ready: boolptr(false),
+ Serving: boolptr(true),
+ Terminating: boolptr(true),
+ },
+ }, {
+ Addresses: []string{"4.5.6.7"},
+ Conditions: v1.EndpointConditions{
+ Ready: boolptr(true),
+ Serving: boolptr(true),
+ Terminating: boolptr(false),
+ },
+ TargetRef: &corev1.ObjectReference{
+ Kind: "Node",
+ Name: "barbaz",
},
},
},
@@ -111,12 +130,27 @@ func makeEndpointSliceV1beta1() *v1beta1.EndpointSlice {
}, {
Addresses: []string{"2.3.4.5"},
Conditions: v1beta1.EndpointConditions{
- Ready: boolptr(true),
+ Ready: boolptr(true),
+ Serving: boolptr(true),
+ Terminating: boolptr(false),
},
}, {
Addresses: []string{"3.4.5.6"},
Conditions: v1beta1.EndpointConditions{
- Ready: boolptr(false),
+ Ready: boolptr(false),
+ Serving: boolptr(true),
+ Terminating: boolptr(true),
+ },
+ }, {
+ Addresses: []string{"4.5.6.7"},
+ Conditions: v1beta1.EndpointConditions{
+ Ready: boolptr(true),
+ Serving: boolptr(true),
+ Terminating: boolptr(false),
+ },
+ TargetRef: &corev1.ObjectReference{
+ Kind: "Node",
+ Name: "barbaz",
},
},
},
@@ -141,6 +175,8 @@ func TestEndpointSliceDiscoveryBeforeRun(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -151,19 +187,35 @@ func TestEndpointSliceDiscoveryBeforeRun(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -199,17 +251,32 @@ func TestEndpointSliceDiscoveryBeforeRunV1beta1(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -367,6 +434,8 @@ func TestEndpointSliceDiscoveryDelete(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -377,19 +446,35 @@ func TestEndpointSliceDiscoveryDelete(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: map[model.LabelName]model.LabelValue{
@@ -445,6 +530,8 @@ func TestEndpointSliceDiscoveryUpdate(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -455,19 +542,35 @@ func TestEndpointSliceDiscoveryUpdate(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -512,6 +615,8 @@ func TestEndpointSliceDiscoveryEmptyEndpoints(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -522,19 +627,35 @@ func TestEndpointSliceDiscoveryEmptyEndpoints(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -574,6 +695,8 @@ func TestEndpointSliceDiscoveryWithService(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -584,19 +707,35 @@ func TestEndpointSliceDiscoveryWithService(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -652,6 +791,8 @@ func TestEndpointSliceDiscoveryWithServiceUpdate(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -662,19 +803,35 @@ func TestEndpointSliceDiscoveryWithServiceUpdate(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -695,7 +852,8 @@ func TestEndpointSliceDiscoveryWithServiceUpdate(t *testing.T) {
func TestEndpointsSlicesDiscoveryWithNodeMetadata(t *testing.T) {
metadataConfig := AttachMetadataConfig{Node: true}
- nodeLabels := map[string]string{"az": "us-east1"}
+ nodeLabels1 := map[string]string{"az": "us-east1"}
+ nodeLabels2 := map[string]string{"az": "us-west2"}
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "testendpoints",
@@ -705,7 +863,7 @@ func TestEndpointsSlicesDiscoveryWithNodeMetadata(t *testing.T) {
},
},
}
- objs := []runtime.Object{makeEndpointSliceV1(), makeNode("foobar", "", "", nodeLabels, nil), svc}
+ objs := []runtime.Object{makeEndpointSliceV1(), makeNode("foobar", "", "", nodeLabels1, nil), makeNode("barbaz", "", "", nodeLabels2, nil), svc}
n, _ := makeDiscoveryWithMetadata(RoleEndpointSlice, NamespaceDiscovery{}, metadataConfig, objs...)
k8sDiscoveryTest{
@@ -719,6 +877,8 @@ func TestEndpointsSlicesDiscoveryWithNodeMetadata(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -732,19 +892,38 @@ func TestEndpointsSlicesDiscoveryWithNodeMetadata(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_node_label_az": "us-west2",
+ "__meta_kubernetes_node_labelpresent_az": "true",
+ "__meta_kubernetes_node_name": "barbaz",
},
},
Labels: model.LabelSet{
@@ -763,7 +942,8 @@ func TestEndpointsSlicesDiscoveryWithNodeMetadata(t *testing.T) {
func TestEndpointsSlicesDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
metadataConfig := AttachMetadataConfig{Node: true}
- nodeLabels := map[string]string{"az": "us-east1"}
+ nodeLabels1 := map[string]string{"az": "us-east1"}
+ nodeLabels2 := map[string]string{"az": "us-west2"}
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "testendpoints",
@@ -773,16 +953,17 @@ func TestEndpointsSlicesDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
},
},
}
- node := makeNode("foobar", "", "", nodeLabels, nil)
- objs := []runtime.Object{makeEndpointSliceV1(), node, svc}
+ node1 := makeNode("foobar", "", "", nodeLabels1, nil)
+ node2 := makeNode("barbaz", "", "", nodeLabels2, nil)
+ objs := []runtime.Object{makeEndpointSliceV1(), node1, node2, svc}
n, c := makeDiscoveryWithMetadata(RoleEndpointSlice, NamespaceDiscovery{}, metadataConfig, objs...)
k8sDiscoveryTest{
discovery: n,
expectedMaxItems: 2,
afterStart: func() {
- node.Labels["az"] = "us-central1"
- c.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{})
+ node1.Labels["az"] = "us-central1"
+ c.CoreV1().Nodes().Update(context.Background(), node1, metav1.UpdateOptions{})
},
expectedRes: map[string]*targetgroup.Group{
"endpointslice/default/testendpoints": {
@@ -792,6 +973,8 @@ func TestEndpointsSlicesDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -799,25 +982,44 @@ func TestEndpointsSlicesDiscoveryWithUpdatedNodeMetadata(t *testing.T) {
"__meta_kubernetes_endpointslice_port_app_protocol": "http",
"__meta_kubernetes_endpointslice_port_name": "testport",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
- "__meta_kubernetes_node_label_az": "us-central1",
+ "__meta_kubernetes_node_label_az": "us-east1",
"__meta_kubernetes_node_labelpresent_az": "true",
"__meta_kubernetes_node_name": "foobar",
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_node_label_az": "us-west2",
+ "__meta_kubernetes_node_labelpresent_az": "true",
+ "__meta_kubernetes_node_name": "barbaz",
},
},
Labels: model.LabelSet{
@@ -913,6 +1115,8 @@ func TestEndpointSliceDiscoveryNamespaces(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -923,19 +1127,35 @@ func TestEndpointSliceDiscoveryNamespaces(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -1039,6 +1259,8 @@ func TestEndpointSliceDiscoveryOwnNamespace(t *testing.T) {
"__meta_kubernetes_endpointslice_address_target_kind": "",
"__meta_kubernetes_endpointslice_address_target_name": "",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_endpoint_hostname": "testendpoint1",
"__meta_kubernetes_endpointslice_endpoint_topology_present_topology": "true",
"__meta_kubernetes_endpointslice_endpoint_topology_topology": "value",
@@ -1049,19 +1271,35 @@ func TestEndpointSliceDiscoveryOwnNamespace(t *testing.T) {
},
{
"__address__": "2.3.4.5:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
},
{
"__address__": "3.4.5.6:9000",
- "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
- "__meta_kubernetes_endpointslice_port": "9000",
- "__meta_kubernetes_endpointslice_port_name": "testport",
- "__meta_kubernetes_endpointslice_port_protocol": "TCP",
- "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "false",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "true",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ },
+ {
+ "__address__": "4.5.6.7:9000",
+ "__meta_kubernetes_endpointslice_address_target_kind": "Node",
+ "__meta_kubernetes_endpointslice_address_target_name": "barbaz",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
+ "__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
+ "__meta_kubernetes_endpointslice_port": "9000",
+ "__meta_kubernetes_endpointslice_port_app_protocol": "http",
+ "__meta_kubernetes_endpointslice_port_name": "testport",
+ "__meta_kubernetes_endpointslice_port_protocol": "TCP",
},
},
Labels: model.LabelSet{
@@ -1074,3 +1312,46 @@ func TestEndpointSliceDiscoveryOwnNamespace(t *testing.T) {
},
}.Run(t)
}
+
+func TestEndpointSliceDiscoveryEmptyPodStatus(t *testing.T) {
+ ep := makeEndpointSliceV1()
+ ep.Namespace = "ns"
+
+ pod := &corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "testpod",
+ Namespace: "ns",
+ UID: types.UID("deadbeef"),
+ },
+ Spec: corev1.PodSpec{
+ NodeName: "testnode",
+ Containers: []corev1.Container{
+ {
+ Name: "p1",
+ Image: "p1:latest",
+ Ports: []corev1.ContainerPort{
+ {
+ Name: "mainport",
+ ContainerPort: 9000,
+ Protocol: corev1.ProtocolTCP,
+ },
+ },
+ },
+ },
+ },
+ Status: corev1.PodStatus{},
+ }
+
+ objs := []runtime.Object{
+ ep,
+ pod,
+ }
+
+ n, _ := makeDiscovery(RoleEndpoint, NamespaceDiscovery{IncludeOwnNamespace: true}, objs...)
+
+ k8sDiscoveryTest{
+ discovery: n,
+ expectedMaxItems: 0,
+ expectedRes: map[string]*targetgroup.Group{},
+ }.Run(t)
+}
diff --git a/discovery/kubernetes/ingress.go b/discovery/kubernetes/ingress.go
index de6d2a0b4d..ad47c341a5 100644
--- a/discovery/kubernetes/ingress.go
+++ b/discovery/kubernetes/ingress.go
@@ -48,7 +48,7 @@ type Ingress struct {
// NewIngress returns a new ingress discovery.
func NewIngress(l log.Logger, inf cache.SharedInformer) *Ingress {
s := &Ingress{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("ingress")}
- s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err := s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
ingressAddCount.Inc()
s.enqueue(o)
@@ -62,6 +62,9 @@ func NewIngress(l log.Logger, inf cache.SharedInformer) *Ingress {
s.enqueue(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding ingresses event handler.", "err", err)
+ }
return s
}
@@ -86,7 +89,7 @@ func (i *Ingress) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
}
go func() {
- for i.process(ctx, ch) {
+ for i.process(ctx, ch) { // nolint:revive
}
}()
diff --git a/discovery/kubernetes/kubernetes.go b/discovery/kubernetes/kubernetes.go
index 0ffedc51ed..e87a1c9b24 100644
--- a/discovery/kubernetes/kubernetes.go
+++ b/discovery/kubernetes/kubernetes.go
@@ -299,12 +299,13 @@ func New(l log.Logger, conf *SDConfig) (*Discovery, error) {
err error
ownNamespace string
)
- if conf.KubeConfig != "" {
+ switch {
+ case conf.KubeConfig != "":
kcfg, err = clientcmd.BuildConfigFromFlags("", conf.KubeConfig)
if err != nil {
return nil, err
}
- } else if conf.APIServer.URL == nil {
+ case conf.APIServer.URL == nil:
// Use the Kubernetes provided pod service account
// as described in https://kubernetes.io/docs/admin/service-accounts-admin/
kcfg, err = rest.InClusterConfig()
@@ -324,7 +325,7 @@ func New(l log.Logger, conf *SDConfig) (*Discovery, error) {
}
level.Info(l).Log("msg", "Using pod service account via in-cluster config")
- } else {
+ default:
rt, err := config.NewRoundTripperFromConfig(conf.HTTPClientConfig, "kubernetes_sd")
if err != nil {
return nil, err
@@ -382,7 +383,8 @@ func mapSelector(rawSelector []SelectorConfig) roleSelector {
return rs
}
-const resyncPeriod = 10 * time.Minute
+// Disable the informer's resync, which just periodically resends already processed updates and distort SD metrics.
+const resyncDisabled = 0
// Run implements the discoverer interface.
func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
@@ -475,8 +477,8 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
eps := NewEndpointSlice(
log.With(d.logger, "role", "endpointslice"),
informer,
- cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
- cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
+ cache.NewSharedInformer(slw, &apiv1.Service{}, resyncDisabled),
+ cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncDisabled),
nodeInf,
)
d.discoverers = append(d.discoverers, eps)
@@ -534,8 +536,8 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
eps := NewEndpoints(
log.With(d.logger, "role", "endpoint"),
d.newEndpointsByNodeInformer(elw),
- cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
- cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
+ cache.NewSharedInformer(slw, &apiv1.Service{}, resyncDisabled),
+ cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncDisabled),
nodeInf,
)
d.discoverers = append(d.discoverers, eps)
@@ -589,7 +591,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
}
svc := NewService(
log.With(d.logger, "role", "service"),
- cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
+ cache.NewSharedInformer(slw, &apiv1.Service{}, resyncDisabled),
)
d.discoverers = append(d.discoverers, svc)
go svc.informer.Run(ctx.Done())
@@ -627,7 +629,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
return i.Watch(ctx, options)
},
}
- informer = cache.NewSharedInformer(ilw, &networkv1.Ingress{}, resyncPeriod)
+ informer = cache.NewSharedInformer(ilw, &networkv1.Ingress{}, resyncDisabled)
} else {
i := d.client.NetworkingV1beta1().Ingresses(namespace)
ilw := &cache.ListWatch{
@@ -642,7 +644,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
return i.Watch(ctx, options)
},
}
- informer = cache.NewSharedInformer(ilw, &v1beta1.Ingress{}, resyncPeriod)
+ informer = cache.NewSharedInformer(ilw, &v1beta1.Ingress{}, resyncDisabled)
}
ingress := NewIngress(
log.With(d.logger, "role", "ingress"),
@@ -732,7 +734,7 @@ func (d *Discovery) newNodeInformer(ctx context.Context) cache.SharedInformer {
return d.client.CoreV1().Nodes().Watch(ctx, options)
},
}
- return cache.NewSharedInformer(nlw, &apiv1.Node{}, resyncPeriod)
+ return cache.NewSharedInformer(nlw, &apiv1.Node{}, resyncDisabled)
}
func (d *Discovery) newPodsByNodeInformer(plw *cache.ListWatch) cache.SharedIndexInformer {
@@ -747,39 +749,45 @@ func (d *Discovery) newPodsByNodeInformer(plw *cache.ListWatch) cache.SharedInde
}
}
- return cache.NewSharedIndexInformer(plw, &apiv1.Pod{}, resyncPeriod, indexers)
+ return cache.NewSharedIndexInformer(plw, &apiv1.Pod{}, resyncDisabled, indexers)
}
func (d *Discovery) newEndpointsByNodeInformer(plw *cache.ListWatch) cache.SharedIndexInformer {
indexers := make(map[string]cache.IndexFunc)
if !d.attachMetadata.Node {
- return cache.NewSharedIndexInformer(plw, &apiv1.Endpoints{}, resyncPeriod, indexers)
+ return cache.NewSharedIndexInformer(plw, &apiv1.Endpoints{}, resyncDisabled, indexers)
}
indexers[nodeIndex] = func(obj interface{}) ([]string, error) {
e, ok := obj.(*apiv1.Endpoints)
if !ok {
- return nil, fmt.Errorf("object is not a pod")
+ return nil, fmt.Errorf("object is not endpoints")
}
var nodes []string
for _, target := range e.Subsets {
for _, addr := range target.Addresses {
- if addr.NodeName == nil {
- continue
+ if addr.TargetRef != nil {
+ switch addr.TargetRef.Kind {
+ case "Pod":
+ if addr.NodeName != nil {
+ nodes = append(nodes, *addr.NodeName)
+ }
+ case "Node":
+ nodes = append(nodes, addr.TargetRef.Name)
+ }
}
- nodes = append(nodes, *addr.NodeName)
}
}
return nodes, nil
}
- return cache.NewSharedIndexInformer(plw, &apiv1.Endpoints{}, resyncPeriod, indexers)
+ return cache.NewSharedIndexInformer(plw, &apiv1.Endpoints{}, resyncDisabled, indexers)
}
func (d *Discovery) newEndpointSlicesByNodeInformer(plw *cache.ListWatch, object runtime.Object) cache.SharedIndexInformer {
indexers := make(map[string]cache.IndexFunc)
if !d.attachMetadata.Node {
- cache.NewSharedIndexInformer(plw, &disv1.EndpointSlice{}, resyncPeriod, indexers)
+ cache.NewSharedIndexInformer(plw, &disv1.EndpointSlice{}, resyncDisabled, indexers)
}
indexers[nodeIndex] = func(obj interface{}) ([]string, error) {
@@ -787,17 +795,29 @@ func (d *Discovery) newEndpointSlicesByNodeInformer(plw *cache.ListWatch, object
switch e := obj.(type) {
case *disv1.EndpointSlice:
for _, target := range e.Endpoints {
- if target.NodeName == nil {
- continue
+ if target.TargetRef != nil {
+ switch target.TargetRef.Kind {
+ case "Pod":
+ if target.NodeName != nil {
+ nodes = append(nodes, *target.NodeName)
+ }
+ case "Node":
+ nodes = append(nodes, target.TargetRef.Name)
+ }
}
- nodes = append(nodes, *target.NodeName)
}
case *disv1beta1.EndpointSlice:
for _, target := range e.Endpoints {
- if target.NodeName == nil {
- continue
+ if target.TargetRef != nil {
+ switch target.TargetRef.Kind {
+ case "Pod":
+ if target.NodeName != nil {
+ nodes = append(nodes, *target.NodeName)
+ }
+ case "Node":
+ nodes = append(nodes, target.TargetRef.Name)
+ }
}
- nodes = append(nodes, *target.NodeName)
}
default:
return nil, fmt.Errorf("object is not an endpointslice")
@@ -806,7 +826,7 @@ func (d *Discovery) newEndpointSlicesByNodeInformer(plw *cache.ListWatch, object
return nodes, nil
}
- return cache.NewSharedIndexInformer(plw, object, resyncPeriod, indexers)
+ return cache.NewSharedIndexInformer(plw, object, resyncDisabled, indexers)
}
func checkDiscoveryV1Supported(client kubernetes.Interface) (bool, error) {
diff --git a/discovery/kubernetes/node.go b/discovery/kubernetes/node.go
index ebb6bbced7..d0a6d2780d 100644
--- a/discovery/kubernetes/node.go
+++ b/discovery/kubernetes/node.go
@@ -55,7 +55,7 @@ func NewNode(l log.Logger, inf cache.SharedInformer) *Node {
l = log.NewNopLogger()
}
n := &Node{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("node")}
- n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err := n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
nodeAddCount.Inc()
n.enqueue(o)
@@ -69,6 +69,9 @@ func NewNode(l log.Logger, inf cache.SharedInformer) *Node {
n.enqueue(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding nodes event handler.", "err", err)
+ }
return n
}
@@ -93,7 +96,7 @@ func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
}
go func() {
- for n.process(ctx, ch) {
+ for n.process(ctx, ch) { // nolint:revive
}
}()
@@ -206,7 +209,7 @@ func (n *Node) buildNode(node *apiv1.Node) *targetgroup.Group {
return tg
}
-// nodeAddresses returns the provided node's address, based on the priority:
+// nodeAddress returns the provided node's address, based on the priority:
// 1. NodeInternalIP
// 2. NodeInternalDNS
// 3. NodeExternalIP
diff --git a/discovery/kubernetes/pod.go b/discovery/kubernetes/pod.go
index e19a33c1ae..732cf52ad9 100644
--- a/discovery/kubernetes/pod.go
+++ b/discovery/kubernetes/pod.go
@@ -65,7 +65,7 @@ func NewPod(l log.Logger, pods cache.SharedIndexInformer, nodes cache.SharedInfo
logger: l,
queue: workqueue.NewNamed("pod"),
}
- p.podInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err := p.podInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
podAddCount.Inc()
p.enqueue(o)
@@ -79,9 +79,12 @@ func NewPod(l log.Logger, pods cache.SharedIndexInformer, nodes cache.SharedInfo
p.enqueue(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding pods event handler.", "err", err)
+ }
if p.withNodeMetadata {
- p.nodeInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err = p.nodeInf.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
node := o.(*apiv1.Node)
p.enqueuePodsForNode(node.Name)
@@ -95,6 +98,9 @@ func NewPod(l log.Logger, pods cache.SharedIndexInformer, nodes cache.SharedInfo
p.enqueuePodsForNode(node.Name)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding pods event handler.", "err", err)
+ }
}
return p
@@ -126,7 +132,7 @@ func (p *Pod) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
}
go func() {
- for p.process(ctx, ch) {
+ for p.process(ctx, ch) { // nolint:revive
}
}()
@@ -177,6 +183,7 @@ const (
podNameLabel = metaLabelPrefix + "pod_name"
podIPLabel = metaLabelPrefix + "pod_ip"
podContainerNameLabel = metaLabelPrefix + "pod_container_name"
+ podContainerIDLabel = metaLabelPrefix + "pod_container_id"
podContainerImageLabel = metaLabelPrefix + "pod_container_image"
podContainerPortNameLabel = metaLabelPrefix + "pod_container_port_name"
podContainerPortNumberLabel = metaLabelPrefix + "pod_container_port_number"
@@ -242,6 +249,24 @@ func podLabels(pod *apiv1.Pod) model.LabelSet {
return ls
}
+func (p *Pod) findPodContainerStatus(statuses *[]apiv1.ContainerStatus, containerName string) (*apiv1.ContainerStatus, error) {
+ for _, s := range *statuses {
+ if s.Name == containerName {
+ return &s, nil
+ }
+ }
+ return nil, fmt.Errorf("cannot find container with name %v", containerName)
+}
+
+func (p *Pod) findPodContainerID(statuses *[]apiv1.ContainerStatus, containerName string) string {
+ cStatus, err := p.findPodContainerStatus(statuses, containerName)
+ if err != nil {
+ level.Debug(p.logger).Log("msg", "cannot find container ID", "err", err)
+ return ""
+ }
+ return cStatus.ContainerID
+}
+
func (p *Pod) buildPod(pod *apiv1.Pod) *targetgroup.Group {
tg := &targetgroup.Group{
Source: podSource(pod),
@@ -261,6 +286,12 @@ func (p *Pod) buildPod(pod *apiv1.Pod) *targetgroup.Group {
for i, c := range containers {
isInit := i >= len(pod.Spec.Containers)
+ cStatuses := &pod.Status.ContainerStatuses
+ if isInit {
+ cStatuses = &pod.Status.InitContainerStatuses
+ }
+ cID := p.findPodContainerID(cStatuses, c.Name)
+
// If no ports are defined for the container, create an anonymous
// target per container.
if len(c.Ports) == 0 {
@@ -269,6 +300,7 @@ func (p *Pod) buildPod(pod *apiv1.Pod) *targetgroup.Group {
tg.Targets = append(tg.Targets, model.LabelSet{
model.AddressLabel: lv(pod.Status.PodIP),
podContainerNameLabel: lv(c.Name),
+ podContainerIDLabel: lv(cID),
podContainerImageLabel: lv(c.Image),
podContainerIsInit: lv(strconv.FormatBool(isInit)),
})
@@ -282,6 +314,7 @@ func (p *Pod) buildPod(pod *apiv1.Pod) *targetgroup.Group {
tg.Targets = append(tg.Targets, model.LabelSet{
model.AddressLabel: lv(addr),
podContainerNameLabel: lv(c.Name),
+ podContainerIDLabel: lv(cID),
podContainerImageLabel: lv(c.Image),
podContainerPortNumberLabel: lv(ports),
podContainerPortNameLabel: lv(port.Name),
diff --git a/discovery/kubernetes/pod_test.go b/discovery/kubernetes/pod_test.go
index f2b79dbb84..286a1a230d 100644
--- a/discovery/kubernetes/pod_test.go
+++ b/discovery/kubernetes/pod_test.go
@@ -81,6 +81,16 @@ func makeMultiPortPods() *v1.Pod {
Status: v1.ConditionTrue,
},
},
+ ContainerStatuses: []v1.ContainerStatus{
+ {
+ Name: "testcontainer0",
+ ContainerID: "docker://a1b2c3d4e5f6",
+ },
+ {
+ Name: "testcontainer1",
+ ContainerID: "containerd://6f5e4d3c2b1a",
+ },
+ },
},
}
}
@@ -118,6 +128,12 @@ func makePods() *v1.Pod {
Status: v1.ConditionTrue,
},
},
+ ContainerStatuses: []v1.ContainerStatus{
+ {
+ Name: "testcontainer",
+ ContainerID: "docker://a1b2c3d4e5f6",
+ },
+ },
},
}
}
@@ -162,6 +178,18 @@ func makeInitContainerPods() *v1.Pod {
Status: v1.ConditionFalse,
},
},
+ ContainerStatuses: []v1.ContainerStatus{
+ {
+ Name: "testcontainer",
+ ContainerID: "docker://a1b2c3d4e5f6",
+ },
+ },
+ InitContainerStatuses: []v1.ContainerStatus{
+ {
+ Name: "initcontainer",
+ ContainerID: "containerd://6f5e4d3c2b1a",
+ },
+ },
},
}
}
@@ -179,6 +207,7 @@ func expectedPodTargetGroups(ns string) map[string]*targetgroup.Group {
"__meta_kubernetes_pod_container_port_number": "9000",
"__meta_kubernetes_pod_container_port_protocol": "TCP",
"__meta_kubernetes_pod_container_init": "false",
+ "__meta_kubernetes_pod_container_id": "docker://a1b2c3d4e5f6",
},
},
Labels: model.LabelSet{
@@ -230,6 +259,7 @@ func TestPodDiscoveryBeforeRun(t *testing.T) {
"__meta_kubernetes_pod_container_port_number": "9000",
"__meta_kubernetes_pod_container_port_protocol": "TCP",
"__meta_kubernetes_pod_container_init": "false",
+ "__meta_kubernetes_pod_container_id": "docker://a1b2c3d4e5f6",
},
{
"__address__": "1.2.3.4:9001",
@@ -239,12 +269,14 @@ func TestPodDiscoveryBeforeRun(t *testing.T) {
"__meta_kubernetes_pod_container_port_number": "9001",
"__meta_kubernetes_pod_container_port_protocol": "UDP",
"__meta_kubernetes_pod_container_init": "false",
+ "__meta_kubernetes_pod_container_id": "docker://a1b2c3d4e5f6",
},
{
"__address__": "1.2.3.4",
"__meta_kubernetes_pod_container_name": "testcontainer1",
"__meta_kubernetes_pod_container_image": "testcontainer1:latest",
"__meta_kubernetes_pod_container_init": "false",
+ "__meta_kubernetes_pod_container_id": "containerd://6f5e4d3c2b1a",
},
},
Labels: model.LabelSet{
@@ -280,6 +312,7 @@ func TestPodDiscoveryInitContainer(t *testing.T) {
"__meta_kubernetes_pod_container_name": "initcontainer",
"__meta_kubernetes_pod_container_image": "initcontainer:latest",
"__meta_kubernetes_pod_container_init": "true",
+ "__meta_kubernetes_pod_container_id": "containerd://6f5e4d3c2b1a",
})
expected[key].Labels["__meta_kubernetes_pod_phase"] = "Pending"
expected[key].Labels["__meta_kubernetes_pod_ready"] = "false"
diff --git a/discovery/kubernetes/service.go b/discovery/kubernetes/service.go
index 44ebcbc190..40e17679ee 100644
--- a/discovery/kubernetes/service.go
+++ b/discovery/kubernetes/service.go
@@ -51,7 +51,7 @@ func NewService(l log.Logger, inf cache.SharedInformer) *Service {
l = log.NewNopLogger()
}
s := &Service{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("service")}
- s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ _, err := s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(o interface{}) {
svcAddCount.Inc()
s.enqueue(o)
@@ -65,6 +65,9 @@ func NewService(l log.Logger, inf cache.SharedInformer) *Service {
s.enqueue(o)
},
})
+ if err != nil {
+ level.Error(l).Log("msg", "Error adding services event handler.", "err", err)
+ }
return s
}
@@ -89,7 +92,7 @@ func (s *Service) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
}
go func() {
- for s.process(ctx, ch) {
+ for s.process(ctx, ch) { // nolint:revive
}
}()
diff --git a/discovery/legacymanager/manager.go b/discovery/legacymanager/manager.go
index 7a3d6b3b82..87823f4010 100644
--- a/discovery/legacymanager/manager.go
+++ b/discovery/legacymanager/manager.go
@@ -311,10 +311,10 @@ func (m *Manager) registerProviders(cfgs discovery.Configs, setName string) int
}
typ := cfg.Name()
d, err := cfg.NewDiscoverer(discovery.DiscovererOptions{
- Logger: log.With(m.logger, "discovery", typ),
+ Logger: log.With(m.logger, "discovery", typ, "config", setName),
})
if err != nil {
- level.Error(m.logger).Log("msg", "Cannot create service discovery", "err", err, "type", typ)
+ level.Error(m.logger).Log("msg", "Cannot create service discovery", "err", err, "type", typ, "config", setName)
failed++
return
}
diff --git a/discovery/legacymanager/manager_test.go b/discovery/legacymanager/manager_test.go
index 57c82b72a8..13b84e6e36 100644
--- a/discovery/legacymanager/manager_test.go
+++ b/discovery/legacymanager/manager_test.go
@@ -686,12 +686,7 @@ func TestTargetUpdatesOrder(t *testing.T) {
case tgs := <-provUpdates:
discoveryManager.updateGroup(poolKey{setName: strconv.Itoa(i), provider: tc.title}, tgs)
for _, got := range discoveryManager.allGroups() {
- assertEqualGroups(t, got, tc.expectedTargets[x], func(got, expected string) string {
- return fmt.Sprintf("%d: \ntargets mismatch \ngot: %v \nexpected: %v",
- x,
- got,
- expected)
- })
+ assertEqualGroups(t, got, tc.expectedTargets[x])
}
}
}
@@ -699,7 +694,7 @@ func TestTargetUpdatesOrder(t *testing.T) {
}
}
-func assertEqualGroups(t *testing.T, got, expected []*targetgroup.Group, msg func(got, expected string) string) {
+func assertEqualGroups(t *testing.T, got, expected []*targetgroup.Group) {
t.Helper()
// Need to sort by the groups's source as the received order is not guaranteed.
@@ -1079,9 +1074,7 @@ func TestCoordinationWithReceiver(t *testing.T) {
if _, ok := tgs[k]; !ok {
t.Fatalf("step %d: target group not found: %s\ngot: %#v", i, k, tgs)
}
- assertEqualGroups(t, tgs[k], expected.tgs[k], func(got, expected string) string {
- return fmt.Sprintf("step %d: targets mismatch \ngot: %q \nexpected: %q", i, got, expected)
- })
+ assertEqualGroups(t, tgs[k], expected.tgs[k])
}
}
}
diff --git a/discovery/legacymanager/registry.go b/discovery/legacymanager/registry.go
index 687f093829..955705394d 100644
--- a/discovery/legacymanager/registry.go
+++ b/discovery/legacymanager/registry.go
@@ -254,7 +254,7 @@ func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
oldStr := oldTyp.String()
newStr := newTyp.String()
for i, s := range e.Errors {
- e.Errors[i] = strings.Replace(s, oldStr, newStr, -1)
+ e.Errors[i] = strings.ReplaceAll(s, oldStr, newStr)
}
}
return err
diff --git a/discovery/linode/linode.go b/discovery/linode/linode.go
index 0fd0a2c370..12b9575143 100644
--- a/discovery/linode/linode.go
+++ b/discovery/linode/linode.go
@@ -249,20 +249,20 @@ func (d *Discovery) refreshData(ctx context.Context) ([]*targetgroup.Group, erro
if detailedIP.Address != ip.String() {
continue
}
-
- if detailedIP.Public && publicIPv4 == "" {
+ switch {
+ case detailedIP.Public && publicIPv4 == "":
publicIPv4 = detailedIP.Address
if detailedIP.RDNS != "" && detailedIP.RDNS != "null" {
publicIPv4RDNS = detailedIP.RDNS
}
- } else if !detailedIP.Public && privateIPv4 == "" {
+ case !detailedIP.Public && privateIPv4 == "":
privateIPv4 = detailedIP.Address
if detailedIP.RDNS != "" && detailedIP.RDNS != "null" {
privateIPv4RDNS = detailedIP.RDNS
}
- } else {
+ default:
extraIPs = append(extraIPs, detailedIP.Address)
}
}
diff --git a/discovery/manager.go b/discovery/manager.go
index b7357fa6cd..8b304a0faf 100644
--- a/discovery/manager.go
+++ b/discovery/manager.go
@@ -428,11 +428,11 @@ func (m *Manager) registerProviders(cfgs Configs, setName string) int {
}
typ := cfg.Name()
d, err := cfg.NewDiscoverer(DiscovererOptions{
- Logger: log.With(m.logger, "discovery", typ),
+ Logger: log.With(m.logger, "discovery", typ, "config", setName),
HTTPClientOptions: m.httpOpts,
})
if err != nil {
- level.Error(m.logger).Log("msg", "Cannot create service discovery", "err", err, "type", typ)
+ level.Error(m.logger).Log("msg", "Cannot create service discovery", "err", err, "type", typ, "config", setName)
failed++
return
}
diff --git a/discovery/manager_test.go b/discovery/manager_test.go
index 970168b0f5..5371608112 100644
--- a/discovery/manager_test.go
+++ b/discovery/manager_test.go
@@ -686,12 +686,7 @@ func TestTargetUpdatesOrder(t *testing.T) {
case tgs := <-provUpdates:
discoveryManager.updateGroup(poolKey{setName: strconv.Itoa(i), provider: tc.title}, tgs)
for _, got := range discoveryManager.allGroups() {
- assertEqualGroups(t, got, tc.expectedTargets[x], func(got, expected string) string {
- return fmt.Sprintf("%d: \ntargets mismatch \ngot: %v \nexpected: %v",
- x,
- got,
- expected)
- })
+ assertEqualGroups(t, got, tc.expectedTargets[x])
}
}
}
@@ -699,7 +694,7 @@ func TestTargetUpdatesOrder(t *testing.T) {
}
}
-func assertEqualGroups(t *testing.T, got, expected []*targetgroup.Group, msg func(got, expected string) string) {
+func assertEqualGroups(t *testing.T, got, expected []*targetgroup.Group) {
t.Helper()
// Need to sort by the groups's source as the received order is not guaranteed.
@@ -1129,7 +1124,7 @@ type lockStaticConfig struct {
}
func (s lockStaticConfig) Name() string { return "lockstatic" }
-func (s lockStaticConfig) NewDiscoverer(options DiscovererOptions) (Discoverer, error) {
+func (s lockStaticConfig) NewDiscoverer(DiscovererOptions) (Discoverer, error) {
return (lockStaticDiscoverer)(s), nil
}
@@ -1330,9 +1325,7 @@ func TestCoordinationWithReceiver(t *testing.T) {
if _, ok := tgs[k]; !ok {
t.Fatalf("step %d: target group not found: %s\ngot: %#v", i, k, tgs)
}
- assertEqualGroups(t, tgs[k], expected.tgs[k], func(got, expected string) string {
- return fmt.Sprintf("step %d: targets mismatch \ngot: %q \nexpected: %q", i, got, expected)
- })
+ assertEqualGroups(t, tgs[k], expected.tgs[k])
}
}
}
@@ -1399,7 +1392,7 @@ func (o onceProvider) Run(_ context.Context, ch chan<- []*targetgroup.Group) {
// TestTargetSetTargetGroupsUpdateDuringApplyConfig is used to detect races when
// ApplyConfig happens at the same time as targets update.
-func TestTargetSetTargetGroupsUpdateDuringApplyConfig(t *testing.T) {
+func TestTargetSetTargetGroupsUpdateDuringApplyConfig(*testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
discoveryManager := NewManager(ctx, log.NewNopLogger())
diff --git a/discovery/marathon/marathon.go b/discovery/marathon/marathon.go
index 079f93ad0b..cfd3e2c083 100644
--- a/discovery/marathon/marathon.go
+++ b/discovery/marathon/marathon.go
@@ -136,9 +136,10 @@ func NewDiscovery(conf SDConfig, logger log.Logger) (*Discovery, error) {
return nil, err
}
- if len(conf.AuthToken) > 0 {
+ switch {
+ case len(conf.AuthToken) > 0:
rt, err = newAuthTokenRoundTripper(conf.AuthToken, rt)
- } else if len(conf.AuthTokenFile) > 0 {
+ case len(conf.AuthTokenFile) > 0:
rt, err = newAuthTokenFileRoundTripper(conf.AuthTokenFile, rt)
}
if err != nil {
@@ -400,19 +401,20 @@ func targetsForApp(app *app) []model.LabelSet {
var labels []map[string]string
var prefix string
- if len(app.Container.PortMappings) != 0 {
+ switch {
+ case len(app.Container.PortMappings) != 0:
// In Marathon 1.5.x the "container.docker.portMappings" object was moved
// to "container.portMappings".
ports, labels = extractPortMapping(app.Container.PortMappings, app.isContainerNet())
prefix = portMappingLabelPrefix
- } else if len(app.Container.Docker.PortMappings) != 0 {
+ case len(app.Container.Docker.PortMappings) != 0:
// Prior to Marathon 1.5 the port mappings could be found at the path
// "container.docker.portMappings".
ports, labels = extractPortMapping(app.Container.Docker.PortMappings, app.isContainerNet())
prefix = portMappingLabelPrefix
- } else if len(app.PortDefinitions) != 0 {
+ case len(app.PortDefinitions) != 0:
// PortDefinitions deprecates the "ports" array and can be used to specify
// a list of ports with metadata in case a mapping is not required.
ports = make([]uint32, len(app.PortDefinitions))
diff --git a/discovery/moby/testdata/dockerprom/containers/json_limit_0.json b/discovery/moby/testdata/dockerprom/containers/json.json
similarity index 100%
rename from discovery/moby/testdata/dockerprom/containers/json_limit_0.json
rename to discovery/moby/testdata/dockerprom/containers/json.json
diff --git a/discovery/nomad/nomad.go b/discovery/nomad/nomad.go
index c8d5130396..7013f0737c 100644
--- a/discovery/nomad/nomad.go
+++ b/discovery/nomad/nomad.go
@@ -161,7 +161,7 @@ func NewDiscovery(conf *SDConfig, logger log.Logger) (*Discovery, error) {
return d, nil
}
-func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
+func (d *Discovery) refresh(context.Context) ([]*targetgroup.Group, error) {
opts := &nomad.QueryOptions{
AllowStale: d.allowStale,
}
diff --git a/discovery/openstack/instance.go b/discovery/openstack/instance.go
index 2f7e99a071..b2fe1e7870 100644
--- a/discovery/openstack/instance.go
+++ b/discovery/openstack/instance.go
@@ -36,6 +36,7 @@ const (
openstackLabelAddressPool = openstackLabelPrefix + "address_pool"
openstackLabelInstanceFlavor = openstackLabelPrefix + "instance_flavor"
openstackLabelInstanceID = openstackLabelPrefix + "instance_id"
+ openstackLabelInstanceImage = openstackLabelPrefix + "instance_image"
openstackLabelInstanceName = openstackLabelPrefix + "instance_name"
openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status"
openstackLabelPrivateIP = openstackLabelPrefix + "private_ip"
@@ -144,12 +145,18 @@ func (i *InstanceDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
openstackLabelUserID: model.LabelValue(s.UserID),
}
- id, ok := s.Flavor["id"].(string)
+ flavorId, ok := s.Flavor["id"].(string)
if !ok {
level.Warn(i.logger).Log("msg", "Invalid type for flavor id, expected string")
continue
}
- labels[openstackLabelInstanceFlavor] = model.LabelValue(id)
+ labels[openstackLabelInstanceFlavor] = model.LabelValue(flavorId)
+
+ imageId, ok := s.Image["id"].(string)
+ if ok {
+ labels[openstackLabelInstanceImage] = model.LabelValue(imageId)
+ }
+
for k, v := range s.Metadata {
name := strutil.SanitizeLabelName(k)
labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v)
diff --git a/discovery/openstack/instance_test.go b/discovery/openstack/instance_test.go
index d47cb0020e..d2da5d9681 100644
--- a/discovery/openstack/instance_test.go
+++ b/discovery/openstack/instance_test.go
@@ -73,6 +73,7 @@ func TestOpenstackSDInstanceRefresh(t *testing.T) {
"__address__": model.LabelValue("10.0.0.32:0"),
"__meta_openstack_instance_flavor": model.LabelValue("1"),
"__meta_openstack_instance_id": model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5"),
+ "__meta_openstack_instance_image": model.LabelValue("f90f6034-2570-4974-8351-6b49732ef2eb"),
"__meta_openstack_instance_status": model.LabelValue("ACTIVE"),
"__meta_openstack_instance_name": model.LabelValue("herp"),
"__meta_openstack_private_ip": model.LabelValue("10.0.0.32"),
@@ -85,6 +86,7 @@ func TestOpenstackSDInstanceRefresh(t *testing.T) {
"__address__": model.LabelValue("10.0.0.31:0"),
"__meta_openstack_instance_flavor": model.LabelValue("1"),
"__meta_openstack_instance_id": model.LabelValue("9e5476bd-a4ec-4653-93d6-72c93aa682ba"),
+ "__meta_openstack_instance_image": model.LabelValue("f90f6034-2570-4974-8351-6b49732ef2eb"),
"__meta_openstack_instance_status": model.LabelValue("ACTIVE"),
"__meta_openstack_instance_name": model.LabelValue("derp"),
"__meta_openstack_private_ip": model.LabelValue("10.0.0.31"),
diff --git a/discovery/ovhcloud/dedicated_server.go b/discovery/ovhcloud/dedicated_server.go
new file mode 100644
index 0000000000..bb5dadcd7b
--- /dev/null
+++ b/discovery/ovhcloud/dedicated_server.go
@@ -0,0 +1,163 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed 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 ovhcloud
+
+import (
+ "context"
+ "fmt"
+ "net/netip"
+ "net/url"
+ "path"
+ "strconv"
+
+ "github.com/go-kit/log"
+ "github.com/go-kit/log/level"
+ "github.com/ovh/go-ovh/ovh"
+ "github.com/prometheus/common/model"
+
+ "github.com/prometheus/prometheus/discovery/refresh"
+ "github.com/prometheus/prometheus/discovery/targetgroup"
+)
+
+const (
+ dedicatedServerAPIPath = "/dedicated/server"
+ dedicatedServerLabelPrefix = metaLabelPrefix + "dedicated_server_"
+)
+
+// dedicatedServer struct from API. Also contains IP addresses that are fetched
+// independently.
+type dedicatedServer struct {
+ State string `json:"state"`
+ ips []netip.Addr
+ CommercialRange string `json:"commercialRange"`
+ LinkSpeed int `json:"linkSpeed"`
+ Rack string `json:"rack"`
+ NoIntervention bool `json:"noIntervention"`
+ Os string `json:"os"`
+ SupportLevel string `json:"supportLevel"`
+ ServerID int64 `json:"serverId"`
+ Reverse string `json:"reverse"`
+ Datacenter string `json:"datacenter"`
+ Name string `json:"name"`
+}
+
+type dedicatedServerDiscovery struct {
+ *refresh.Discovery
+ config *SDConfig
+ logger log.Logger
+}
+
+func newDedicatedServerDiscovery(conf *SDConfig, logger log.Logger) *dedicatedServerDiscovery {
+ return &dedicatedServerDiscovery{config: conf, logger: logger}
+}
+
+func getDedicatedServerList(client *ovh.Client) ([]string, error) {
+ var dedicatedListName []string
+ err := client.Get(dedicatedServerAPIPath, &dedicatedListName)
+ if err != nil {
+ return nil, err
+ }
+
+ return dedicatedListName, nil
+}
+
+func getDedicatedServerDetails(client *ovh.Client, serverName string) (*dedicatedServer, error) {
+ var dedicatedServerDetails dedicatedServer
+ err := client.Get(path.Join(dedicatedServerAPIPath, url.QueryEscape(serverName)), &dedicatedServerDetails)
+ if err != nil {
+ return nil, err
+ }
+
+ var ips []string
+ err = client.Get(path.Join(dedicatedServerAPIPath, url.QueryEscape(serverName), "ips"), &ips)
+ if err != nil {
+ return nil, err
+ }
+
+ parsedIPs, err := parseIPList(ips)
+ if err != nil {
+ return nil, err
+ }
+
+ dedicatedServerDetails.ips = parsedIPs
+ return &dedicatedServerDetails, nil
+}
+
+func (d *dedicatedServerDiscovery) getService() string {
+ return "dedicated_server"
+}
+
+func (d *dedicatedServerDiscovery) getSource() string {
+ return fmt.Sprintf("%s_%s", d.config.Name(), d.getService())
+}
+
+func (d *dedicatedServerDiscovery) refresh(context.Context) ([]*targetgroup.Group, error) {
+ client, err := createClient(d.config)
+ if err != nil {
+ return nil, err
+ }
+ var dedicatedServerDetailedList []dedicatedServer
+ dedicatedServerList, err := getDedicatedServerList(client)
+ if err != nil {
+ return nil, err
+ }
+ for _, dedicatedServerName := range dedicatedServerList {
+ dedicatedServer, err := getDedicatedServerDetails(client, dedicatedServerName)
+ if err != nil {
+ err := level.Warn(d.logger).Log("msg", fmt.Sprintf("%s: Could not get details of %s", d.getSource(), dedicatedServerName), "err", err.Error())
+ if err != nil {
+ return nil, err
+ }
+ continue
+ }
+ dedicatedServerDetailedList = append(dedicatedServerDetailedList, *dedicatedServer)
+ }
+ var targets []model.LabelSet
+
+ for _, server := range dedicatedServerDetailedList {
+ var ipv4, ipv6 string
+ for _, ip := range server.ips {
+ if ip.Is4() {
+ ipv4 = ip.String()
+ }
+ if ip.Is6() {
+ ipv6 = ip.String()
+ }
+ }
+ defaultIP := ipv4
+ if defaultIP == "" {
+ defaultIP = ipv6
+ }
+ labels := model.LabelSet{
+ model.AddressLabel: model.LabelValue(defaultIP),
+ model.InstanceLabel: model.LabelValue(server.Name),
+ dedicatedServerLabelPrefix + "state": model.LabelValue(server.State),
+ dedicatedServerLabelPrefix + "commercial_range": model.LabelValue(server.CommercialRange),
+ dedicatedServerLabelPrefix + "link_speed": model.LabelValue(fmt.Sprintf("%d", server.LinkSpeed)),
+ dedicatedServerLabelPrefix + "rack": model.LabelValue(server.Rack),
+ dedicatedServerLabelPrefix + "no_intervention": model.LabelValue(strconv.FormatBool(server.NoIntervention)),
+ dedicatedServerLabelPrefix + "os": model.LabelValue(server.Os),
+ dedicatedServerLabelPrefix + "support_level": model.LabelValue(server.SupportLevel),
+ dedicatedServerLabelPrefix + "server_id": model.LabelValue(fmt.Sprintf("%d", server.ServerID)),
+ dedicatedServerLabelPrefix + "reverse": model.LabelValue(server.Reverse),
+ dedicatedServerLabelPrefix + "datacenter": model.LabelValue(server.Datacenter),
+ dedicatedServerLabelPrefix + "name": model.LabelValue(server.Name),
+ dedicatedServerLabelPrefix + "ipv4": model.LabelValue(ipv4),
+ dedicatedServerLabelPrefix + "ipv6": model.LabelValue(ipv6),
+ }
+ targets = append(targets, labels)
+ }
+
+ return []*targetgroup.Group{{Source: d.getSource(), Targets: targets}}, nil
+}
diff --git a/discovery/ovhcloud/dedicated_server_test.go b/discovery/ovhcloud/dedicated_server_test.go
new file mode 100644
index 0000000000..e8ffa4a283
--- /dev/null
+++ b/discovery/ovhcloud/dedicated_server_test.go
@@ -0,0 +1,123 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed 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 ovhcloud
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/go-kit/log"
+ "github.com/prometheus/common/model"
+ "github.com/stretchr/testify/require"
+ "gopkg.in/yaml.v2"
+)
+
+func TestOvhcloudDedicatedServerRefresh(t *testing.T) {
+ var cfg SDConfig
+
+ mock := httptest.NewServer(http.HandlerFunc(MockDedicatedAPI))
+ defer mock.Close()
+ cfgString := fmt.Sprintf(`
+---
+service: dedicated_server
+endpoint: %s
+application_key: %s
+application_secret: %s
+consumer_key: %s`, mock.URL, ovhcloudApplicationKeyTest, ovhcloudApplicationSecretTest, ovhcloudConsumerKeyTest)
+
+ require.NoError(t, yaml.UnmarshalStrict([]byte(cfgString), &cfg))
+ d, err := newRefresher(&cfg, log.NewNopLogger())
+ require.NoError(t, err)
+ ctx := context.Background()
+ targetGroups, err := d.refresh(ctx)
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(targetGroups))
+ targetGroup := targetGroups[0]
+ require.NotNil(t, targetGroup)
+ require.NotNil(t, targetGroup.Targets)
+ require.Equal(t, 1, len(targetGroup.Targets))
+
+ for i, lbls := range []model.LabelSet{
+ {
+ "__address__": "1.2.3.4",
+ "__meta_ovhcloud_dedicated_server_commercial_range": "Advance-1 Gen 2",
+ "__meta_ovhcloud_dedicated_server_datacenter": "gra3",
+ "__meta_ovhcloud_dedicated_server_ipv4": "1.2.3.4",
+ "__meta_ovhcloud_dedicated_server_ipv6": "",
+ "__meta_ovhcloud_dedicated_server_link_speed": "123",
+ "__meta_ovhcloud_dedicated_server_name": "abcde",
+ "__meta_ovhcloud_dedicated_server_no_intervention": "false",
+ "__meta_ovhcloud_dedicated_server_os": "debian11_64",
+ "__meta_ovhcloud_dedicated_server_rack": "TESTRACK",
+ "__meta_ovhcloud_dedicated_server_reverse": "abcde-rev",
+ "__meta_ovhcloud_dedicated_server_server_id": "1234",
+ "__meta_ovhcloud_dedicated_server_state": "test",
+ "__meta_ovhcloud_dedicated_server_support_level": "pro",
+ "instance": "abcde",
+ },
+ } {
+ t.Run(fmt.Sprintf("item %d", i), func(t *testing.T) {
+ require.Equal(t, lbls, targetGroup.Targets[i])
+ })
+ }
+}
+
+func MockDedicatedAPI(w http.ResponseWriter, r *http.Request) {
+ if r.Header.Get("X-Ovh-Application") != ovhcloudApplicationKeyTest {
+ http.Error(w, "bad application key", http.StatusBadRequest)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if r.URL.Path == "/dedicated/server" {
+ dedicatedServersList, err := os.ReadFile("testdata/dedicated_server/dedicated_servers.json")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(dedicatedServersList)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ if r.URL.Path == "/dedicated/server/abcde" {
+ dedicatedServer, err := os.ReadFile("testdata/dedicated_server/dedicated_servers_details.json")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(dedicatedServer)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ if r.URL.Path == "/dedicated/server/abcde/ips" {
+ dedicatedServerIPs, err := os.ReadFile("testdata/dedicated_server/dedicated_servers_abcde_ips.json")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(dedicatedServerIPs)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+}
diff --git a/discovery/ovhcloud/ovhcloud.go b/discovery/ovhcloud/ovhcloud.go
new file mode 100644
index 0000000000..535ade4df5
--- /dev/null
+++ b/discovery/ovhcloud/ovhcloud.go
@@ -0,0 +1,155 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed 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 ovhcloud
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/netip"
+ "time"
+
+ "github.com/go-kit/log"
+ "github.com/ovh/go-ovh/ovh"
+ "github.com/prometheus/common/config"
+ "github.com/prometheus/common/model"
+
+ "github.com/prometheus/prometheus/discovery"
+ "github.com/prometheus/prometheus/discovery/refresh"
+ "github.com/prometheus/prometheus/discovery/targetgroup"
+)
+
+// metaLabelPrefix is the meta prefix used for all meta labels in this discovery.
+const metaLabelPrefix = model.MetaLabelPrefix + "ovhcloud_"
+
+type refresher interface {
+ refresh(context.Context) ([]*targetgroup.Group, error)
+}
+
+var DefaultSDConfig = SDConfig{
+ Endpoint: "ovh-eu",
+ RefreshInterval: model.Duration(60 * time.Second),
+}
+
+// SDConfig defines the Service Discovery struct used for configuration.
+type SDConfig struct {
+ Endpoint string `yaml:"endpoint"`
+ ApplicationKey string `yaml:"application_key"`
+ ApplicationSecret config.Secret `yaml:"application_secret"`
+ ConsumerKey config.Secret `yaml:"consumer_key"`
+ RefreshInterval model.Duration `yaml:"refresh_interval"`
+ Service string `yaml:"service"`
+}
+
+// Name implements the Discoverer interface.
+func (c SDConfig) Name() string {
+ return "ovhcloud"
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ *c = DefaultSDConfig
+ type plain SDConfig
+ err := unmarshal((*plain)(c))
+ if err != nil {
+ return err
+ }
+
+ if c.Endpoint == "" {
+ return errors.New("endpoint can not be empty")
+ }
+ if c.ApplicationKey == "" {
+ return errors.New("application key can not be empty")
+ }
+ if c.ApplicationSecret == "" {
+ return errors.New("application secret can not be empty")
+ }
+ if c.ConsumerKey == "" {
+ return errors.New("consumer key can not be empty")
+ }
+ switch c.Service {
+ case "dedicated_server", "vps":
+ return nil
+ default:
+ return fmt.Errorf("unknown service: %v", c.Service)
+ }
+}
+
+// CreateClient creates a new ovh client configured with given credentials.
+func createClient(config *SDConfig) (*ovh.Client, error) {
+ return ovh.NewClient(config.Endpoint, config.ApplicationKey, string(config.ApplicationSecret), string(config.ConsumerKey))
+}
+
+// NewDiscoverer returns a Discoverer for the Config.
+func (c *SDConfig) NewDiscoverer(options discovery.DiscovererOptions) (discovery.Discoverer, error) {
+ return NewDiscovery(c, options.Logger)
+}
+
+func init() {
+ discovery.RegisterConfig(&SDConfig{})
+}
+
+// ParseIPList parses ip list as they can have different formats.
+func parseIPList(ipList []string) ([]netip.Addr, error) {
+ var ipAddresses []netip.Addr
+ for _, ip := range ipList {
+ ipAddr, err := netip.ParseAddr(ip)
+ if err != nil {
+ ipPrefix, err := netip.ParsePrefix(ip)
+ if err != nil {
+ return nil, errors.New("could not parse IP addresses from list")
+ }
+ if ipPrefix.IsValid() {
+ netmask := ipPrefix.Bits()
+ if netmask != 32 {
+ continue
+ }
+ ipAddr = ipPrefix.Addr()
+ }
+ }
+ if ipAddr.IsValid() && !ipAddr.IsUnspecified() {
+ ipAddresses = append(ipAddresses, ipAddr)
+ }
+ }
+
+ if len(ipAddresses) == 0 {
+ return nil, errors.New("could not parse IP addresses from list")
+ }
+ return ipAddresses, nil
+}
+
+func newRefresher(conf *SDConfig, logger log.Logger) (refresher, error) {
+ switch conf.Service {
+ case "vps":
+ return newVpsDiscovery(conf, logger), nil
+ case "dedicated_server":
+ return newDedicatedServerDiscovery(conf, logger), nil
+ }
+ return nil, fmt.Errorf("unknown OVHcloud discovery service '%s'", conf.Service)
+}
+
+// NewDiscovery returns a new OVHcloud Discoverer which periodically refreshes its targets.
+func NewDiscovery(conf *SDConfig, logger log.Logger) (*refresh.Discovery, error) {
+ r, err := newRefresher(conf, logger)
+ if err != nil {
+ return nil, err
+ }
+
+ return refresh.NewDiscovery(
+ logger,
+ "ovhcloud",
+ time.Duration(conf.RefreshInterval),
+ r.refresh,
+ ), nil
+}
diff --git a/discovery/ovhcloud/ovhcloud_test.go b/discovery/ovhcloud/ovhcloud_test.go
new file mode 100644
index 0000000000..efcd95bb0d
--- /dev/null
+++ b/discovery/ovhcloud/ovhcloud_test.go
@@ -0,0 +1,129 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed 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 ovhcloud
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/prometheus/common/config"
+ "github.com/stretchr/testify/require"
+ "gopkg.in/yaml.v2"
+
+ "github.com/prometheus/prometheus/discovery"
+ "github.com/prometheus/prometheus/util/testutil"
+)
+
+var (
+ ovhcloudApplicationKeyTest = "TDPKJdwZwAQPwKX2"
+ ovhcloudApplicationSecretTest = config.Secret("9ufkBmLaTQ9nz5yMUlg79taH0GNnzDjk")
+ ovhcloudConsumerKeyTest = config.Secret("5mBuy6SUQcRw2ZUxg0cG68BoDKpED4KY")
+)
+
+const (
+ mockURL = "https://localhost:1234"
+)
+
+func getMockConf(service string) (SDConfig, error) {
+ confString := fmt.Sprintf(`
+endpoint: %s
+application_key: %s
+application_secret: %s
+consumer_key: %s
+refresh_interval: 1m
+service: %s
+`, mockURL, ovhcloudApplicationKeyTest, ovhcloudApplicationSecretTest, ovhcloudConsumerKeyTest, service)
+
+ return getMockConfFromString(confString)
+}
+
+func getMockConfFromString(confString string) (SDConfig, error) {
+ var conf SDConfig
+ err := yaml.UnmarshalStrict([]byte(confString), &conf)
+ return conf, err
+}
+
+func TestErrorInitClient(t *testing.T) {
+ confString := fmt.Sprintf(`
+endpoint: %s
+
+`, mockURL)
+
+ conf, _ := getMockConfFromString(confString)
+
+ _, err := createClient(&conf)
+
+ require.ErrorContains(t, err, "missing application key")
+}
+
+func TestParseIPs(t *testing.T) {
+ testCases := []struct {
+ name string
+ input []string
+ want error
+ }{
+ {
+ name: "Parse IPv4 failed.",
+ input: []string{"A.b"},
+ want: errors.New("could not parse IP addresses from list"),
+ },
+ {
+ name: "Parse unspecified failed.",
+ input: []string{"0.0.0.0"},
+ want: errors.New("could not parse IP addresses from list"),
+ },
+ {
+ name: "Parse void IP failed.",
+ input: []string{""},
+ want: errors.New("could not parse IP addresses from list"),
+ },
+ {
+ name: "Parse IPv6 ok.",
+ input: []string{"2001:0db8:0000:0000:0000:0000:0000:0001"},
+ want: nil,
+ },
+ {
+ name: "Parse IPv6 failed.",
+ input: []string{"bbb:cccc:1111"},
+ want: errors.New("could not parse IP addresses from list"),
+ },
+ {
+ name: "Parse IPv4 bad mask.",
+ input: []string{"192.0.2.1/23"},
+ want: errors.New("could not parse IP addresses from list"),
+ },
+ {
+ name: "Parse IPv4 ok.",
+ input: []string{"192.0.2.1/32"},
+ want: nil,
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := parseIPList(tc.input)
+ require.Equal(t, tc.want, err)
+ })
+ }
+}
+
+func TestDiscoverer(t *testing.T) {
+ conf, _ := getMockConf("vps")
+ logger := testutil.NewLogger(t)
+ _, err := conf.NewDiscoverer(discovery.DiscovererOptions{
+ Logger: logger,
+ })
+
+ require.NoError(t, err)
+}
diff --git a/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers.json b/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers.json
new file mode 100644
index 0000000000..a250e96051
--- /dev/null
+++ b/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers.json
@@ -0,0 +1,3 @@
+[
+ "abcde"
+]
\ No newline at end of file
diff --git a/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers_abcde_ips.json b/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers_abcde_ips.json
new file mode 100644
index 0000000000..a1b949c975
--- /dev/null
+++ b/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers_abcde_ips.json
@@ -0,0 +1,4 @@
+[
+ "1.2.3.4/32",
+ "2001:0db8:0000:0000:0000:0000:0000:0001/64"
+]
\ No newline at end of file
diff --git a/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers_details.json b/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers_details.json
new file mode 100644
index 0000000000..68f3e3b5c9
--- /dev/null
+++ b/discovery/ovhcloud/testdata/dedicated_server/dedicated_servers_details.json
@@ -0,0 +1,20 @@
+{
+ "ip": "1.2.3.4",
+ "newUpgradeSystem": true,
+ "commercialRange": "Advance-1 Gen 2",
+ "rack": "TESTRACK",
+ "rescueMail": null,
+ "supportLevel": "pro",
+ "bootId": 1,
+ "linkSpeed": 123,
+ "professionalUse": false,
+ "monitoring": true,
+ "noIntervention": false,
+ "name": "abcde",
+ "rootDevice": null,
+ "state": "test",
+ "datacenter": "gra3",
+ "os": "debian11_64",
+ "reverse": "abcde-rev",
+ "serverId": 1234
+}
\ No newline at end of file
diff --git a/discovery/ovhcloud/testdata/vps/vps.json b/discovery/ovhcloud/testdata/vps/vps.json
new file mode 100644
index 0000000000..18147865ba
--- /dev/null
+++ b/discovery/ovhcloud/testdata/vps/vps.json
@@ -0,0 +1,3 @@
+[
+ "abc"
+]
\ No newline at end of file
diff --git a/discovery/ovhcloud/testdata/vps/vps_abc_ips.json b/discovery/ovhcloud/testdata/vps/vps_abc_ips.json
new file mode 100644
index 0000000000..3c871d0933
--- /dev/null
+++ b/discovery/ovhcloud/testdata/vps/vps_abc_ips.json
@@ -0,0 +1,4 @@
+[
+ "192.0.2.1/32",
+ "2001:0db1:0000:0000:0000:0000:0000:0001/64"
+]
\ No newline at end of file
diff --git a/discovery/ovhcloud/testdata/vps/vps_details.json b/discovery/ovhcloud/testdata/vps/vps_details.json
new file mode 100644
index 0000000000..f2bb2e803c
--- /dev/null
+++ b/discovery/ovhcloud/testdata/vps/vps_details.json
@@ -0,0 +1,25 @@
+{
+ "offerType": "ssd",
+ "monitoringIpBlocks": [],
+ "displayName": "abc",
+ "zone": "zone",
+ "cluster": "cluster_test",
+ "slaMonitoring": false,
+ "name": "abc",
+ "vcore": 1,
+ "state": "running",
+ "keymap": null,
+ "netbootMode": "local",
+ "model": {
+ "name": "vps-value-1-2-40",
+ "availableOptions": [],
+ "maximumAdditionnalIp": 16,
+ "offer": "VPS abc",
+ "disk": 40,
+ "version": "2019v1",
+ "vcore": 1,
+ "memory": 2048,
+ "datacenter": []
+ },
+ "memoryLimit": 2048
+}
\ No newline at end of file
diff --git a/discovery/ovhcloud/vps.go b/discovery/ovhcloud/vps.go
new file mode 100644
index 0000000000..e2d1dee364
--- /dev/null
+++ b/discovery/ovhcloud/vps.go
@@ -0,0 +1,187 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed 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 ovhcloud
+
+import (
+ "context"
+ "fmt"
+ "net/netip"
+ "net/url"
+ "path"
+
+ "github.com/go-kit/log"
+ "github.com/go-kit/log/level"
+ "github.com/ovh/go-ovh/ovh"
+ "github.com/prometheus/common/model"
+
+ "github.com/prometheus/prometheus/discovery/refresh"
+ "github.com/prometheus/prometheus/discovery/targetgroup"
+)
+
+const (
+ vpsAPIPath = "/vps"
+ vpsLabelPrefix = metaLabelPrefix + "vps_"
+)
+
+// Model struct from API.
+type vpsModel struct {
+ MaximumAdditionalIP int `json:"maximumAdditionnalIp"`
+ Offer string `json:"offer"`
+ Datacenter []string `json:"datacenter"`
+ Vcore int `json:"vcore"`
+ Version string `json:"version"`
+ Name string `json:"name"`
+ Disk int `json:"disk"`
+ Memory int `json:"memory"`
+}
+
+// VPS struct from API. Also contains IP addresses that are fetched
+// independently.
+type virtualPrivateServer struct {
+ ips []netip.Addr
+ Keymap []string `json:"keymap"`
+ Zone string `json:"zone"`
+ Model vpsModel `json:"model"`
+ DisplayName string `json:"displayName"`
+ MonitoringIPBlocks []string `json:"monitoringIpBlocks"`
+ Cluster string `json:"cluster"`
+ State string `json:"state"`
+ Name string `json:"name"`
+ NetbootMode string `json:"netbootMode"`
+ MemoryLimit int `json:"memoryLimit"`
+ OfferType string `json:"offerType"`
+ Vcore int `json:"vcore"`
+}
+
+type vpsDiscovery struct {
+ *refresh.Discovery
+ config *SDConfig
+ logger log.Logger
+}
+
+func newVpsDiscovery(conf *SDConfig, logger log.Logger) *vpsDiscovery {
+ return &vpsDiscovery{config: conf, logger: logger}
+}
+
+func getVpsDetails(client *ovh.Client, vpsName string) (*virtualPrivateServer, error) {
+ var vpsDetails virtualPrivateServer
+ vpsNamePath := path.Join(vpsAPIPath, url.QueryEscape(vpsName))
+
+ err := client.Get(vpsNamePath, &vpsDetails)
+ if err != nil {
+ return nil, err
+ }
+
+ var ips []string
+ err = client.Get(path.Join(vpsNamePath, "ips"), &ips)
+ if err != nil {
+ return nil, err
+ }
+
+ parsedIPs, err := parseIPList(ips)
+ if err != nil {
+ return nil, err
+ }
+ vpsDetails.ips = parsedIPs
+
+ return &vpsDetails, nil
+}
+
+func getVpsList(client *ovh.Client) ([]string, error) {
+ var vpsListName []string
+
+ err := client.Get(vpsAPIPath, &vpsListName)
+ if err != nil {
+ return nil, err
+ }
+
+ return vpsListName, nil
+}
+
+func (d *vpsDiscovery) getService() string {
+ return "vps"
+}
+
+func (d *vpsDiscovery) getSource() string {
+ return fmt.Sprintf("%s_%s", d.config.Name(), d.getService())
+}
+
+func (d *vpsDiscovery) refresh(context.Context) ([]*targetgroup.Group, error) {
+ client, err := createClient(d.config)
+ if err != nil {
+ return nil, err
+ }
+
+ var vpsDetailedList []virtualPrivateServer
+ vpsList, err := getVpsList(client)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, vpsName := range vpsList {
+ vpsDetailed, err := getVpsDetails(client, vpsName)
+ if err != nil {
+ err := level.Warn(d.logger).Log("msg", fmt.Sprintf("%s: Could not get details of %s", d.getSource(), vpsName), "err", err.Error())
+ if err != nil {
+ return nil, err
+ }
+ continue
+ }
+ vpsDetailedList = append(vpsDetailedList, *vpsDetailed)
+ }
+
+ var targets []model.LabelSet
+ for _, server := range vpsDetailedList {
+ var ipv4, ipv6 string
+ for _, ip := range server.ips {
+ if ip.Is4() {
+ ipv4 = ip.String()
+ }
+ if ip.Is6() {
+ ipv6 = ip.String()
+ }
+ }
+ defaultIP := ipv4
+ if defaultIP == "" {
+ defaultIP = ipv6
+ }
+ labels := model.LabelSet{
+ model.AddressLabel: model.LabelValue(defaultIP),
+ model.InstanceLabel: model.LabelValue(server.Name),
+ vpsLabelPrefix + "offer": model.LabelValue(server.Model.Offer),
+ vpsLabelPrefix + "datacenter": model.LabelValue(fmt.Sprintf("%+v", server.Model.Datacenter)),
+ vpsLabelPrefix + "model_vcore": model.LabelValue(fmt.Sprintf("%d", server.Model.Vcore)),
+ vpsLabelPrefix + "maximum_additional_ip": model.LabelValue(fmt.Sprintf("%d", server.Model.MaximumAdditionalIP)),
+ vpsLabelPrefix + "version": model.LabelValue(server.Model.Version),
+ vpsLabelPrefix + "model_name": model.LabelValue(server.Model.Name),
+ vpsLabelPrefix + "disk": model.LabelValue(fmt.Sprintf("%d", server.Model.Disk)),
+ vpsLabelPrefix + "memory": model.LabelValue(fmt.Sprintf("%d", server.Model.Memory)),
+ vpsLabelPrefix + "zone": model.LabelValue(server.Zone),
+ vpsLabelPrefix + "display_name": model.LabelValue(server.DisplayName),
+ vpsLabelPrefix + "cluster": model.LabelValue(server.Cluster),
+ vpsLabelPrefix + "state": model.LabelValue(server.State),
+ vpsLabelPrefix + "name": model.LabelValue(server.Name),
+ vpsLabelPrefix + "netboot_mode": model.LabelValue(server.NetbootMode),
+ vpsLabelPrefix + "memory_limit": model.LabelValue(fmt.Sprintf("%d", server.MemoryLimit)),
+ vpsLabelPrefix + "offer_type": model.LabelValue(server.OfferType),
+ vpsLabelPrefix + "vcore": model.LabelValue(fmt.Sprintf("%d", server.Vcore)),
+ vpsLabelPrefix + "ipv4": model.LabelValue(ipv4),
+ vpsLabelPrefix + "ipv6": model.LabelValue(ipv6),
+ }
+
+ targets = append(targets, labels)
+ }
+
+ return []*targetgroup.Group{{Source: d.getSource(), Targets: targets}}, nil
+}
diff --git a/discovery/ovhcloud/vps_test.go b/discovery/ovhcloud/vps_test.go
new file mode 100644
index 0000000000..b1177f215e
--- /dev/null
+++ b/discovery/ovhcloud/vps_test.go
@@ -0,0 +1,130 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed 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 ovhcloud
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ yaml "gopkg.in/yaml.v2"
+
+ "github.com/go-kit/log"
+ "github.com/prometheus/common/model"
+ "github.com/stretchr/testify/require"
+)
+
+func TestOvhCloudVpsRefresh(t *testing.T) {
+ var cfg SDConfig
+
+ mock := httptest.NewServer(http.HandlerFunc(MockVpsAPI))
+ defer mock.Close()
+ cfgString := fmt.Sprintf(`
+---
+service: vps
+endpoint: %s
+application_key: %s
+application_secret: %s
+consumer_key: %s`, mock.URL, ovhcloudApplicationKeyTest, ovhcloudApplicationSecretTest, ovhcloudConsumerKeyTest)
+
+ require.NoError(t, yaml.UnmarshalStrict([]byte(cfgString), &cfg))
+
+ d, err := newRefresher(&cfg, log.NewNopLogger())
+ require.NoError(t, err)
+ ctx := context.Background()
+ targetGroups, err := d.refresh(ctx)
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(targetGroups))
+ targetGroup := targetGroups[0]
+ require.NotNil(t, targetGroup)
+ require.NotNil(t, targetGroup.Targets)
+ require.Equal(t, 1, len(targetGroup.Targets))
+ for i, lbls := range []model.LabelSet{
+ {
+ "__address__": "192.0.2.1",
+ "__meta_ovhcloud_vps_ipv4": "192.0.2.1",
+ "__meta_ovhcloud_vps_ipv6": "",
+ "__meta_ovhcloud_vps_cluster": "cluster_test",
+ "__meta_ovhcloud_vps_datacenter": "[]",
+ "__meta_ovhcloud_vps_disk": "40",
+ "__meta_ovhcloud_vps_display_name": "abc",
+ "__meta_ovhcloud_vps_maximum_additional_ip": "16",
+ "__meta_ovhcloud_vps_memory": "2048",
+ "__meta_ovhcloud_vps_memory_limit": "2048",
+ "__meta_ovhcloud_vps_model_name": "vps-value-1-2-40",
+ "__meta_ovhcloud_vps_name": "abc",
+ "__meta_ovhcloud_vps_netboot_mode": "local",
+ "__meta_ovhcloud_vps_offer": "VPS abc",
+ "__meta_ovhcloud_vps_offer_type": "ssd",
+ "__meta_ovhcloud_vps_state": "running",
+ "__meta_ovhcloud_vps_vcore": "1",
+ "__meta_ovhcloud_vps_model_vcore": "1",
+ "__meta_ovhcloud_vps_version": "2019v1",
+ "__meta_ovhcloud_vps_zone": "zone",
+ "instance": "abc",
+ },
+ } {
+ t.Run(fmt.Sprintf("item %d", i), func(t *testing.T) {
+ require.Equal(t, lbls, targetGroup.Targets[i])
+ })
+ }
+}
+
+func MockVpsAPI(w http.ResponseWriter, r *http.Request) {
+ if r.Header.Get("X-Ovh-Application") != ovhcloudApplicationKeyTest {
+ http.Error(w, "bad application key", http.StatusBadRequest)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if r.URL.Path == "/vps" {
+ dedicatedServersList, err := os.ReadFile("testdata/vps/vps.json")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(dedicatedServersList)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ if r.URL.Path == "/vps/abc" {
+ dedicatedServer, err := os.ReadFile("testdata/vps/vps_details.json")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(dedicatedServer)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ if r.URL.Path == "/vps/abc/ips" {
+ dedicatedServerIPs, err := os.ReadFile("testdata/vps/vps_abc_ips.json")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(dedicatedServerIPs)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+}
diff --git a/discovery/refresh/refresh.go b/discovery/refresh/refresh.go
index 043b2f7ba8..919567a53b 100644
--- a/discovery/refresh/refresh.go
+++ b/discovery/refresh/refresh.go
@@ -114,7 +114,10 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
now := time.Now()
- defer d.duration.Observe(time.Since(now).Seconds())
+ defer func() {
+ d.duration.Observe(time.Since(now).Seconds())
+ }()
+
tgs, err := d.refreshf(ctx)
if err != nil {
d.failures.Inc()
diff --git a/discovery/registry.go b/discovery/registry.go
index 8274628c23..13168a07a7 100644
--- a/discovery/registry.go
+++ b/discovery/registry.go
@@ -253,7 +253,7 @@ func replaceYAMLTypeError(err error, oldTyp, newTyp reflect.Type) error {
oldStr := oldTyp.String()
newStr := newTyp.String()
for i, s := range e.Errors {
- e.Errors[i] = strings.Replace(s, oldStr, newStr, -1)
+ e.Errors[i] = strings.ReplaceAll(s, oldStr, newStr)
}
}
return err
diff --git a/discovery/vultr/vultr.go b/discovery/vultr/vultr.go
index 2f489e7d45..42881d3c19 100644
--- a/discovery/vultr/vultr.go
+++ b/discovery/vultr/vultr.go
@@ -202,10 +202,8 @@ func (d *Discovery) listInstances(ctx context.Context) ([]govultr.Instance, erro
if meta.Links.Next == "" {
break
- } else {
- listOptions.Cursor = meta.Links.Next
- continue
}
+ listOptions.Cursor = meta.Links.Next
}
return instances, nil
diff --git a/discovery/xds/xds_test.go b/discovery/xds/xds_test.go
index b6c141e283..974a47342f 100644
--- a/discovery/xds/xds_test.go
+++ b/discovery/xds/xds_test.go
@@ -74,16 +74,16 @@ func createTestHTTPServer(t *testing.T, responder discoveryResponder) *httptest.
discoveryRes, err := responder(discoveryReq)
if err != nil {
- w.WriteHeader(500)
+ w.WriteHeader(http.StatusInternalServerError)
return
}
if discoveryRes == nil {
- w.WriteHeader(304)
+ w.WriteHeader(http.StatusNotModified)
return
}
- w.WriteHeader(200)
+ w.WriteHeader(http.StatusOK)
data, err := protoJSONMarshalOptions.Marshal(discoveryRes)
require.NoError(t, err)
diff --git a/discovery/zookeeper/zookeeper.go b/discovery/zookeeper/zookeeper.go
index 308d63a5fc..cadff5fd2e 100644
--- a/discovery/zookeeper/zookeeper.go
+++ b/discovery/zookeeper/zookeeper.go
@@ -193,7 +193,7 @@ func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
}
for _, pathUpdate := range d.pathUpdates {
// Drain event channel in case the treecache leaks goroutines otherwise.
- for range pathUpdate {
+ for range pathUpdate { // nolint:revive
}
}
d.conn.Close()
diff --git a/docs/command-line/index.md b/docs/command-line/index.md
new file mode 100644
index 0000000000..53786fbb20
--- /dev/null
+++ b/docs/command-line/index.md
@@ -0,0 +1,4 @@
+---
+title: Command Line
+sort_rank: 9
+---
diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md
new file mode 100644
index 0000000000..46f286e009
--- /dev/null
+++ b/docs/command-line/prometheus.md
@@ -0,0 +1,59 @@
+---
+title: prometheus
+---
+
+# prometheus
+
+The Prometheus monitoring server
+
+
+
+## Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| -h
, --help
| Show context-sensitive help (also try --help-long and --help-man). | |
+| --version
| Show application version. | |
+| --config.file
| Prometheus configuration file path. | `prometheus.yml` |
+| --web.listen-address
| Address to listen on for UI, API, and telemetry. | `0.0.0.0:9090` |
+| --web.config.file
| [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication. | |
+| --web.read-timeout
| Maximum duration before timing out read of the request, and closing idle connections. | `5m` |
+| --web.max-connections
| Maximum number of simultaneous connections. | `512` |
+| --web.external-url
| The URL under which Prometheus is externally reachable (for example, if Prometheus is served via a reverse proxy). Used for generating relative and absolute links back to Prometheus itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Prometheus. If omitted, relevant URL components will be derived automatically. | |
+| --web.route-prefix
| Prefix for the internal routes of web endpoints. Defaults to path of --web.external-url. | |
+| --web.user-assets
| Path to static asset directory, available at /user. | |
+| --web.enable-lifecycle
| Enable shutdown and reload via HTTP request. | `false` |
+| --web.enable-admin-api
| Enable API endpoints for admin control actions. | `false` |
+| --web.enable-remote-write-receiver
| Enable API endpoint accepting remote write requests. | `false` |
+| --web.console.templates
| Path to the console template directory, available at /consoles. | `consoles` |
+| --web.console.libraries
| Path to the console library directory. | `console_libraries` |
+| --web.page-title
| Document title of Prometheus instance. | `Prometheus Time Series Collection and Processing Server` |
+| --web.cors.origin
| Regex for CORS origin. It is fully anchored. Example: 'https?://(domain1|domain2)\.com' | `.*` |
+| --storage.tsdb.path
| Base path for metrics storage. Use with server mode only. | `data/` |
+| --storage.tsdb.retention
| [DEPRECATED] How long to retain samples in storage. This flag has been deprecated, use "storage.tsdb.retention.time" instead. Use with server mode only. | |
+| --storage.tsdb.retention.time
| How long to retain samples in storage. When this flag is set it overrides "storage.tsdb.retention". If neither this flag nor "storage.tsdb.retention" nor "storage.tsdb.retention.size" is set, the retention time defaults to 15d. Units Supported: y, w, d, h, m, s, ms. Use with server mode only. | |
+| --storage.tsdb.retention.size
| Maximum number of bytes that can be stored for blocks. A unit is required, supported units: B, KB, MB, GB, TB, PB, EB. Ex: "512MB". Based on powers-of-2, so 1KB is 1024B. Use with server mode only. | |
+| --storage.tsdb.no-lockfile
| Do not create lockfile in data directory. Use with server mode only. | `false` |
+| --storage.tsdb.head-chunks-write-queue-size
| Size of the queue through which head chunks are written to the disk to be m-mapped, 0 disables the queue completely. Experimental. Use with server mode only. | `0` |
+| --storage.agent.path
| Base path for metrics storage. Use with agent mode only. | `data-agent/` |
+| --storage.agent.wal-compression
| Compress the agent WAL. Use with agent mode only. | `true` |
+| --storage.agent.retention.min-time
| Minimum age samples may be before being considered for deletion when the WAL is truncated Use with agent mode only. | |
+| --storage.agent.retention.max-time
| Maximum age samples may be before being forcibly deleted when the WAL is truncated Use with agent mode only. | |
+| --storage.agent.no-lockfile
| Do not create lockfile in data directory. Use with agent mode only. | `false` |
+| --storage.remote.flush-deadline
| How long to wait flushing sample on shutdown or config reload. | `1m` |
+| --storage.remote.read-sample-limit
| Maximum overall number of samples to return via the remote read interface, in a single query. 0 means no limit. This limit is ignored for streamed response types. Use with server mode only. | `5e7` |
+| --storage.remote.read-concurrent-limit
| Maximum number of concurrent remote read calls. 0 means no limit. Use with server mode only. | `10` |
+| --storage.remote.read-max-bytes-in-frame
| Maximum number of bytes in a single frame for streaming remote read response types before marshalling. Note that client might have limit on frame size as well. 1MB as recommended by protobuf by default. Use with server mode only. | `1048576` |
+| --rules.alert.for-outage-tolerance
| Max time to tolerate prometheus outage for restoring "for" state of alert. Use with server mode only. | `1h` |
+| --rules.alert.for-grace-period
| Minimum duration between alert and restored "for" state. This is maintained only for alerts with configured "for" time greater than grace period. Use with server mode only. | `10m` |
+| --rules.alert.resend-delay
| Minimum amount of time to wait before resending an alert to Alertmanager. Use with server mode only. | `1m` |
+| --alertmanager.notification-queue-capacity
| The capacity of the queue for pending Alertmanager notifications. Use with server mode only. | `10000` |
+| --query.lookback-delta
| The maximum lookback duration for retrieving metrics during expression evaluations and federation. Use with server mode only. | `5m` |
+| --query.timeout
| Maximum time a query may take before being aborted. Use with server mode only. | `2m` |
+| --query.max-concurrency
| Maximum number of queries executed concurrently. Use with server mode only. | `20` |
+| --query.max-samples
| Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return. Use with server mode only. | `50000000` |
+| --enable-feature
| Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | |
+| --log.level
| Only log messages with the given severity or above. One of: [debug, info, warn, error] | `info` |
+| --log.format
| Output format of log messages. One of: [logfmt, json] | `logfmt` |
+
+
diff --git a/docs/command-line/promtool.md b/docs/command-line/promtool.md
new file mode 100644
index 0000000000..673e8c0481
--- /dev/null
+++ b/docs/command-line/promtool.md
@@ -0,0 +1,611 @@
+---
+title: promtool
+---
+
+# promtool
+
+Tooling for the Prometheus monitoring system.
+
+
+
+## Flags
+
+| Flag | Description |
+| --- | --- |
+| -h
, --help
| Show context-sensitive help (also try --help-long and --help-man). |
+| --version
| Show application version. |
+| --enable-feature
| Comma separated feature names to enable (only PromQL related and no-default-scrape-port). See https://prometheus.io/docs/prometheus/latest/feature_flags/ for the options and more details. |
+
+
+
+
+## Commands
+
+| Command | Description |
+| --- | --- |
+| help | Show help. |
+| check | Check the resources for validity. |
+| query | Run query against a Prometheus server. |
+| debug | Fetch debug information. |
+| push | Push to a Prometheus server. |
+| test | Unit testing. |
+| tsdb | Run tsdb commands. |
+
+
+
+
+### `promtool help`
+
+Show help.
+
+
+
+#### Arguments
+
+| Argument | Description |
+| --- | --- |
+| command | Show help on command. |
+
+
+
+
+### `promtool check`
+
+Check the resources for validity.
+
+
+
+#### Flags
+
+| Flag | Description |
+| --- | --- |
+| --extended
| Print extended information related to the cardinality of the metrics. |
+
+
+
+
+##### `promtool check service-discovery`
+
+Perform service discovery for the given job name and report the results, including relabeling.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --timeout
| The time to wait for discovery results. | `30s` |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| config-file | The prometheus config file. | Yes |
+| job | The job to run service discovery for. | Yes |
+
+
+
+
+##### `promtool check config`
+
+Check if the config files are valid or not.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --syntax-only
| Only check the config file syntax, ignoring file and content validation referenced in the config | |
+| --lint
| Linting checks to apply to the rules specified in the config. Available options are: all, duplicate-rules, none. Use --lint=none to disable linting | `duplicate-rules` |
+| --lint-fatal
| Make lint errors exit with exit code 3. | `false` |
+| --agent
| Check config file for Prometheus in Agent mode. | |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| config-files | The config files to check. | Yes |
+
+
+
+
+##### `promtool check web-config`
+
+Check if the web config files are valid or not.
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| web-config-files | The config files to check. | Yes |
+
+
+
+
+##### `promtool check healthy`
+
+Check if the Prometheus server is healthy.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --http.config.file
| HTTP client configuration file for promtool to connect to Prometheus. | |
+| --url
| The URL for the Prometheus server. | `http://localhost:9090` |
+
+
+
+
+##### `promtool check ready`
+
+Check if the Prometheus server is ready.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --http.config.file
| HTTP client configuration file for promtool to connect to Prometheus. | |
+| --url
| The URL for the Prometheus server. | `http://localhost:9090` |
+
+
+
+
+##### `promtool check rules`
+
+Check if the rule files are valid or not.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --lint
| Linting checks to apply. Available options are: all, duplicate-rules, none. Use --lint=none to disable linting | `duplicate-rules` |
+| --lint-fatal
| Make lint errors exit with exit code 3. | `false` |
+
+
+
+
+###### Arguments
+
+| Argument | Description |
+| --- | --- |
+| rule-files | The rule files to check, default is read from standard input. |
+
+
+
+
+##### `promtool check metrics`
+
+Pass Prometheus metrics over stdin to lint them for consistency and correctness.
+
+examples:
+
+$ cat metrics.prom | promtool check metrics
+
+$ curl -s http://localhost:9090/metrics | promtool check metrics
+
+
+
+### `promtool query`
+
+Run query against a Prometheus server.
+
+
+
+#### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| -o
, --format
| Output format of the query. | `promql` |
+| --http.config.file
| HTTP client configuration file for promtool to connect to Prometheus. | |
+
+
+
+
+##### `promtool query instant`
+
+Run instant query.
+
+
+
+###### Flags
+
+| Flag | Description |
+| --- | --- |
+| --time
| Query evaluation time (RFC3339 or Unix timestamp). |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| server | Prometheus server to query. | Yes |
+| expr | PromQL query expression. | Yes |
+
+
+
+
+##### `promtool query range`
+
+Run range query.
+
+
+
+###### Flags
+
+| Flag | Description |
+| --- | --- |
+| --header
| Extra headers to send to server. |
+| --start
| Query range start time (RFC3339 or Unix timestamp). |
+| --end
| Query range end time (RFC3339 or Unix timestamp). |
+| --step
| Query step size (duration). |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| server | Prometheus server to query. | Yes |
+| expr | PromQL query expression. | Yes |
+
+
+
+
+##### `promtool query series`
+
+Run series query.
+
+
+
+###### Flags
+
+| Flag | Description |
+| --- | --- |
+| --match
| Series selector. Can be specified multiple times. |
+| --start
| Start time (RFC3339 or Unix timestamp). |
+| --end
| End time (RFC3339 or Unix timestamp). |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| server | Prometheus server to query. | Yes |
+
+
+
+
+##### `promtool query labels`
+
+Run labels query.
+
+
+
+###### Flags
+
+| Flag | Description |
+| --- | --- |
+| --start
| Start time (RFC3339 or Unix timestamp). |
+| --end
| End time (RFC3339 or Unix timestamp). |
+| --match
| Series selector. Can be specified multiple times. |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| server | Prometheus server to query. | Yes |
+| name | Label name to provide label values for. | Yes |
+
+
+
+
+### `promtool debug`
+
+Fetch debug information.
+
+
+
+##### `promtool debug pprof`
+
+Fetch profiling debug information.
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| server | Prometheus server to get pprof files from. | Yes |
+
+
+
+
+##### `promtool debug metrics`
+
+Fetch metrics debug information.
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| server | Prometheus server to get metrics from. | Yes |
+
+
+
+
+##### `promtool debug all`
+
+Fetch all debug information.
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| server | Prometheus server to get all debug information from. | Yes |
+
+
+
+
+### `promtool push`
+
+Push to a Prometheus server.
+
+
+
+#### Flags
+
+| Flag | Description |
+| --- | --- |
+| --http.config.file
| HTTP client configuration file for promtool to connect to Prometheus. |
+
+
+
+
+##### `promtool push metrics`
+
+Push metrics to a prometheus remote write (for testing purpose only).
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --label
| Label to attach to metrics. Can be specified multiple times. | `job=promtool` |
+| --timeout
| The time to wait for pushing metrics. | `30s` |
+| --header
| Prometheus remote write header. | |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| remote-write-url | Prometheus remote write url to push metrics. | Yes |
+| metric-files | The metric files to push, default is read from standard input. | |
+
+
+
+
+### `promtool test`
+
+Unit testing.
+
+
+
+##### `promtool test rules`
+
+Unit tests for rules.
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| test-rule-file | The unit test file. | Yes |
+
+
+
+
+### `promtool tsdb`
+
+Run tsdb commands.
+
+
+
+##### `promtool tsdb bench`
+
+Run benchmarks.
+
+
+
+##### `promtool tsdb bench write`
+
+Run a write performance benchmark.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --out
| Set the output path. | `benchout` |
+| --metrics
| Number of metrics to read. | `10000` |
+| --scrapes
| Number of scrapes to simulate. | `3000` |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Default |
+| --- | --- | --- |
+| file | Input file with samples data, default is (../../tsdb/testdata/20kseries.json). | `../../tsdb/testdata/20kseries.json` |
+
+
+
+
+##### `promtool tsdb analyze`
+
+Analyze churn, label pair cardinality and compaction efficiency.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --limit
| How many items to show in each list. | `20` |
+| --extended
| Run extended analysis. | |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Default |
+| --- | --- | --- |
+| db path | Database path (default is data/). | `data/` |
+| block id | Block to analyze (default is the last block). | |
+
+
+
+
+##### `promtool tsdb list`
+
+List tsdb blocks.
+
+
+
+###### Flags
+
+| Flag | Description |
+| --- | --- |
+| -r
, --human-readable
| Print human readable values. |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Default |
+| --- | --- | --- |
+| db path | Database path (default is data/). | `data/` |
+
+
+
+
+##### `promtool tsdb dump`
+
+Dump samples from a TSDB.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --min-time
| Minimum timestamp to dump. | `-9223372036854775808` |
+| --max-time
| Maximum timestamp to dump. | `9223372036854775807` |
+| --match
| Series selector. | `{__name__=~'(?s:.*)'}` |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Default |
+| --- | --- | --- |
+| db path | Database path (default is data/). | `data/` |
+
+
+
+
+##### `promtool tsdb create-blocks-from`
+
+[Experimental] Import samples from input and produce TSDB blocks. Please refer to the storage docs for more details.
+
+
+
+###### Flags
+
+| Flag | Description |
+| --- | --- |
+| -r
, --human-readable
| Print human readable values. |
+| -q
, --quiet
| Do not print created blocks. |
+
+
+
+
+##### `promtool tsdb create-blocks-from openmetrics`
+
+Import samples from OpenMetrics input and produce TSDB blocks. Please refer to the storage docs for more details.
+
+
+
+###### Arguments
+
+| Argument | Description | Default | Required |
+| --- | --- | --- | --- |
+| input file | OpenMetrics file to read samples from. | | Yes |
+| output directory | Output directory for generated blocks. | `data/` | |
+
+
+
+
+##### `promtool tsdb create-blocks-from rules`
+
+Create blocks of data for new recording rules.
+
+
+
+###### Flags
+
+| Flag | Description | Default |
+| --- | --- | --- |
+| --http.config.file
| HTTP client configuration file for promtool to connect to Prometheus. | |
+| --url
| The URL for the Prometheus API with the data where the rule will be backfilled from. | `http://localhost:9090` |
+| --start
| The time to start backfilling the new rule from. Must be a RFC3339 formatted date or Unix timestamp. Required. | |
+| --end
| If an end time is provided, all recording rules in the rule files provided will be backfilled to the end time. Default will backfill up to 3 hours ago. Must be a RFC3339 formatted date or Unix timestamp. | |
+| --output-dir
| Output directory for generated blocks. | `data/` |
+| --eval-interval
| How frequently to evaluate rules when backfilling if a value is not set in the recording rule files. | `60s` |
+
+
+
+
+###### Arguments
+
+| Argument | Description | Required |
+| --- | --- | --- |
+| rule-files | A list of one or more files containing recording rules to be backfilled. All recording rules listed in the files will be backfilled. Alerting rules are not evaluated. | Yes |
+
+
diff --git a/docs/configuration/alerting_rules.md b/docs/configuration/alerting_rules.md
index 74f6c02b12..3c1ec84f0f 100644
--- a/docs/configuration/alerting_rules.md
+++ b/docs/configuration/alerting_rules.md
@@ -32,7 +32,11 @@ groups:
```
The optional `for` clause causes Prometheus to wait for a certain duration
-between first encountering a new expression output vector element and counting an alert as firing for this element. In this case, Prometheus will check that the alert continues to be active during each evaluation for 10 minutes before firing the alert. Elements that are active, but not firing yet, are in the pending state.
+between first encountering a new expression output vector element and counting
+an alert as firing for this element. In this case, Prometheus will check that
+the alert continues to be active during each evaluation for 10 minutes before
+firing the alert. Elements that are active, but not firing yet, are in the pending state.
+Alerting rules without the `for` clause will become active on the first evaluation.
The `labels` clause allows specifying a set of additional labels to be attached
to the alert. Any existing conflicting labels will be overwritten. The label
diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md
index 49affc393d..30bb07a8c1 100644
--- a/docs/configuration/configuration.md
+++ b/docs/configuration/configuration.md
@@ -73,11 +73,49 @@ global:
# Reloading the configuration will reopen the file.
[ query_log_file: ]
+ # An uncompressed response body larger than this many bytes will cause the
+ # scrape to fail. 0 means no limit. Example: 100MB.
+ # This is an experimental feature, this behaviour could
+ # change or be removed in the future.
+ [ body_size_limit: | default = 0 ]
+
+ # Per-scrape limit on number of scraped samples that will be accepted.
+ # If more than this number of samples are present after metric relabeling
+ # the entire scrape will be treated as failed. 0 means no limit.
+ [ sample_limit: | default = 0 ]
+
+ # Per-scrape limit on number of labels that will be accepted for a sample. If
+ # more than this number of labels are present post metric-relabeling, the
+ # entire scrape will be treated as failed. 0 means no limit.
+ [ label_limit: | default = 0 ]
+
+ # Per-scrape limit on length of labels name that will be accepted for a sample.
+ # If a label name is longer than this number post metric-relabeling, the entire
+ # scrape will be treated as failed. 0 means no limit.
+ [ label_name_length_limit: | default = 0 ]
+
+ # Per-scrape limit on length of labels value that will be accepted for a sample.
+ # If a label value is longer than this number post metric-relabeling, the
+ # entire scrape will be treated as failed. 0 means no limit.
+ [ label_value_length_limit: | default = 0 ]
+
+ # Per-scrape config limit on number of unique targets that will be
+ # accepted. If more than this number of targets are present after target
+ # relabeling, Prometheus will mark the targets as failed without scraping them.
+ # 0 means no limit. This is an experimental feature, this behaviour could
+ # change in the future.
+ [ target_limit: | default = 0 ]
+
# Rule files specifies a list of globs. Rules and alerts are read from
# all matching files.
rule_files:
[ - ... ]
+# Scrape config files specifies a list of globs. Scrape configs are read from
+# all matching files and appended to the list of scrape configs.
+scrape_config_files:
+ [ - ... ]
+
# A list of scrape configurations.
scrape_configs:
[ - ... ]
@@ -129,6 +167,10 @@ job_name:
# Per-scrape timeout when scraping this job.
[ scrape_timeout: | default = ]
+# Whether to scrape a classic histogram that is also exposed as a native
+# histogram (has no effect without --enable-feature=native-histograms).
+[ scrape_classic_histograms: | default = false ]
+
# The HTTP resource path on which to fetch metrics from targets.
[ metrics_path: | default = /metrics ]
@@ -200,7 +242,7 @@ oauth2:
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# Configures the scrape request's TLS settings.
tls_config:
@@ -208,6 +250,16 @@ tls_config:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
+
# List of Azure service discovery configurations.
azure_sd_configs:
@@ -294,6 +346,10 @@ nomad_sd_configs:
openstack_sd_configs:
[ - ... ]
+# List of OVHcloud service discovery configurations.
+ovhcloud_sd_configs:
+ [ - ... ]
+
# List of PuppetDB service discovery configurations.
puppetdb_sd_configs:
[ - ... ]
@@ -331,6 +387,7 @@ metric_relabel_configs:
# This is an experimental feature, this behaviour could
# change or be removed in the future.
[ body_size_limit: | default = 0 ]
+
# Per-scrape limit on number of scraped samples that will be accepted.
# If more than this number of samples are present after metric relabeling
# the entire scrape will be treated as failed. 0 means no limit.
@@ -357,6 +414,11 @@ metric_relabel_configs:
# 0 means no limit. This is an experimental feature, this behaviour could
# change in the future.
[ target_limit: | default = 0 ]
+
+# Limit on total number of positive and negative buckets allowed in a single
+# native histogram. If this is exceeded, the entire scrape will be treated as
+# failed. 0 means no limit.
+[ native_histogram_bucket_limit: | default = 0 ]
```
Where `` must be unique across all scrape configurations.
@@ -366,11 +428,16 @@ Where `` must be unique across all scrape configurations.
A `tls_config` allows configuring TLS connections.
```yaml
-# CA certificate to validate API server certificate with.
+# CA certificate to validate API server certificate with. At most one of ca and ca_file is allowed.
+[ ca: ]
[ ca_file: ]
-# Certificate and key files for client cert authentication to the server.
+# Certificate and key for client cert authentication to the server.
+# At most one of cert and cert_file is allowed.
+# At most one of key and key_file is allowed.
+[ cert: ]
[ cert_file: ]
+[ key: ]
[ key_file: ]
# ServerName extension to indicate the name of the server.
@@ -385,6 +452,11 @@ A `tls_config` allows configuring TLS connections.
# If unset, Prometheus will use Go default minimum version, which is TLS 1.2.
# See MinVersion in https://pkg.go.dev/crypto/tls#Config.
[ min_version: ]
+# Maximum acceptable TLS version. Accepted values: TLS10 (TLS 1.0), TLS11 (TLS
+# 1.1), TLS12 (TLS 1.2), TLS13 (TLS 1.3).
+# If unset, Prometheus will use Go default maximum version, which is TLS 1.3.
+# See MaxVersion in https://pkg.go.dev/crypto/tls#Config.
+[ max_version: ]
```
### ``
@@ -418,6 +490,15 @@ tls_config:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
```
### ``
@@ -436,6 +517,7 @@ The following meta labels are available on targets during [relabeling](#relabel_
* `__meta_azure_machine_resource_group`: the machine's resource group
* `__meta_azure_machine_tag_`: each tag value of the machine
* `__meta_azure_machine_scale_set`: the name of the scale set which the vm is part of (this value is only set if you are using a [scale set](https://docs.microsoft.com/en-us/azure/virtual-machine-scale-sets/))
+* `__meta_azure_machine_size`: the machine size
* `__meta_azure_subscription_id`: the subscription ID
* `__meta_azure_tenant_id`: the tenant ID
@@ -496,12 +578,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -518,6 +609,7 @@ The following meta labels are available on targets during [relabeling](#relabel_
* `__meta_consul_address`: the address of the target
* `__meta_consul_dc`: the datacenter name for the target
* `__meta_consul_health`: the health status of the service
+* `__meta_consul_partition`: the admin partition name where the service is registered
* `__meta_consul_metadata_`: each node metadata key value of the target
* `__meta_consul_node`: the node name defined for the target
* `__meta_consul_service_address`: the service address of the target
@@ -532,10 +624,14 @@ The following meta labels are available on targets during [relabeling](#relabel_
# The information to access the Consul API. It is to be defined
# as the Consul documentation requires.
[ server: | default = "localhost:8500" ]
+# Prefix for URIs for when consul is behind an API gateway (reverse proxy).
+[ path_prefix: ]
[ token: ]
[ datacenter: ]
# Namespaces are only supported in Consul Enterprise.
[ namespace: ]
+# Admin Partitions are only supported in Consul Enterprise.
+[ partition: ]
[ scheme: | default = "http" ]
# The username and password fields are deprecated in favor of the basic_auth configuration.
[ username: ]
@@ -595,12 +691,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -673,12 +778,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -722,6 +836,15 @@ host:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# TLS configuration.
tls_config:
@@ -776,7 +899,7 @@ oauth2:
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
```
@@ -888,6 +1011,15 @@ host:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# TLS configuration.
tls_config:
@@ -944,7 +1076,7 @@ oauth2:
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
```
@@ -1057,6 +1189,54 @@ See below for the configuration options for EC2 discovery:
filters:
[ - name:
values: , [...] ]
+
+# Authentication information used to authenticate to the EC2 API.
+# Note that `basic_auth`, `authorization` and `oauth2` options are
+# mutually exclusive.
+# `password` and `password_file` are mutually exclusive.
+
+# Optional HTTP basic authentication information, currently not supported by AWS.
+basic_auth:
+ [ username: ]
+ [ password: ]
+ [ password_file: ]
+
+# Optional `Authorization` header configuration, currently not supported by AWS.
+authorization:
+ # Sets the authentication type.
+ [ type: | default: Bearer ]
+ # Sets the credentials. It is mutually exclusive with
+ # `credentials_file`.
+ [ credentials: ]
+ # Sets the credentials to the credentials read from the configured file.
+ # It is mutuall exclusive with `credentials`.
+ [ credentials_file: ]
+
+# Optional OAuth 2.0 configuration, currently not supported by AWS.
+oauth2:
+ [ ]
+
+# Optional proxy URL.
+[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
+
+# Configure whether HTTP requests follow HTTP 3xx redirects.
+[ follow_redirects: | default = true ]
+
+# Whether to enable HTTP2.
+[ enable_http2: | default: true ]
+
+# TLS configuration.
+tls_config:
+ [ ]
```
The [relabeling phase](#relabel_config) is the preferred and more powerful
@@ -1096,6 +1276,7 @@ The following meta labels are available on targets during [relabeling](#relabel_
* `__meta_openstack_address_pool`: the pool of the private IP.
* `__meta_openstack_instance_flavor`: the flavor of the OpenStack instance.
* `__meta_openstack_instance_id`: the OpenStack instance ID.
+* `__meta_openstack_instance_image`: the ID of the image the OpenStack instance is using.
* `__meta_openstack_instance_name`: the OpenStack instance name.
* `__meta_openstack_instance_status`: the status of the OpenStack instance.
* `__meta_openstack_private_ip`: the private IP of the OpenStack instance.
@@ -1173,6 +1354,66 @@ tls_config:
[ ]
```
+### ``
+
+OVHcloud SD configurations allow retrieving scrape targets from OVHcloud's [dedicated servers](https://www.ovhcloud.com/en/bare-metal/) and [VPS](https://www.ovhcloud.com/en/vps/) using
+their [API](https://api.ovh.com/).
+Prometheus will periodically check the REST endpoint and create a target for every discovered server.
+The role will try to use the public IPv4 address as default address, if there's none it will try to use the IPv6 one. This may be changed with relabeling.
+For OVHcloud's [public cloud instances](https://www.ovhcloud.com/en/public-cloud/) you can use the [openstack_sd_config](#openstack_sd_config).
+
+#### VPS
+
+* `__meta_ovhcloud_vps_cluster`: the cluster of the server
+* `__meta_ovhcloud_vps_datacenter`: the datacenter of the server
+* `__meta_ovhcloud_vps_disk`: the disk of the server
+* `__meta_ovhcloud_vps_display_name`: the display name of the server
+* `__meta_ovhcloud_vps_ipv4`: the IPv4 of the server
+* `__meta_ovhcloud_vps_ipv6`: the IPv6 of the server
+* `__meta_ovhcloud_vps_keymap`: the KVM keyboard layout of the server
+* `__meta_ovhcloud_vps_maximum_additional_ip`: the maximum additional IPs of the server
+* `__meta_ovhcloud_vps_memory_limit`: the memory limit of the server
+* `__meta_ovhcloud_vps_memory`: the memory of the server
+* `__meta_ovhcloud_vps_monitoring_ip_blocks`: the monitoring IP blocks of the server
+* `__meta_ovhcloud_vps_name`: the name of the server
+* `__meta_ovhcloud_vps_netboot_mode`: the netboot mode of the server
+* `__meta_ovhcloud_vps_offer_type`: the offer type of the server
+* `__meta_ovhcloud_vps_offer`: the offer of the server
+* `__meta_ovhcloud_vps_state`: the state of the server
+* `__meta_ovhcloud_vps_vcore`: the number of virtual cores of the server
+* `__meta_ovhcloud_vps_version`: the version of the server
+* `__meta_ovhcloud_vps_zone`: the zone of the server
+
+#### Dedicated servers
+
+* `__meta_ovhcloud_dedicated_server_commercial_range`: the commercial range of the server
+* `__meta_ovhcloud_dedicated_server_datacenter`: the datacenter of the server
+* `__meta_ovhcloud_dedicated_server_ipv4`: the IPv4 of the server
+* `__meta_ovhcloud_dedicated_server_ipv6`: the IPv6 of the server
+* `__meta_ovhcloud_dedicated_server_link_speed`: the link speed of the server
+* `__meta_ovhcloud_dedicated_server_name`: the name of the server
+* `__meta_ovhcloud_dedicated_server_os`: the operating system of the server
+* `__meta_ovhcloud_dedicated_server_rack`: the rack of the server
+* `__meta_ovhcloud_dedicated_server_reverse`: the reverse DNS name of the server
+* `__meta_ovhcloud_dedicated_server_server_id`: the ID of the server
+* `__meta_ovhcloud_dedicated_server_state`: the state of the server
+* `__meta_ovhcloud_dedicated_server_support_level`: the support level of the server
+
+See below for the configuration options for OVHcloud discovery:
+
+```yaml
+# Access key to use. https://api.ovh.com
+application_key:
+application_secret:
+consumer_key:
+# Service of the targets to retrieve. Must be `vps` or `dedicated_server`.
+service:
+# API endpoint. https://github.com/ovh/go-ovh#supported-apis
+[ endpoint: | default = "ovh-eu" ]
+# Refresh interval to re-read the resources list.
+[ refresh_interval: | default = 60s ]
+```
+
### ``
PuppetDB SD configurations allow retrieving scrape targets from
@@ -1253,12 +1494,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
```
See [this example Prometheus configuration file](/documentation/examples/prometheus-puppetdb.yml)
@@ -1409,7 +1659,6 @@ The labels below are only available for targets with `role` set to `hcloud`:
* `__meta_hetzner_hcloud_image_description`: the description of the server image
* `__meta_hetzner_hcloud_image_os_flavor`: the OS flavor of the server image
* `__meta_hetzner_hcloud_image_os_version`: the OS version of the server image
-* `__meta_hetzner_hcloud_image_description`: the description of the server image
* `__meta_hetzner_hcloud_datacenter_location`: the location of the server
* `__meta_hetzner_hcloud_datacenter_location_network_zone`: the network zone of the server
* `__meta_hetzner_hcloud_server_type`: the type of the server
@@ -1462,12 +1711,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -1547,12 +1805,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -1621,12 +1888,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -1708,6 +1984,7 @@ Available meta labels:
* `__meta_kubernetes_pod_annotationpresent_`: `true` for each annotation from the pod object.
* `__meta_kubernetes_pod_container_init`: `true` if the container is an [InitContainer](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/)
* `__meta_kubernetes_pod_container_name`: Name of the container the target address points to.
+* `__meta_kubernetes_pod_container_id`: ID of the container the target address points to. The ID is in the form `://`.
* `__meta_kubernetes_pod_container_image`: The image the container is using.
* `__meta_kubernetes_pod_container_port_name`: Name of the container port.
* `__meta_kubernetes_pod_container_port_number`: Number of the container port.
@@ -1761,6 +2038,8 @@ Available meta labels:
* `__meta_kubernetes_endpointslice_address_target_name`: Name of referenced object.
* `__meta_kubernetes_endpointslice_address_type`: The ip protocol family of the address of the target.
* `__meta_kubernetes_endpointslice_endpoint_conditions_ready`: Set to `true` or `false` for the referenced endpoint's ready state.
+ * `__meta_kubernetes_endpointslice_endpoint_conditions_serving`: Set to `true` or `false` for the referenced endpoint's serving state.
+ * `__meta_kubernetes_endpointslice_endpoint_conditions_terminating`: Set to `true` or `false` for the referenced endpoint's terminating state.
* `__meta_kubernetes_endpointslice_endpoint_topology_kubernetes_io_hostname`: Name of the node hosting the referenced endpoint.
* `__meta_kubernetes_endpointslice_endpoint_topology_present_kubernetes_io_hostname`: Flag that shows if the referenced object has a kubernetes.io/hostname annotation.
* `__meta_kubernetes_endpointslice_port`: Port of the referenced endpoint.
@@ -1834,12 +2113,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -1879,7 +2167,7 @@ attach_metadata:
See [this example Prometheus configuration file](/documentation/examples/prometheus-kubernetes.yml)
for a detailed example of configuring Prometheus for Kubernetes.
-You may wish to check out the 3rd party [Prometheus Operator](https://github.com/coreos/prometheus-operator),
+You may wish to check out the 3rd party [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator),
which automates the Prometheus setup on top of Kubernetes.
### ``
@@ -1911,6 +2199,15 @@ server:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# TLS configuration.
tls_config:
@@ -1947,7 +2244,7 @@ oauth2:
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
```
The [relabeling phase](#relabel_config) is the preferred and more powerful way
@@ -2000,6 +2297,54 @@ See below for the configuration options for Lightsail discovery:
# The port to scrape metrics from. If using the public IP address, this must
# instead be specified in the relabeling rule.
[ port: | default = 80 ]
+
+# Authentication information used to authenticate to the Lightsail API.
+# Note that `basic_auth`, `authorization` and `oauth2` options are
+# mutually exclusive.
+# `password` and `password_file` are mutually exclusive.
+
+# Optional HTTP basic authentication information, currently not supported by AWS.
+basic_auth:
+ [ username: ]
+ [ password: ]
+ [ password_file: ]
+
+# Optional `Authorization` header configuration, currently not supported by AWS.
+authorization:
+ # Sets the authentication type.
+ [ type: | default: Bearer ]
+ # Sets the credentials. It is mutually exclusive with
+ # `credentials_file`.
+ [ credentials: ]
+ # Sets the credentials to the credentials read from the configured file.
+ # It is mutuall exclusive with `credentials`.
+ [ credentials_file: ]
+
+# Optional OAuth 2.0 configuration, currently not supported by AWS.
+oauth2:
+ [ ]
+
+# Optional proxy URL.
+[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
+
+# Configure whether HTTP requests follow HTTP 3xx redirects.
+[ follow_redirects: | default = true ]
+
+# Whether to enable HTTP2.
+[ enable_http2: | default: true ]
+
+# TLS configuration.
+tls_config:
+ [ ]
```
### ``
@@ -2062,12 +2407,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -2153,7 +2507,7 @@ oauth2:
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration for connecting to marathon servers
tls_config:
@@ -2161,6 +2515,15 @@ tls_config:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
```
By default every app listed in Marathon will be scraped by Prometheus. If not all
@@ -2216,7 +2579,6 @@ The following meta labels are available on targets during [relabeling](#relabel_
# The information to access the Nomad API. It is to be defined
# as the Nomad documentation requires.
[ allow_stale: | default = true ]
-[ datacenter: ]
[ namespace: | default = default ]
[ refresh_interval: | default = 60s ]
[ region: | default = global ]
@@ -2251,12 +2613,21 @@ oauth2:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# TLS configuration.
tls_config:
@@ -2266,7 +2637,7 @@ tls_config:
### ``
Serverset SD configurations allow retrieving scrape targets from [Serversets]
-(https://github.com/twitter/finagle/tree/master/finagle-serversets) which are
+(https://github.com/twitter/finagle/tree/develop/finagle-serversets) which are
stored in [Zookeeper](https://zookeeper.apache.org/). Serversets are commonly
used by [Finagle](https://twitter.github.io/finagle/) and
[Aurora](https://aurora.apache.org/).
@@ -2428,12 +2799,21 @@ tls_config:
# Optional proxy URL.
[ proxy_url: ]
+# Comma-separated string that can contain IPs, CIDR notation, domain names
+# that should be excluded from proxying. IP and domain names can
+# contain port numbers.
+[ no_proxy: ]
+# Use proxy URL indicated by environment variables (HTTP_PROXY, https_proxy, HTTPs_PROXY, https_proxy, and no_proxy)
+[ proxy_from_environment: | default: false ]
+# Specifies headers to send to proxies during CONNECT requests.
+[ proxy_connect_header:
+ [ : [, ...] ] ]
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: | default: true ]
# Refresh interval to re-read the app instance list.
[ refresh_interval: | default = 30s ]
@@ -2540,10 +2920,19 @@ tags_filter:
[ follow_redirects: | default = true ]
# Whether to enable HTTP2.
-[ enable_http2: | default: true ]
+[ enable_http2: