Changes to add metric to external-dns.

1. external_dns_controller_verified_records - No of DNS A-records that exists both in source and registry - Gauge Metric
This commit is contained in:
Aditya Kolhar 2021-09-30 12:17:10 +05:30
parent fed5dd26b5
commit 39d5b39e18
3 changed files with 133 additions and 1 deletions

View File

@ -94,6 +94,14 @@ var (
Help: "Number of Source errors.", Help: "Number of Source errors.",
}, },
) )
verifiedARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "controller",
Name: "verified_a_records",
Help: "Number of DNS A-records that exists both in source and registry.",
},
)
) )
func init() { func init() {
@ -105,6 +113,7 @@ func init() {
prometheus.MustRegister(deprecatedRegistryErrors) prometheus.MustRegister(deprecatedRegistryErrors)
prometheus.MustRegister(deprecatedSourceErrors) prometheus.MustRegister(deprecatedSourceErrors)
prometheus.MustRegister(controllerNoChangesTotal) prometheus.MustRegister(controllerNoChangesTotal)
prometheus.MustRegister(verifiedARecords)
} }
// Controller is responsible for orchestrating the different components. // Controller is responsible for orchestrating the different components.
@ -151,7 +160,8 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return err return err
} }
sourceEndpointsTotal.Set(float64(len(endpoints))) sourceEndpointsTotal.Set(float64(len(endpoints)))
vRecords := fetchMatchingARecords(endpoints, records)
verifiedARecords.Set(float64(len(vRecords)))
endpoints = c.Registry.AdjustEndpoints(endpoints) endpoints = c.Registry.AdjustEndpoints(endpoints)
plan := &plan.Plan{ plan := &plan.Plan{
@ -181,6 +191,32 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return nil return nil
} }
// Checks and returns the intersection of A records in endpoint and registry.
func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string {
aRecords := filterARecords(endpoints)
recordsMap := make(map[string]struct{})
for _, regRecord := range registryRecords {
recordsMap[regRecord.DNSName] = struct{}{}
}
var cm []string
for _, sourceRecord := range aRecords {
if _, found := recordsMap[sourceRecord]; found {
cm = append(cm, sourceRecord)
}
}
return cm
}
func filterARecords(endpoints []*endpoint.Endpoint) []string {
var aRecords []string
for _, endPoint := range endpoints {
if endPoint.RecordType == endpoint.RecordTypeA {
aRecords = append(aRecords, endPoint.DNSName)
}
}
return aRecords
}
// ScheduleRunOnce makes sure execution happens at most once per interval. // ScheduleRunOnce makes sure execution happens at most once per interval.
func (c *Controller) ScheduleRunOnce(now time.Time) { func (c *Controller) ScheduleRunOnce(now time.Time) {
c.nextRunAtMux.Lock() c.nextRunAtMux.Lock()

View File

@ -19,6 +19,8 @@ package controller
import ( import (
"context" "context"
"errors" "errors"
"github.com/prometheus/client_golang/prometheus"
"math"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -49,6 +51,10 @@ type filteredMockProvider struct {
ApplyChangesCalls []*plan.Changes ApplyChangesCalls []*plan.Changes
} }
type errorMockProvider struct {
mockProvider
}
func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface { func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface {
return p.domainFilter return p.domainFilter
} }
@ -70,6 +76,10 @@ func (p *mockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error
return p.RecordsStore, nil return p.RecordsStore, nil
} }
func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
return nil, errors.New("error for testing")
}
// ApplyChanges validates that the passed in changes satisfy the assumptions. // ApplyChanges validates that the passed in changes satisfy the assumptions.
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if len(changes.Create) != len(p.ExpectChanges.Create) { if len(changes.Create) != len(p.ExpectChanges.Create) {
@ -180,6 +190,13 @@ 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
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
}
func valueFromMetric(metric prometheus.Gauge) uint64 {
ref := reflect.ValueOf(metric)
return reflect.Indirect(ref).FieldByName("valBits").Uint()
} }
func TestShouldRunOnce(t *testing.T) { func TestShouldRunOnce(t *testing.T) {
@ -376,3 +393,80 @@ 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))
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))
}

View File

@ -185,6 +185,8 @@ Here is the full list of available metrics provided by ExternalDNS:
| external_dns_registry_errors_total | Number of Registry errors | Counter | | external_dns_registry_errors_total | Number of Registry errors | Counter |
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge | | external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter | | external_dns_source_errors_total | Number of Source errors | Counter |
| external_dns_controller_verified_records | Number of DNS A-records that exists both in | Gauge |
| | source & registry | |
### How can I run ExternalDNS under a specific GCP Service Account, e.g. to access DNS records in other projects? ### How can I run ExternalDNS under a specific GCP Service Account, e.g. to access DNS records in other projects?