mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 09:36:58 +02:00
feat(controller)!: publish metrics for all supported endpoint types (#5516)
* feat(controller): add more metrics for all supported endpoint types * feat(controller): add cardinality and labels for records metrics Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * feat(controller): add cardinality and labels for records metrics Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * feat(controller): add cardinality and labels for records metrics Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * feat(controller): add cardinality and labels for records metrics Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * feat(controller): add cardinality and labels for records metrics Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * feat(controller): add cardinality and labels for records metrics Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * feat(controller): add cardinality and labels for records metrics Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> * feat(controller): add cardinality and labels for records metrics Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * fix rebase Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> --------- Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>
This commit is contained in:
parent
662fb3652d
commit
d63bfb324c
@ -105,54 +105,37 @@ var (
|
|||||||
Help: "Number of Source errors.",
|
Help: "Number of Source errors.",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
registryARecords = metrics.NewGaugeWithOpts(
|
|
||||||
|
registryRecords = metrics.NewGaugedVectorOpts(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: "external_dns",
|
Namespace: "external_dns",
|
||||||
Subsystem: "registry",
|
Subsystem: "registry",
|
||||||
Name: "a_records",
|
Name: "records",
|
||||||
Help: "Number of Registry A records.",
|
Help: "Number of registry records partitioned by label name (vector).",
|
||||||
},
|
},
|
||||||
|
[]string{"record_type"},
|
||||||
)
|
)
|
||||||
registryAAAARecords = metrics.NewGaugeWithOpts(
|
|
||||||
prometheus.GaugeOpts{
|
sourceRecords = metrics.NewGaugedVectorOpts(
|
||||||
Namespace: "external_dns",
|
|
||||||
Subsystem: "registry",
|
|
||||||
Name: "aaaa_records",
|
|
||||||
Help: "Number of Registry AAAA records.",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
sourceARecords = metrics.NewGaugeWithOpts(
|
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: "external_dns",
|
Namespace: "external_dns",
|
||||||
Subsystem: "source",
|
Subsystem: "source",
|
||||||
Name: "a_records",
|
Name: "records",
|
||||||
Help: "Number of Source A records.",
|
Help: "Number of source records partitioned by label name (vector).",
|
||||||
},
|
},
|
||||||
|
[]string{"record_type"},
|
||||||
)
|
)
|
||||||
sourceAAAARecords = metrics.NewGaugeWithOpts(
|
|
||||||
prometheus.GaugeOpts{
|
verifiedRecords = metrics.NewGaugedVectorOpts(
|
||||||
Namespace: "external_dns",
|
|
||||||
Subsystem: "source",
|
|
||||||
Name: "aaaa_records",
|
|
||||||
Help: "Number of Source AAAA records.",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
verifiedARecords = metrics.NewGaugeWithOpts(
|
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: "external_dns",
|
Namespace: "external_dns",
|
||||||
Subsystem: "controller",
|
Subsystem: "controller",
|
||||||
Name: "verified_a_records",
|
Name: "verified_records",
|
||||||
Help: "Number of DNS A-records that exists both in source and registry.",
|
Help: "Number of DNS records that exists both in source and registry (vector).",
|
||||||
},
|
|
||||||
)
|
|
||||||
verifiedAAAARecords = metrics.NewGaugeWithOpts(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Namespace: "external_dns",
|
|
||||||
Subsystem: "controller",
|
|
||||||
Name: "verified_aaaa_records",
|
|
||||||
Help: "Number of DNS AAAA-records that exists both in source and registry.",
|
|
||||||
},
|
},
|
||||||
|
[]string{"record_type"},
|
||||||
)
|
)
|
||||||
|
|
||||||
consecutiveSoftErrors = metrics.NewGaugeWithOpts(
|
consecutiveSoftErrors = metrics.NewGaugeWithOpts(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Namespace: "external_dns",
|
Namespace: "external_dns",
|
||||||
@ -173,25 +156,24 @@ func init() {
|
|||||||
metrics.RegisterMetric.MustRegister(deprecatedRegistryErrors)
|
metrics.RegisterMetric.MustRegister(deprecatedRegistryErrors)
|
||||||
metrics.RegisterMetric.MustRegister(deprecatedSourceErrors)
|
metrics.RegisterMetric.MustRegister(deprecatedSourceErrors)
|
||||||
metrics.RegisterMetric.MustRegister(controllerNoChangesTotal)
|
metrics.RegisterMetric.MustRegister(controllerNoChangesTotal)
|
||||||
metrics.RegisterMetric.MustRegister(registryARecords)
|
|
||||||
metrics.RegisterMetric.MustRegister(registryAAAARecords)
|
metrics.RegisterMetric.MustRegister(registryRecords)
|
||||||
metrics.RegisterMetric.MustRegister(sourceARecords)
|
metrics.RegisterMetric.MustRegister(sourceRecords)
|
||||||
metrics.RegisterMetric.MustRegister(sourceAAAARecords)
|
metrics.RegisterMetric.MustRegister(verifiedRecords)
|
||||||
metrics.RegisterMetric.MustRegister(verifiedARecords)
|
|
||||||
metrics.RegisterMetric.MustRegister(verifiedAAAARecords)
|
|
||||||
metrics.RegisterMetric.MustRegister(consecutiveSoftErrors)
|
metrics.RegisterMetric.MustRegister(consecutiveSoftErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controller is responsible for orchestrating the different components.
|
// Controller is responsible for orchestrating the different components.
|
||||||
// It works in the following way:
|
// It works in the following way:
|
||||||
// * Ask the DNS provider for current list of endpoints.
|
// * Ask the DNS provider for the current list of endpoints.
|
||||||
// * Ask the Source for the desired list of endpoints.
|
// * Ask the Source for the desired list of endpoints.
|
||||||
// * Take both lists and calculate a Plan to move current towards desired state.
|
// * Take both lists and calculate a Plan to move current towards the desired state.
|
||||||
// * Tell the DNS provider to apply the changes calculated by the Plan.
|
// * Tell the DNS provider to apply the changes calculated by the Plan.
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
Source source.Source
|
Source source.Source
|
||||||
Registry registry.Registry
|
Registry registry.Registry
|
||||||
// The policy that defines which changes to DNS records are allowed
|
// The policy that defines which change to DNS records is allowed
|
||||||
Policy plan.Policy
|
Policy plan.Policy
|
||||||
// The interval between individual synchronizations
|
// The interval between individual synchronizations
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
@ -207,7 +189,7 @@ type Controller struct {
|
|||||||
ManagedRecordTypes []string
|
ManagedRecordTypes []string
|
||||||
// ExcludeRecordTypes are DNS record types that will be excluded from management.
|
// ExcludeRecordTypes are DNS record types that will be excluded from management.
|
||||||
ExcludeRecordTypes []string
|
ExcludeRecordTypes []string
|
||||||
// MinEventSyncInterval is used as window for batching events
|
// MinEventSyncInterval is used as a window for batching events
|
||||||
MinEventSyncInterval time.Duration
|
MinEventSyncInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,33 +201,37 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
|||||||
c.lastRunAt = time.Now()
|
c.lastRunAt = time.Now()
|
||||||
c.runAtMutex.Unlock()
|
c.runAtMutex.Unlock()
|
||||||
|
|
||||||
records, err := c.Registry.Records(ctx)
|
regMetrics := newMetricsRecorder()
|
||||||
|
|
||||||
|
regRecords, err := c.Registry.Records(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
registryErrorsTotal.Counter.Inc()
|
registryErrorsTotal.Counter.Inc()
|
||||||
deprecatedRegistryErrors.Counter.Inc()
|
deprecatedRegistryErrors.Counter.Inc()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
registryEndpointsTotal.Gauge.Set(float64(len(records)))
|
registryEndpointsTotal.Gauge.Set(float64(len(regRecords)))
|
||||||
regARecords, regAAAARecords := countAddressRecords(records)
|
|
||||||
registryARecords.Gauge.Set(float64(regARecords))
|
|
||||||
registryAAAARecords.Gauge.Set(float64(regAAAARecords))
|
|
||||||
ctx = context.WithValue(ctx, provider.RecordsContextKey, records)
|
|
||||||
|
|
||||||
endpoints, err := c.Source.Endpoints(ctx)
|
countAddressRecords(regMetrics, regRecords, registryRecords)
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, provider.RecordsContextKey, regRecords)
|
||||||
|
|
||||||
|
sourceEndpoints, err := c.Source.Endpoints(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sourceErrorsTotal.Counter.Inc()
|
sourceErrorsTotal.Counter.Inc()
|
||||||
deprecatedSourceErrors.Counter.Inc()
|
deprecatedSourceErrors.Counter.Inc()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sourceEndpointsTotal.Gauge.Set(float64(len(endpoints)))
|
|
||||||
srcARecords, srcAAAARecords := countAddressRecords(endpoints)
|
sourceEndpointsTotal.Gauge.Set(float64(len(sourceEndpoints)))
|
||||||
sourceARecords.Gauge.Set(float64(srcARecords))
|
|
||||||
sourceAAAARecords.Gauge.Set(float64(srcAAAARecords))
|
sourceMetrics := newMetricsRecorder()
|
||||||
vARecords, vAAAARecords := countMatchingAddressRecords(endpoints, records)
|
countAddressRecords(sourceMetrics, sourceEndpoints, sourceRecords)
|
||||||
verifiedARecords.Gauge.Set(float64(vARecords))
|
|
||||||
verifiedAAAARecords.Gauge.Set(float64(vAAAARecords))
|
vaMetrics := newMetricsRecorder()
|
||||||
endpoints, err = c.Registry.AdjustEndpoints(endpoints)
|
countMatchingAddressRecords(vaMetrics, sourceEndpoints, regRecords, verifiedRecords)
|
||||||
|
|
||||||
|
endpoints, err := c.Registry.AdjustEndpoints(sourceEndpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("adjusting endpoints: %w", err)
|
return fmt.Errorf("adjusting endpoints: %w", err)
|
||||||
}
|
}
|
||||||
@ -253,7 +239,7 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
|||||||
|
|
||||||
plan := &plan.Plan{
|
plan := &plan.Plan{
|
||||||
Policies: []plan.Policy{c.Policy},
|
Policies: []plan.Policy{c.Policy},
|
||||||
Current: records,
|
Current: regRecords,
|
||||||
Desired: endpoints,
|
Desired: endpoints,
|
||||||
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, registryFilter},
|
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, registryFilter},
|
||||||
ManagedRecords: c.ManagedRecordTypes,
|
ManagedRecords: c.ManagedRecordTypes,
|
||||||
@ -298,8 +284,8 @@ func latest(r time.Time, times ...time.Time) time.Time {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Counts the intersections of A and AAAA records in endpoint and registry.
|
// Counts the intersections of records in endpoint and registry.
|
||||||
func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) (int, int) {
|
func countMatchingAddressRecords(rec *metricsRecorder, endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint, metric metrics.GaugeVecMetric) {
|
||||||
recordsMap := make(map[string]map[string]struct{})
|
recordsMap := make(map[string]map[string]struct{})
|
||||||
for _, regRecord := range registryRecords {
|
for _, regRecord := range registryRecords {
|
||||||
if _, found := recordsMap[regRecord.DNSName]; !found {
|
if _, found := recordsMap[regRecord.DNSName]; !found {
|
||||||
@ -307,35 +293,32 @@ func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registryRecords
|
|||||||
}
|
}
|
||||||
recordsMap[regRecord.DNSName][regRecord.RecordType] = struct{}{}
|
recordsMap[regRecord.DNSName][regRecord.RecordType] = struct{}{}
|
||||||
}
|
}
|
||||||
aCount := 0
|
|
||||||
aaaaCount := 0
|
|
||||||
for _, sourceRecord := range endpoints {
|
for _, sourceRecord := range endpoints {
|
||||||
if _, found := recordsMap[sourceRecord.DNSName]; found {
|
if _, found := recordsMap[sourceRecord.DNSName]; found {
|
||||||
if _, found := recordsMap[sourceRecord.DNSName][sourceRecord.RecordType]; found {
|
if _, ok := recordsMap[sourceRecord.DNSName][sourceRecord.RecordType]; ok {
|
||||||
switch sourceRecord.RecordType {
|
rec.recordEndpointType(sourceRecord.RecordType)
|
||||||
case endpoint.RecordTypeA:
|
|
||||||
aCount++
|
|
||||||
case endpoint.RecordTypeAAAA:
|
|
||||||
aaaaCount++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return aCount, aaaaCount
|
|
||||||
|
for _, rt := range endpoint.KnownRecordTypes {
|
||||||
|
metric.SetWithLabels(rec.loadFloat64(rt), rt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func countAddressRecords(endpoints []*endpoint.Endpoint) (int, int) {
|
// countAddressRecords updates the metricsRecorder with the count of each record type
|
||||||
aCount := 0
|
// found in the provided endpoints slice, and sets the corresponding metrics for each
|
||||||
aaaaCount := 0
|
// known DNS record type using the sourceRecords metric.
|
||||||
|
func countAddressRecords(rec *metricsRecorder, endpoints []*endpoint.Endpoint, metric metrics.GaugeVecMetric) {
|
||||||
|
// compute the number of records per type
|
||||||
for _, endPoint := range endpoints {
|
for _, endPoint := range endpoints {
|
||||||
switch endPoint.RecordType {
|
rec.recordEndpointType(endPoint.RecordType)
|
||||||
case endpoint.RecordTypeA:
|
}
|
||||||
aCount++
|
// set metrics for each record type
|
||||||
case endpoint.RecordTypeAAAA:
|
for _, rt := range endpoint.KnownRecordTypes {
|
||||||
aaaaCount++
|
metric.SetWithLabels(rec.loadFloat64(rt), rt)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return aCount, aaaaCount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleRunOnce makes sure execution happens at most once per interval.
|
// ScheduleRunOnce makes sure execution happens at most once per interval.
|
||||||
|
@ -19,15 +19,12 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||||
@ -235,8 +232,9 @@ func TestRunOnce(t *testing.T) {
|
|||||||
// Validate that the mock source was called.
|
// Validate that the mock source was called.
|
||||||
source.AssertExpectations(t)
|
source.AssertExpectations(t)
|
||||||
// check the verified records
|
// check the verified records
|
||||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords.Gauge))
|
|
||||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords.Gauge))
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRun tests that Run correctly starts and stops
|
// TestRun tests that Run correctly starts and stops
|
||||||
@ -268,14 +266,9 @@ func TestRun(t *testing.T) {
|
|||||||
|
|
||||||
// Validate that the mock source was called.
|
// Validate that the mock source was called.
|
||||||
source.AssertExpectations(t)
|
source.AssertExpectations(t)
|
||||||
// check the verified records
|
|
||||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords.Gauge))
|
|
||||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords.Gauge))
|
|
||||||
}
|
|
||||||
|
|
||||||
func valueFromMetric(metric prometheus.Gauge) uint64 {
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
ref := reflect.ValueOf(metric)
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
return reflect.Indirect(ref).FieldByName("valBits").Uint()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRunOnce(t *testing.T) {
|
func TestShouldRunOnce(t *testing.T) {
|
||||||
@ -491,256 +484,6 @@ func TestWhenMultipleControllerConsidersAllFilteredComain(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyARecords(t *testing.T) {
|
|
||||||
testControllerFiltersDomains(
|
|
||||||
t,
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "create-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"1.2.3.4"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"8.8.8.8"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"8.8.8.8"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "create-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"1.2.3.4"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]*plan.Changes{},
|
|
||||||
)
|
|
||||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords.Gauge))
|
|
||||||
|
|
||||||
testControllerFiltersDomains(
|
|
||||||
t,
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"1.2.3.4"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"8.8.8.8"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.3.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"24.24.24.24"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"1.2.3.4"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"8.8.8.8"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]*plan.Changes{{
|
|
||||||
Create: []*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.3.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"24.24.24.24"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords.Gauge))
|
|
||||||
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedAAAARecords.Gauge))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyAAAARecords(t *testing.T) {
|
|
||||||
testControllerFiltersDomains(
|
|
||||||
t,
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "create-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "create-record.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]*plan.Changes{},
|
|
||||||
)
|
|
||||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords.Gauge))
|
|
||||||
|
|
||||||
testControllerFiltersDomains(
|
|
||||||
t,
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.3.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::3"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "some-record.2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]*plan.Changes{{
|
|
||||||
Create: []*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "some-record.3.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::3"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedARecords.Gauge))
|
|
||||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords.Gauge))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestARecords(t *testing.T) {
|
|
||||||
testControllerFiltersDomains(
|
|
||||||
t,
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "record1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"1.2.3.4"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "record2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"8.8.8.8"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeSRV,
|
|
||||||
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "record1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"1.2.3.4"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeSRV,
|
|
||||||
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]*plan.Changes{{
|
|
||||||
Create: []*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "record2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeA,
|
|
||||||
Targets: endpoint.Targets{"8.8.8.8"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceARecords.Gauge))
|
|
||||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryARecords.Gauge))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAAAARecords(t *testing.T) {
|
|
||||||
testControllerFiltersDomains(
|
|
||||||
t,
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "record1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "record2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeSRV,
|
|
||||||
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
|
||||||
[]*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "record1.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeSRV,
|
|
||||||
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]*plan.Changes{{
|
|
||||||
Create: []*endpoint.Endpoint{
|
|
||||||
{
|
|
||||||
DNSName: "record2.used.tld",
|
|
||||||
RecordType: endpoint.RecordTypeAAAA,
|
|
||||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
)
|
|
||||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceAAAARecords.Gauge))
|
|
||||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryAAAARecords.Gauge))
|
|
||||||
}
|
|
||||||
|
|
||||||
type toggleRegistry struct {
|
type toggleRegistry struct {
|
||||||
registry.NoopRegistry
|
registry.NoopRegistry
|
||||||
failCount int
|
failCount int
|
||||||
@ -749,7 +492,7 @@ type toggleRegistry struct {
|
|||||||
|
|
||||||
const toggleRegistryFailureCount = 3
|
const toggleRegistryFailureCount = 3
|
||||||
|
|
||||||
func (r *toggleRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
func (r *toggleRegistry) Records(_ context.Context) ([]*endpoint.Endpoint, error) {
|
||||||
r.failCountMu.Lock()
|
r.failCountMu.Lock()
|
||||||
defer r.failCountMu.Unlock()
|
defer r.failCountMu.Unlock()
|
||||||
if r.failCount < toggleRegistryFailureCount {
|
if r.failCount < toggleRegistryFailureCount {
|
||||||
@ -759,7 +502,7 @@ func (r *toggleRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, err
|
|||||||
return []*endpoint.Endpoint{}, nil
|
return []*endpoint.Endpoint{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *toggleRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
func (r *toggleRegistry) ApplyChanges(_ context.Context, changes *plan.Changes) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
controller/metrics.go
Normal file
54
controller/metrics.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 The Kubernetes 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 controller
|
||||||
|
|
||||||
|
import "sigs.k8s.io/external-dns/endpoint"
|
||||||
|
|
||||||
|
type metricsRecorder struct {
|
||||||
|
counterPerEndpointType map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMetricsRecorder() *metricsRecorder {
|
||||||
|
return &metricsRecorder{
|
||||||
|
counterPerEndpointType: map[string]int{
|
||||||
|
endpoint.RecordTypeA: 0,
|
||||||
|
endpoint.RecordTypeAAAA: 0,
|
||||||
|
endpoint.RecordTypeCNAME: 0,
|
||||||
|
endpoint.RecordTypeTXT: 0,
|
||||||
|
endpoint.RecordTypeSRV: 0,
|
||||||
|
endpoint.RecordTypeNS: 0,
|
||||||
|
endpoint.RecordTypePTR: 0,
|
||||||
|
endpoint.RecordTypeMX: 0,
|
||||||
|
endpoint.RecordTypeNAPTR: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metricsRecorder) recordEndpointType(endpointType string) {
|
||||||
|
m.counterPerEndpointType[endpointType]++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metricsRecorder) getEndpointTypeCount(endpointType string) int {
|
||||||
|
if count, ok := m.counterPerEndpointType[endpointType]; ok {
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metricsRecorder) loadFloat64(endpointType string) float64 {
|
||||||
|
return float64(m.getEndpointTypeCount(endpointType))
|
||||||
|
}
|
376
controller/metrics_test.go
Normal file
376
controller/metrics_test.go
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 The Kubernetes 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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
|
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||||
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRecordKnownEndpointType(t *testing.T) {
|
||||||
|
mr := newMetricsRecorder()
|
||||||
|
|
||||||
|
// Recording a built-in type should start at 1 and increment
|
||||||
|
mr.recordEndpointType(endpoint.RecordTypeA)
|
||||||
|
assert.Equal(t, 1, mr.getEndpointTypeCount(endpoint.RecordTypeA))
|
||||||
|
|
||||||
|
mr.recordEndpointType(endpoint.RecordTypeA)
|
||||||
|
assert.Equal(t, 2, mr.getEndpointTypeCount(endpoint.RecordTypeA))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecordUnknownEndpointType(t *testing.T) {
|
||||||
|
mr := newMetricsRecorder()
|
||||||
|
const customType = "CUSTOM"
|
||||||
|
|
||||||
|
// Unknown types start at zero
|
||||||
|
assert.Equal(t, 0, mr.getEndpointTypeCount(customType))
|
||||||
|
|
||||||
|
// First record sets to 1
|
||||||
|
mr.recordEndpointType(customType)
|
||||||
|
assert.Equal(t, 1, mr.getEndpointTypeCount(customType))
|
||||||
|
|
||||||
|
// Subsequent records increment
|
||||||
|
mr.recordEndpointType(customType)
|
||||||
|
assert.Equal(t, 2, mr.getEndpointTypeCount(customType))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadFloat64(t *testing.T) {
|
||||||
|
mr := newMetricsRecorder()
|
||||||
|
|
||||||
|
// loadFloat64 should return the float64 representation of the count
|
||||||
|
mr.recordEndpointType(endpoint.RecordTypeAAAA)
|
||||||
|
assert.InDelta(t, float64(1), mr.loadFloat64(endpoint.RecordTypeAAAA), 0.0001)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyARecords(t *testing.T) {
|
||||||
|
testControllerFiltersDomains(
|
||||||
|
t,
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "create-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"8.8.8.8"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"8.8.8.8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "create-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*plan.Changes{},
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
|
||||||
|
testControllerFiltersDomains(
|
||||||
|
t,
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"8.8.8.8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.3.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"24.24.24.24"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"8.8.8.8"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*plan.Changes{{
|
||||||
|
Create: []*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.3.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"24.24.24.24"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyAAAARecords(t *testing.T) {
|
||||||
|
testControllerFiltersDomains(
|
||||||
|
t,
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "create-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "create-record.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*plan.Changes{},
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
|
||||||
|
testControllerFiltersDomains(
|
||||||
|
t,
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.3.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "some-record.2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*plan.Changes{{
|
||||||
|
Create: []*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "some-record.3.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestARecords(t *testing.T) {
|
||||||
|
testControllerFiltersDomains(
|
||||||
|
t,
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "record1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "record2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"8.8.8.8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeSRV,
|
||||||
|
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "record1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeSRV,
|
||||||
|
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*plan.Changes{{
|
||||||
|
Create: []*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "record2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeA,
|
||||||
|
Targets: endpoint.Targets{"8.8.8.8"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAAAARecords(t *testing.T) {
|
||||||
|
testControllerFiltersDomains(
|
||||||
|
t,
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "record1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "record2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeSRV,
|
||||||
|
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "record1.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeSRV,
|
||||||
|
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*plan.Changes{{
|
||||||
|
Create: []*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
DNSName: "record2.used.tld",
|
||||||
|
RecordType: endpoint.RecordTypeAAAA,
|
||||||
|
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, sourceRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGaugeMetricsWithMixedRecords(t *testing.T) {
|
||||||
|
configuredEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
|
||||||
|
endpoint.RecordTypeA: 534,
|
||||||
|
endpoint.RecordTypeAAAA: 324,
|
||||||
|
endpoint.RecordTypeCNAME: 2,
|
||||||
|
endpoint.RecordTypeTXT: 56,
|
||||||
|
endpoint.RecordTypeSRV: 11,
|
||||||
|
endpoint.RecordTypeNS: 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
providerEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
|
||||||
|
endpoint.RecordTypeA: 5334,
|
||||||
|
endpoint.RecordTypeAAAA: 324,
|
||||||
|
endpoint.RecordTypeCNAME: 23,
|
||||||
|
endpoint.RecordTypeTXT: 6,
|
||||||
|
endpoint.RecordTypeSRV: 25,
|
||||||
|
endpoint.RecordTypeNS: 1,
|
||||||
|
endpoint.RecordTypePTR: 43,
|
||||||
|
})
|
||||||
|
|
||||||
|
cfg := externaldns.NewConfig()
|
||||||
|
cfg.ManagedDNSRecordTypes = endpoint.KnownRecordTypes
|
||||||
|
|
||||||
|
source := new(testutils.MockSource)
|
||||||
|
source.On("Endpoints").Return(configuredEndpoints, nil)
|
||||||
|
|
||||||
|
provider := &filteredMockProvider{
|
||||||
|
RecordsStore: providerEndpoints,
|
||||||
|
}
|
||||||
|
r, err := registry.NewNoopRegistry(provider)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctrl := &Controller{
|
||||||
|
Source: source,
|
||||||
|
Registry: r,
|
||||||
|
Policy: &plan.SyncPolicy{},
|
||||||
|
DomainFilter: endpoint.NewDomainFilter([]string{}),
|
||||||
|
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, ctrl.RunOnce(t.Context()))
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 534, sourceRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, sourceRecords.Gauge, map[string]string{"record_type": "cname"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 11, sourceRecords.Gauge, map[string]string{"record_type": "srv"})
|
||||||
|
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 5334, registryRecords.Gauge, map[string]string{"record_type": "a"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, registryRecords.Gauge, map[string]string{"record_type": "aaaa"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, registryRecords.Gauge, map[string]string{"record_type": "mx"})
|
||||||
|
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 43, registryRecords.Gauge, map[string]string{"record_type": "ptr"})
|
||||||
|
}
|
@ -24,18 +24,15 @@ curl https://localhost:7979/metrics
|
|||||||
| last_reconcile_timestamp_seconds | Gauge | controller | Timestamp of last attempted sync with the DNS provider |
|
| last_reconcile_timestamp_seconds | Gauge | controller | Timestamp of last attempted sync with the DNS provider |
|
||||||
| last_sync_timestamp_seconds | Gauge | controller | Timestamp of last successful sync with the DNS provider |
|
| last_sync_timestamp_seconds | Gauge | controller | Timestamp of last successful sync with the DNS provider |
|
||||||
| no_op_runs_total | Counter | controller | Number of reconcile loops ending up with no changes on the DNS provider side. |
|
| no_op_runs_total | Counter | controller | Number of reconcile loops ending up with no changes on the DNS provider side. |
|
||||||
| verified_a_records | Gauge | controller | Number of DNS A-records that exists both in source and registry. |
|
| verified_records | Gauge | controller | Number of DNS records that exists both in source and registry (vector). |
|
||||||
| verified_aaaa_records | Gauge | controller | Number of DNS AAAA-records that exists both in source and registry. |
|
|
||||||
| cache_apply_changes_calls | Counter | provider | Number of calls to the provider cache ApplyChanges. |
|
| cache_apply_changes_calls | Counter | provider | Number of calls to the provider cache ApplyChanges. |
|
||||||
| cache_records_calls | Counter | provider | Number of calls to the provider cache Records list. |
|
| cache_records_calls | Counter | provider | Number of calls to the provider cache Records list. |
|
||||||
| a_records | Gauge | registry | Number of Registry A records. |
|
|
||||||
| aaaa_records | Gauge | registry | Number of Registry AAAA records. |
|
|
||||||
| endpoints_total | Gauge | registry | Number of Endpoints in the registry |
|
| endpoints_total | Gauge | registry | Number of Endpoints in the registry |
|
||||||
| errors_total | Counter | registry | Number of Registry errors. |
|
| errors_total | Counter | registry | Number of Registry errors. |
|
||||||
| a_records | Gauge | source | Number of Source A records. |
|
| records | Gauge | registry | Number of registry records partitioned by label name (vector). |
|
||||||
| aaaa_records | Gauge | source | Number of Source AAAA records. |
|
|
||||||
| endpoints_total | Gauge | source | Number of Endpoints in all sources |
|
| endpoints_total | Gauge | source | Number of Endpoints in all sources |
|
||||||
| errors_total | Counter | source | Number of Source errors. |
|
| errors_total | Counter | source | Number of Source errors. |
|
||||||
|
| records | Gauge | source | Number of source records partitioned by label name (vector). |
|
||||||
| adjustendpoints_errors_total | Gauge | webhook_provider | Errors with AdjustEndpoints method |
|
| adjustendpoints_errors_total | Gauge | webhook_provider | Errors with AdjustEndpoints method |
|
||||||
| adjustendpoints_requests_total | Gauge | webhook_provider | Requests with AdjustEndpoints method |
|
| adjustendpoints_requests_total | Gauge | webhook_provider | Requests with AdjustEndpoints method |
|
||||||
| applychanges_errors_total | Gauge | webhook_provider | Errors with ApplyChanges method |
|
| applychanges_errors_total | Gauge | webhook_provider | Errors with ApplyChanges method |
|
||||||
|
@ -47,6 +47,19 @@ const (
|
|||||||
RecordTypeNAPTR = "NAPTR"
|
RecordTypeNAPTR = "NAPTR"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
KnownRecordTypes = []string{
|
||||||
|
RecordTypeA,
|
||||||
|
RecordTypeAAAA,
|
||||||
|
RecordTypeTXT,
|
||||||
|
RecordTypeSRV,
|
||||||
|
RecordTypeNS,
|
||||||
|
RecordTypePTR,
|
||||||
|
RecordTypeMX,
|
||||||
|
RecordTypeNAPTR,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// TTL is a structure defining the TTL of a DNS record
|
// TTL is a structure defining the TTL of a DNS record
|
||||||
type TTL int64
|
type TTL int64
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ func generateMarkdownTable(m *metrics.MetricRegistry, withRuntime bool) (string,
|
|||||||
"process_network_receive_bytes_total",
|
"process_network_receive_bytes_total",
|
||||||
"process_network_transmit_bytes_total",
|
"process_network_transmit_bytes_total",
|
||||||
}...)
|
}...)
|
||||||
|
sort.Strings(runtimeMetrics)
|
||||||
} else {
|
} else {
|
||||||
runtimeMetrics = []string{}
|
runtimeMetrics = []string{}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ func TestComputeMetrics(t *testing.T) {
|
|||||||
t.Errorf("Expected not empty metrics registry, got %d", len(reg.Metrics))
|
t.Errorf("Expected not empty metrics registry, got %d", len(reg.Metrics))
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Len(t, reg.Metrics, 22)
|
assert.Len(t, reg.Metrics, 19)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateMarkdownTableRenderer(t *testing.T) {
|
func TestGenerateMarkdownTableRenderer(t *testing.T) {
|
||||||
|
@ -17,9 +17,12 @@ limitations under the License.
|
|||||||
package testutils
|
package testutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
)
|
)
|
||||||
@ -132,3 +135,28 @@ func NewTargetsFromAddr(targets []netip.Addr) endpoint.Targets {
|
|||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateTestEndpointsByType generates a shuffled slice of test Endpoints for each record type and count specified in typeCounts.
|
||||||
|
// Usage example:
|
||||||
|
//
|
||||||
|
// endpoints := GenerateTestEndpointsByType(map[string]int{"A": 2, "CNAME": 1})
|
||||||
|
// // endpoints will contain 2 A records and 1 CNAME record with unique DNS names and targets.
|
||||||
|
func GenerateTestEndpointsByType(typeCounts map[string]int) []*endpoint.Endpoint {
|
||||||
|
var result []*endpoint.Endpoint
|
||||||
|
idx := 0
|
||||||
|
for rt, count := range typeCounts {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
result = append(result, &endpoint.Endpoint{
|
||||||
|
DNSName: fmt.Sprintf("%s-%d.example.com", strings.ToLower(rt), idx),
|
||||||
|
Targets: endpoint.Targets{fmt.Sprintf("192.0.2.%d", idx)},
|
||||||
|
RecordType: rt,
|
||||||
|
RecordTTL: 300,
|
||||||
|
})
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rand.Shuffle(len(result), func(i, j int) {
|
||||||
|
result[i], result[j] = result[j], result[i]
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
51
internal/testutils/metrics.go
Normal file
51
internal/testutils/metrics.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 The Kubernetes 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 testutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestHelperVerifyMetricsGaugeVectorWithLabels verifies that a prometheus.GaugeVec metric with specific labels has the expected value.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// labels := map[string]string{"method": "GET", "status": "200"}
|
||||||
|
// TestHelperVerifyMetricsGaugeVectorWithLabels(t, 42.0, myGaugeVec, labels)
|
||||||
|
func TestHelperVerifyMetricsGaugeVectorWithLabels(t *testing.T, expected float64, metric prometheus.GaugeVec, labels map[string]string) {
|
||||||
|
TestHelperVerifyMetricsGaugeVectorWithLabelsFunc(t, expected, assert.Equal, metric, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHelperVerifyMetricsGaugeVectorWithLabelsFunc is a helper function that verifies a prometheus.GaugeVec metric with specific labels using a custom assertion function.
|
||||||
|
func TestHelperVerifyMetricsGaugeVectorWithLabelsFunc(t *testing.T, expected float64, aFunc assert.ComparisonAssertionFunc, metric prometheus.GaugeVec, labels map[string]string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
g, err := metric.MetricVec.GetMetricWith(labels)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var m dto.Metric
|
||||||
|
err = g.Write(&m)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NotNil(t, m.Gauge)
|
||||||
|
|
||||||
|
aFunc(t, expected, *m.Gauge.Value, "Expected gauge value does not match the actual value", labels)
|
||||||
|
}
|
@ -54,7 +54,7 @@ func NewMetricsRegister() *MetricRegistry {
|
|||||||
// }
|
// }
|
||||||
func (m *MetricRegistry) MustRegister(cs IMetric) {
|
func (m *MetricRegistry) MustRegister(cs IMetric) {
|
||||||
switch v := cs.(type) {
|
switch v := cs.(type) {
|
||||||
case CounterMetric, GaugeMetric, CounterVecMetric:
|
case CounterMetric, GaugeMetric, CounterVecMetric, GaugeVecMetric:
|
||||||
if _, exists := m.mName[cs.Get().FQDN]; exists {
|
if _, exists := m.mName[cs.Get().FQDN]; exists {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@ -66,6 +66,8 @@ func (m *MetricRegistry) MustRegister(cs IMetric) {
|
|||||||
m.Registerer.MustRegister(metric.Counter)
|
m.Registerer.MustRegister(metric.Counter)
|
||||||
case GaugeMetric:
|
case GaugeMetric:
|
||||||
m.Registerer.MustRegister(metric.Gauge)
|
m.Registerer.MustRegister(metric.Gauge)
|
||||||
|
case GaugeVecMetric:
|
||||||
|
m.Registerer.MustRegister(metric.Gauge)
|
||||||
case CounterVecMetric:
|
case CounterVecMetric:
|
||||||
m.Registerer.MustRegister(metric.CounterVec)
|
m.Registerer.MustRegister(metric.CounterVec)
|
||||||
}
|
}
|
||||||
|
@ -60,8 +60,9 @@ func TestMustRegister(t *testing.T) {
|
|||||||
NewGaugeWithOpts(prometheus.GaugeOpts{Name: "test_gauge_3"}),
|
NewGaugeWithOpts(prometheus.GaugeOpts{Name: "test_gauge_3"}),
|
||||||
NewCounterWithOpts(prometheus.CounterOpts{Name: "test_counter_3"}),
|
NewCounterWithOpts(prometheus.CounterOpts{Name: "test_counter_3"}),
|
||||||
NewCounterVecWithOpts(prometheus.CounterOpts{Name: "test_counter_vec_3"}, []string{"label"}),
|
NewCounterVecWithOpts(prometheus.CounterOpts{Name: "test_counter_vec_3"}, []string{"label"}),
|
||||||
|
NewGaugedVectorOpts(prometheus.GaugeOpts{Name: "test_gauge_v_3"}, []string{"label"}),
|
||||||
},
|
},
|
||||||
expected: 3,
|
expected: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unsupported metric",
|
name: "unsupported metric",
|
||||||
|
@ -18,6 +18,7 @@ package metrics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
@ -68,6 +69,24 @@ func (g CounterVecMetric) Get() *Metric {
|
|||||||
return &g.Metric
|
return &g.Metric
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GaugeVecMetric struct {
|
||||||
|
Metric
|
||||||
|
Gauge prometheus.GaugeVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GaugeVecMetric) Get() *Metric {
|
||||||
|
return &g.Metric
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWithLabels sets the value of the Gauge metric for the specified label values.
|
||||||
|
// All label values are converted to lowercase before being applied.
|
||||||
|
func (g GaugeVecMetric) SetWithLabels(value float64, lvs ...string) {
|
||||||
|
for i, v := range lvs {
|
||||||
|
lvs[i] = strings.ToLower(v)
|
||||||
|
}
|
||||||
|
g.Gauge.WithLabelValues(lvs...).Set(value)
|
||||||
|
}
|
||||||
|
|
||||||
func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric {
|
func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric {
|
||||||
return GaugeMetric{
|
return GaugeMetric{
|
||||||
Metric: Metric{
|
Metric: Metric{
|
||||||
@ -82,6 +101,22 @@ func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewGaugedVectorOpts creates a new GaugeVec based on the provided GaugeOpts and
|
||||||
|
// partitioned by the given label names.
|
||||||
|
func NewGaugedVectorOpts(opts prometheus.GaugeOpts, labelNames []string) GaugeVecMetric {
|
||||||
|
return GaugeVecMetric{
|
||||||
|
Metric: Metric{
|
||||||
|
Type: "gauge",
|
||||||
|
Name: opts.Name,
|
||||||
|
FQDN: fmt.Sprintf("%s_%s", opts.Subsystem, opts.Name),
|
||||||
|
Namespace: opts.Namespace,
|
||||||
|
Subsystem: opts.Subsystem,
|
||||||
|
Help: opts.Help,
|
||||||
|
},
|
||||||
|
Gauge: *prometheus.NewGaugeVec(opts, labelNames),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewCounterWithOpts(opts prometheus.CounterOpts) CounterMetric {
|
func NewCounterWithOpts(opts prometheus.CounterOpts) CounterMetric {
|
||||||
return CounterMetric{
|
return CounterMetric{
|
||||||
Metric: Metric{
|
Metric: Metric{
|
||||||
|
@ -19,6 +19,8 @@ package metrics
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -81,3 +83,33 @@ func TestNewCounterVecWithOpts(t *testing.T) {
|
|||||||
assert.Equal(t, "test_subsystem_test_counter_vec", counterVecMetric.FQDN)
|
assert.Equal(t, "test_subsystem_test_counter_vec", counterVecMetric.FQDN)
|
||||||
assert.NotNil(t, counterVecMetric.CounterVec)
|
assert.NotNil(t, counterVecMetric.CounterVec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGaugeV_SetWithLabels(t *testing.T) {
|
||||||
|
opts := prometheus.GaugeOpts{
|
||||||
|
Name: "test_gauge",
|
||||||
|
Namespace: "test_ns",
|
||||||
|
Subsystem: "test_sub",
|
||||||
|
Help: "help text",
|
||||||
|
}
|
||||||
|
gv := NewGaugedVectorOpts(opts, []string{"label1", "label2"})
|
||||||
|
|
||||||
|
gv.SetWithLabels(1.23, "Alpha", "BETA")
|
||||||
|
|
||||||
|
g, err := gv.Gauge.GetMetricWithLabelValues("alpha", "beta")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var m dto.Metric
|
||||||
|
err = g.Write(&m)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, m.Gauge)
|
||||||
|
assert.InDelta(t, 1.23, *m.Gauge.Value, 0.01)
|
||||||
|
|
||||||
|
// Override the value
|
||||||
|
gv.SetWithLabels(4.56, "ALPHA", "beta")
|
||||||
|
// reuse g (same label combination)
|
||||||
|
err = g.Write(&m)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.InDelta(t, 4.56, *m.Gauge.Value, 0.01)
|
||||||
|
|
||||||
|
assert.Len(t, m.Label, 2)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user