From 39d5b39e18badcb52e05262a7452bae1863fd6c2 Mon Sep 17 00:00:00 2001 From: Aditya Kolhar Date: Thu, 30 Sep 2021 12:17:10 +0530 Subject: [PATCH] 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 --- controller/controller.go | 38 +++++++++++++- controller/controller_test.go | 94 +++++++++++++++++++++++++++++++++++ docs/faq.md | 2 + 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/controller/controller.go b/controller/controller.go index 7d083cce15..6070d66dd5 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -94,6 +94,14 @@ var ( 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() { @@ -105,6 +113,7 @@ func init() { prometheus.MustRegister(deprecatedRegistryErrors) prometheus.MustRegister(deprecatedSourceErrors) prometheus.MustRegister(controllerNoChangesTotal) + prometheus.MustRegister(verifiedARecords) } // Controller is responsible for orchestrating the different components. @@ -151,7 +160,8 @@ func (c *Controller) RunOnce(ctx context.Context) error { return err } sourceEndpointsTotal.Set(float64(len(endpoints))) - + vRecords := fetchMatchingARecords(endpoints, records) + verifiedARecords.Set(float64(len(vRecords))) endpoints = c.Registry.AdjustEndpoints(endpoints) plan := &plan.Plan{ @@ -181,6 +191,32 @@ func (c *Controller) RunOnce(ctx context.Context) error { 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. func (c *Controller) ScheduleRunOnce(now time.Time) { c.nextRunAtMux.Lock() diff --git a/controller/controller_test.go b/controller/controller_test.go index dff2a6f62b..591c80d245 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -19,6 +19,8 @@ package controller import ( "context" "errors" + "github.com/prometheus/client_golang/prometheus" + "math" "reflect" "testing" "time" @@ -49,6 +51,10 @@ type filteredMockProvider struct { ApplyChangesCalls []*plan.Changes } +type errorMockProvider struct { + mockProvider +} + func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface { return p.domainFilter } @@ -70,6 +76,10 @@ func (p *mockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error 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. func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { if len(changes.Create) != len(p.ExpectChanges.Create) { @@ -180,6 +190,13 @@ func TestRunOnce(t *testing.T) { // Validate that the mock source was called. 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) { @@ -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)) +} diff --git a/docs/faq.md b/docs/faq.md index 67662a782b..157ff0d52c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -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_source_endpoints_total | Number of Endpoints in the registry | Gauge | | 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?