sigs.k8s.io/external-dns@v0.14.1/controller/controller_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controller
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"math"
    23  	"reflect"
    24  	"sort"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/prometheus/client_golang/prometheus"
    29  
    30  	"sigs.k8s.io/external-dns/endpoint"
    31  	"sigs.k8s.io/external-dns/internal/testutils"
    32  	"sigs.k8s.io/external-dns/pkg/apis/externaldns"
    33  	"sigs.k8s.io/external-dns/plan"
    34  	"sigs.k8s.io/external-dns/provider"
    35  	"sigs.k8s.io/external-dns/registry"
    36  
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  // mockProvider returns mock endpoints and validates changes.
    42  type mockProvider struct {
    43  	provider.BaseProvider
    44  	RecordsStore  []*endpoint.Endpoint
    45  	ExpectChanges *plan.Changes
    46  }
    47  
    48  type filteredMockProvider struct {
    49  	provider.BaseProvider
    50  	domainFilter      endpoint.DomainFilter
    51  	RecordsStore      []*endpoint.Endpoint
    52  	RecordsCallCount  int
    53  	ApplyChangesCalls []*plan.Changes
    54  }
    55  
    56  type errorMockProvider struct {
    57  	mockProvider
    58  }
    59  
    60  func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilter {
    61  	return p.domainFilter
    62  }
    63  
    64  // Records returns the desired mock endpoints.
    65  func (p *filteredMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
    66  	p.RecordsCallCount++
    67  	return p.RecordsStore, nil
    68  }
    69  
    70  // ApplyChanges stores all calls for later check
    71  func (p *filteredMockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
    72  	p.ApplyChangesCalls = append(p.ApplyChangesCalls, changes)
    73  	return nil
    74  }
    75  
    76  // Records returns the desired mock endpoints.
    77  func (p *mockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
    78  	return p.RecordsStore, nil
    79  }
    80  
    81  func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
    82  	return nil, errors.New("error for testing")
    83  }
    84  
    85  // ApplyChanges validates that the passed in changes satisfy the assumptions.
    86  func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
    87  	if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
    88  		return err
    89  	}
    90  
    91  	if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
    92  		return err
    93  	}
    94  
    95  	if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
    96  		return err
    97  	}
    98  
    99  	if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
   100  		return err
   101  	}
   102  
   103  	if !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) {
   104  		return errors.New("context is wrong")
   105  	}
   106  	return nil
   107  }
   108  
   109  func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
   110  	if len(actual) != len(expected) {
   111  		return errors.New("number of records is wrong")
   112  	}
   113  	sort.Slice(actual, func(i, j int) bool {
   114  		return actual[i].DNSName < actual[j].DNSName
   115  	})
   116  	for i := range actual {
   117  		if actual[i].DNSName != expected[i].DNSName || !actual[i].Targets.Same(expected[i].Targets) {
   118  			return errors.New("record is wrong")
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  // newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
   125  func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
   126  	dnsProvider := &mockProvider{
   127  		RecordsStore:  endpoints,
   128  		ExpectChanges: changes,
   129  	}
   130  
   131  	return dnsProvider
   132  }
   133  
   134  // TestRunOnce tests that RunOnce correctly orchestrates the different components.
   135  func TestRunOnce(t *testing.T) {
   136  	// Fake some desired endpoints coming from our source.
   137  	source := new(testutils.MockSource)
   138  	cfg := externaldns.NewConfig()
   139  	cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
   140  	source.On("Endpoints").Return([]*endpoint.Endpoint{
   141  		{
   142  			DNSName:    "create-record",
   143  			RecordType: endpoint.RecordTypeA,
   144  			Targets:    endpoint.Targets{"1.2.3.4"},
   145  		},
   146  		{
   147  			DNSName:    "update-record",
   148  			RecordType: endpoint.RecordTypeA,
   149  			Targets:    endpoint.Targets{"8.8.4.4"},
   150  		},
   151  		{
   152  			DNSName:    "create-aaaa-record",
   153  			RecordType: endpoint.RecordTypeAAAA,
   154  			Targets:    endpoint.Targets{"2001:DB8::1"},
   155  		},
   156  		{
   157  			DNSName:    "update-aaaa-record",
   158  			RecordType: endpoint.RecordTypeAAAA,
   159  			Targets:    endpoint.Targets{"2001:DB8::2"},
   160  		},
   161  	}, nil)
   162  
   163  	// Fake some existing records in our DNS provider and validate some desired changes.
   164  	provider := newMockProvider(
   165  		[]*endpoint.Endpoint{
   166  			{
   167  				DNSName:    "update-record",
   168  				RecordType: endpoint.RecordTypeA,
   169  				Targets:    endpoint.Targets{"8.8.8.8"},
   170  			},
   171  			{
   172  				DNSName:    "delete-record",
   173  				RecordType: endpoint.RecordTypeA,
   174  				Targets:    endpoint.Targets{"4.3.2.1"},
   175  			},
   176  			{
   177  				DNSName:    "update-aaaa-record",
   178  				RecordType: endpoint.RecordTypeAAAA,
   179  				Targets:    endpoint.Targets{"2001:DB8::3"},
   180  			},
   181  			{
   182  				DNSName:    "delete-aaaa-record",
   183  				RecordType: endpoint.RecordTypeAAAA,
   184  				Targets:    endpoint.Targets{"2001:DB8::4"},
   185  			},
   186  		},
   187  		&plan.Changes{
   188  			Create: []*endpoint.Endpoint{
   189  				{DNSName: "create-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
   190  				{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
   191  			},
   192  			UpdateNew: []*endpoint.Endpoint{
   193  				{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::2"}},
   194  				{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
   195  			},
   196  			UpdateOld: []*endpoint.Endpoint{
   197  				{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::3"}},
   198  				{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
   199  			},
   200  			Delete: []*endpoint.Endpoint{
   201  				{DNSName: "delete-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::4"}},
   202  				{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
   203  			},
   204  		},
   205  	)
   206  
   207  	r, err := registry.NewNoopRegistry(provider)
   208  	require.NoError(t, err)
   209  
   210  	// Run our controller once to trigger the validation.
   211  	ctrl := &Controller{
   212  		Source:             source,
   213  		Registry:           r,
   214  		Policy:             &plan.SyncPolicy{},
   215  		ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
   216  	}
   217  
   218  	assert.NoError(t, ctrl.RunOnce(context.Background()))
   219  
   220  	// Validate that the mock source was called.
   221  	source.AssertExpectations(t)
   222  	// check the verified records
   223  	assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
   224  	assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords))
   225  }
   226  
   227  func valueFromMetric(metric prometheus.Gauge) uint64 {
   228  	ref := reflect.ValueOf(metric)
   229  	return reflect.Indirect(ref).FieldByName("valBits").Uint()
   230  }
   231  
   232  func TestShouldRunOnce(t *testing.T) {
   233  	ctrl := &Controller{Interval: 10 * time.Minute, MinEventSyncInterval: 5 * time.Second}
   234  
   235  	now := time.Now()
   236  
   237  	// First run of Run loop should execute RunOnce
   238  	assert.True(t, ctrl.ShouldRunOnce(now))
   239  
   240  	// Second run should not
   241  	assert.False(t, ctrl.ShouldRunOnce(now))
   242  
   243  	now = now.Add(10 * time.Second)
   244  	// Changes happen in ingresses or services
   245  	ctrl.ScheduleRunOnce(now)
   246  	ctrl.ScheduleRunOnce(now)
   247  
   248  	// Because we batch changes, ShouldRunOnce returns False at first
   249  	assert.False(t, ctrl.ShouldRunOnce(now))
   250  	assert.False(t, ctrl.ShouldRunOnce(now.Add(100*time.Microsecond)))
   251  
   252  	// But after MinInterval we should run reconciliation
   253  	now = now.Add(5 * time.Second)
   254  	assert.True(t, ctrl.ShouldRunOnce(now))
   255  
   256  	// But just one time
   257  	assert.False(t, ctrl.ShouldRunOnce(now))
   258  
   259  	// We should wait maximum possible time after last reconciliation started
   260  	now = now.Add(10*time.Minute - time.Second)
   261  	assert.False(t, ctrl.ShouldRunOnce(now))
   262  
   263  	// After exactly Interval it's OK again to reconcile
   264  	now = now.Add(time.Second)
   265  	assert.True(t, ctrl.ShouldRunOnce(now))
   266  
   267  	// But not two times
   268  	assert.False(t, ctrl.ShouldRunOnce(now))
   269  
   270  	// Multiple ingresses or services changes, closer than MinInterval from each other
   271  	firstChangeTime := now
   272  	secondChangeTime := firstChangeTime.Add(time.Second)
   273  	// First change
   274  	ctrl.ScheduleRunOnce(firstChangeTime)
   275  	// Second change
   276  	ctrl.ScheduleRunOnce(secondChangeTime)
   277  	// Should not postpone the reconciliation further than firstChangeTime + MinInterval
   278  	now = now.Add(ctrl.MinEventSyncInterval)
   279  	assert.True(t, ctrl.ShouldRunOnce(now))
   280  }
   281  
   282  func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilter, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
   283  	t.Helper()
   284  	cfg := externaldns.NewConfig()
   285  	cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
   286  
   287  	source := new(testutils.MockSource)
   288  	source.On("Endpoints").Return(configuredEndpoints, nil)
   289  
   290  	// Fake some existing records in our DNS provider and validate some desired changes.
   291  	provider := &filteredMockProvider{
   292  		RecordsStore: providerEndpoints,
   293  	}
   294  	r, err := registry.NewNoopRegistry(provider)
   295  
   296  	require.NoError(t, err)
   297  
   298  	ctrl := &Controller{
   299  		Source:             source,
   300  		Registry:           r,
   301  		Policy:             &plan.SyncPolicy{},
   302  		DomainFilter:       domainFilter,
   303  		ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
   304  	}
   305  
   306  	assert.NoError(t, ctrl.RunOnce(context.Background()))
   307  	assert.Equal(t, 1, provider.RecordsCallCount)
   308  	require.Len(t, provider.ApplyChangesCalls, len(expectedChanges))
   309  	for i, change := range expectedChanges {
   310  		assert.Equal(t, *change, *provider.ApplyChangesCalls[i])
   311  	}
   312  }
   313  
   314  func TestControllerSkipsEmptyChanges(t *testing.T) {
   315  	testControllerFiltersDomains(
   316  		t,
   317  		[]*endpoint.Endpoint{
   318  			{
   319  				DNSName:    "create-record.other.tld",
   320  				RecordType: endpoint.RecordTypeA,
   321  				Targets:    endpoint.Targets{"1.2.3.4"},
   322  			},
   323  			{
   324  				DNSName:    "some-record.used.tld",
   325  				RecordType: endpoint.RecordTypeA,
   326  				Targets:    endpoint.Targets{"8.8.8.8"},
   327  			},
   328  		},
   329  		endpoint.NewDomainFilter([]string{"used.tld"}),
   330  		[]*endpoint.Endpoint{
   331  			{
   332  				DNSName:    "some-record.used.tld",
   333  				RecordType: endpoint.RecordTypeA,
   334  				Targets:    endpoint.Targets{"8.8.8.8"},
   335  			},
   336  		},
   337  		[]*plan.Changes{},
   338  	)
   339  }
   340  
   341  func TestWhenNoFilterControllerConsidersAllComain(t *testing.T) {
   342  	testControllerFiltersDomains(
   343  		t,
   344  		[]*endpoint.Endpoint{
   345  			{
   346  				DNSName:    "create-record.other.tld",
   347  				RecordType: endpoint.RecordTypeA,
   348  				Targets:    endpoint.Targets{"1.2.3.4"},
   349  			},
   350  			{
   351  				DNSName:    "some-record.used.tld",
   352  				RecordType: endpoint.RecordTypeA,
   353  				Targets:    endpoint.Targets{"8.8.8.8"},
   354  			},
   355  		},
   356  		endpoint.DomainFilter{},
   357  		[]*endpoint.Endpoint{
   358  			{
   359  				DNSName:    "some-record.used.tld",
   360  				RecordType: endpoint.RecordTypeA,
   361  				Targets:    endpoint.Targets{"8.8.8.8"},
   362  			},
   363  		},
   364  		[]*plan.Changes{
   365  			{
   366  				Create: []*endpoint.Endpoint{
   367  					{
   368  						DNSName:    "create-record.other.tld",
   369  						RecordType: endpoint.RecordTypeA,
   370  						Targets:    endpoint.Targets{"1.2.3.4"},
   371  					},
   372  				},
   373  			},
   374  		},
   375  	)
   376  }
   377  
   378  func TestWhenMultipleControllerConsidersAllFilteredComain(t *testing.T) {
   379  	testControllerFiltersDomains(
   380  		t,
   381  		[]*endpoint.Endpoint{
   382  			{
   383  				DNSName:    "create-record.other.tld",
   384  				RecordType: endpoint.RecordTypeA,
   385  				Targets:    endpoint.Targets{"1.2.3.4"},
   386  			},
   387  			{
   388  				DNSName:    "some-record.used.tld",
   389  				RecordType: endpoint.RecordTypeA,
   390  				Targets:    endpoint.Targets{"1.1.1.1"},
   391  			},
   392  			{
   393  				DNSName:    "create-record.unused.tld",
   394  				RecordType: endpoint.RecordTypeA,
   395  				Targets:    endpoint.Targets{"1.2.3.4"},
   396  			},
   397  		},
   398  		endpoint.NewDomainFilter([]string{"used.tld", "other.tld"}),
   399  		[]*endpoint.Endpoint{
   400  			{
   401  				DNSName:    "some-record.used.tld",
   402  				RecordType: endpoint.RecordTypeA,
   403  				Targets:    endpoint.Targets{"8.8.8.8"},
   404  			},
   405  		},
   406  		[]*plan.Changes{
   407  			{
   408  				Create: []*endpoint.Endpoint{
   409  					{
   410  						DNSName:    "create-record.other.tld",
   411  						RecordType: endpoint.RecordTypeA,
   412  						Targets:    endpoint.Targets{"1.2.3.4"},
   413  					},
   414  				},
   415  				UpdateOld: []*endpoint.Endpoint{
   416  					{
   417  						DNSName:    "some-record.used.tld",
   418  						RecordType: endpoint.RecordTypeA,
   419  						Targets:    endpoint.Targets{"8.8.8.8"},
   420  						Labels:     endpoint.Labels{},
   421  					},
   422  				},
   423  				UpdateNew: []*endpoint.Endpoint{
   424  					{
   425  						DNSName:    "some-record.used.tld",
   426  						RecordType: endpoint.RecordTypeA,
   427  						Targets:    endpoint.Targets{"1.1.1.1"},
   428  						Labels: endpoint.Labels{
   429  							"owner": "",
   430  						},
   431  					},
   432  				},
   433  			},
   434  		},
   435  	)
   436  }
   437  
   438  func TestVerifyARecords(t *testing.T) {
   439  	testControllerFiltersDomains(
   440  		t,
   441  		[]*endpoint.Endpoint{
   442  			{
   443  				DNSName:    "create-record.used.tld",
   444  				RecordType: endpoint.RecordTypeA,
   445  				Targets:    endpoint.Targets{"1.2.3.4"},
   446  			},
   447  			{
   448  				DNSName:    "some-record.used.tld",
   449  				RecordType: endpoint.RecordTypeA,
   450  				Targets:    endpoint.Targets{"8.8.8.8"},
   451  			},
   452  		},
   453  		endpoint.NewDomainFilter([]string{"used.tld"}),
   454  		[]*endpoint.Endpoint{
   455  			{
   456  				DNSName:    "some-record.used.tld",
   457  				RecordType: endpoint.RecordTypeA,
   458  				Targets:    endpoint.Targets{"8.8.8.8"},
   459  			},
   460  			{
   461  				DNSName:    "create-record.used.tld",
   462  				RecordType: endpoint.RecordTypeA,
   463  				Targets:    endpoint.Targets{"1.2.3.4"},
   464  			},
   465  		},
   466  		[]*plan.Changes{},
   467  	)
   468  	assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
   469  
   470  	testControllerFiltersDomains(
   471  		t,
   472  		[]*endpoint.Endpoint{
   473  			{
   474  				DNSName:    "some-record.1.used.tld",
   475  				RecordType: endpoint.RecordTypeA,
   476  				Targets:    endpoint.Targets{"1.2.3.4"},
   477  			},
   478  			{
   479  				DNSName:    "some-record.2.used.tld",
   480  				RecordType: endpoint.RecordTypeA,
   481  				Targets:    endpoint.Targets{"8.8.8.8"},
   482  			},
   483  			{
   484  				DNSName:    "some-record.3.used.tld",
   485  				RecordType: endpoint.RecordTypeA,
   486  				Targets:    endpoint.Targets{"24.24.24.24"},
   487  			},
   488  		},
   489  		endpoint.NewDomainFilter([]string{"used.tld"}),
   490  		[]*endpoint.Endpoint{
   491  			{
   492  				DNSName:    "some-record.1.used.tld",
   493  				RecordType: endpoint.RecordTypeA,
   494  				Targets:    endpoint.Targets{"1.2.3.4"},
   495  			},
   496  			{
   497  				DNSName:    "some-record.2.used.tld",
   498  				RecordType: endpoint.RecordTypeA,
   499  				Targets:    endpoint.Targets{"8.8.8.8"},
   500  			},
   501  		},
   502  		[]*plan.Changes{{
   503  			Create: []*endpoint.Endpoint{
   504  				{
   505  					DNSName:    "some-record.3.used.tld",
   506  					RecordType: endpoint.RecordTypeA,
   507  					Targets:    endpoint.Targets{"24.24.24.24"},
   508  				},
   509  			},
   510  		}},
   511  	)
   512  	assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
   513  	assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedAAAARecords))
   514  }
   515  
   516  func TestVerifyAAAARecords(t *testing.T) {
   517  	testControllerFiltersDomains(
   518  		t,
   519  		[]*endpoint.Endpoint{
   520  			{
   521  				DNSName:    "create-record.used.tld",
   522  				RecordType: endpoint.RecordTypeAAAA,
   523  				Targets:    endpoint.Targets{"2001:DB8::1"},
   524  			},
   525  			{
   526  				DNSName:    "some-record.used.tld",
   527  				RecordType: endpoint.RecordTypeAAAA,
   528  				Targets:    endpoint.Targets{"2001:DB8::2"},
   529  			},
   530  		},
   531  		endpoint.NewDomainFilter([]string{"used.tld"}),
   532  		[]*endpoint.Endpoint{
   533  			{
   534  				DNSName:    "some-record.used.tld",
   535  				RecordType: endpoint.RecordTypeAAAA,
   536  				Targets:    endpoint.Targets{"2001:DB8::2"},
   537  			},
   538  			{
   539  				DNSName:    "create-record.used.tld",
   540  				RecordType: endpoint.RecordTypeAAAA,
   541  				Targets:    endpoint.Targets{"2001:DB8::1"},
   542  			},
   543  		},
   544  		[]*plan.Changes{},
   545  	)
   546  	assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
   547  
   548  	testControllerFiltersDomains(
   549  		t,
   550  		[]*endpoint.Endpoint{
   551  			{
   552  				DNSName:    "some-record.1.used.tld",
   553  				RecordType: endpoint.RecordTypeAAAA,
   554  				Targets:    endpoint.Targets{"2001:DB8::1"},
   555  			},
   556  			{
   557  				DNSName:    "some-record.2.used.tld",
   558  				RecordType: endpoint.RecordTypeAAAA,
   559  				Targets:    endpoint.Targets{"2001:DB8::2"},
   560  			},
   561  			{
   562  				DNSName:    "some-record.3.used.tld",
   563  				RecordType: endpoint.RecordTypeAAAA,
   564  				Targets:    endpoint.Targets{"2001:DB8::3"},
   565  			},
   566  		},
   567  		endpoint.NewDomainFilter([]string{"used.tld"}),
   568  		[]*endpoint.Endpoint{
   569  			{
   570  				DNSName:    "some-record.1.used.tld",
   571  				RecordType: endpoint.RecordTypeAAAA,
   572  				Targets:    endpoint.Targets{"2001:DB8::1"},
   573  			},
   574  			{
   575  				DNSName:    "some-record.2.used.tld",
   576  				RecordType: endpoint.RecordTypeAAAA,
   577  				Targets:    endpoint.Targets{"2001:DB8::2"},
   578  			},
   579  		},
   580  		[]*plan.Changes{{
   581  			Create: []*endpoint.Endpoint{
   582  				{
   583  					DNSName:    "some-record.3.used.tld",
   584  					RecordType: endpoint.RecordTypeAAAA,
   585  					Targets:    endpoint.Targets{"2001:DB8::3"},
   586  				},
   587  			},
   588  		}},
   589  	)
   590  	assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedARecords))
   591  	assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
   592  }
   593  
   594  func TestARecords(t *testing.T) {
   595  	testControllerFiltersDomains(
   596  		t,
   597  		[]*endpoint.Endpoint{
   598  			{
   599  				DNSName:    "record1.used.tld",
   600  				RecordType: endpoint.RecordTypeA,
   601  				Targets:    endpoint.Targets{"1.2.3.4"},
   602  			},
   603  			{
   604  				DNSName:    "record2.used.tld",
   605  				RecordType: endpoint.RecordTypeA,
   606  				Targets:    endpoint.Targets{"8.8.8.8"},
   607  			},
   608  			{
   609  				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
   610  				RecordType: endpoint.RecordTypeSRV,
   611  				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
   612  			},
   613  		},
   614  		endpoint.NewDomainFilter([]string{"used.tld"}),
   615  		[]*endpoint.Endpoint{
   616  			{
   617  				DNSName:    "record1.used.tld",
   618  				RecordType: endpoint.RecordTypeA,
   619  				Targets:    endpoint.Targets{"1.2.3.4"},
   620  			},
   621  			{
   622  				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
   623  				RecordType: endpoint.RecordTypeSRV,
   624  				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
   625  			},
   626  		},
   627  		[]*plan.Changes{{
   628  			Create: []*endpoint.Endpoint{
   629  				{
   630  					DNSName:    "record2.used.tld",
   631  					RecordType: endpoint.RecordTypeA,
   632  					Targets:    endpoint.Targets{"8.8.8.8"},
   633  				},
   634  			},
   635  		}},
   636  	)
   637  	assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceARecords))
   638  	assert.Equal(t, math.Float64bits(1), valueFromMetric(registryARecords))
   639  }
   640  
   641  func TestAAAARecords(t *testing.T) {
   642  	testControllerFiltersDomains(
   643  		t,
   644  		[]*endpoint.Endpoint{
   645  			{
   646  				DNSName:    "record1.used.tld",
   647  				RecordType: endpoint.RecordTypeAAAA,
   648  				Targets:    endpoint.Targets{"2001:DB8::1"},
   649  			},
   650  			{
   651  				DNSName:    "record2.used.tld",
   652  				RecordType: endpoint.RecordTypeAAAA,
   653  				Targets:    endpoint.Targets{"2001:DB8::2"},
   654  			},
   655  			{
   656  				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
   657  				RecordType: endpoint.RecordTypeSRV,
   658  				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
   659  			},
   660  		},
   661  		endpoint.NewDomainFilter([]string{"used.tld"}),
   662  		[]*endpoint.Endpoint{
   663  			{
   664  				DNSName:    "record1.used.tld",
   665  				RecordType: endpoint.RecordTypeAAAA,
   666  				Targets:    endpoint.Targets{"2001:DB8::1"},
   667  			},
   668  			{
   669  				DNSName:    "_mysql-svc._tcp.mysql.used.tld",
   670  				RecordType: endpoint.RecordTypeSRV,
   671  				Targets:    endpoint.Targets{"0 50 30007 mysql.used.tld"},
   672  			},
   673  		},
   674  		[]*plan.Changes{{
   675  			Create: []*endpoint.Endpoint{
   676  				{
   677  					DNSName:    "record2.used.tld",
   678  					RecordType: endpoint.RecordTypeAAAA,
   679  					Targets:    endpoint.Targets{"2001:DB8::2"},
   680  				},
   681  			},
   682  		}},
   683  	)
   684  	assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceAAAARecords))
   685  	assert.Equal(t, math.Float64bits(1), valueFromMetric(registryAAAARecords))
   686  }