sigs.k8s.io/external-dns@v0.14.1/provider/azure/azure_privatedns_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 azure
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	azcoreruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
    24  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
    25  	privatedns "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns"
    26  	"sigs.k8s.io/external-dns/endpoint"
    27  	"sigs.k8s.io/external-dns/plan"
    28  	"sigs.k8s.io/external-dns/provider"
    29  )
    30  
    31  const (
    32  	recordTTL = 300
    33  )
    34  
    35  // mockPrivateZonesClient implements the methods of the Azure Private DNS Zones Client which are used in the Azure Private DNS Provider
    36  // and returns static results which are defined per test
    37  type mockPrivateZonesClient struct {
    38  	pagingHandler azcoreruntime.PagingHandler[privatedns.PrivateZonesClientListByResourceGroupResponse]
    39  }
    40  
    41  func newMockPrivateZonesClient(zones []*privatedns.PrivateZone) mockPrivateZonesClient {
    42  	pagingHandler := azcoreruntime.PagingHandler[privatedns.PrivateZonesClientListByResourceGroupResponse]{
    43  		More: func(resp privatedns.PrivateZonesClientListByResourceGroupResponse) bool {
    44  			return false
    45  		},
    46  		Fetcher: func(context.Context, *privatedns.PrivateZonesClientListByResourceGroupResponse) (privatedns.PrivateZonesClientListByResourceGroupResponse, error) {
    47  			return privatedns.PrivateZonesClientListByResourceGroupResponse{
    48  				PrivateZoneListResult: privatedns.PrivateZoneListResult{
    49  					Value: zones,
    50  				},
    51  			}, nil
    52  		},
    53  	}
    54  	return mockPrivateZonesClient{
    55  		pagingHandler: pagingHandler,
    56  	}
    57  }
    58  
    59  func (client *mockPrivateZonesClient) NewListByResourceGroupPager(resourceGroupName string, options *privatedns.PrivateZonesClientListByResourceGroupOptions) *azcoreruntime.Pager[privatedns.PrivateZonesClientListByResourceGroupResponse] {
    60  	return azcoreruntime.NewPager(client.pagingHandler)
    61  }
    62  
    63  // mockPrivateRecordSetsClient implements the methods of the Azure Private DNS RecordSet Client which are used in the Azure Private DNS Provider
    64  // and returns static results which are defined per test
    65  type mockPrivateRecordSetsClient struct {
    66  	pagingHandler    azcoreruntime.PagingHandler[privatedns.RecordSetsClientListResponse]
    67  	deletedEndpoints []*endpoint.Endpoint
    68  	updatedEndpoints []*endpoint.Endpoint
    69  }
    70  
    71  func newMockPrivateRecordSectsClient(recordSets []*privatedns.RecordSet) mockPrivateRecordSetsClient {
    72  	pagingHandler := azcoreruntime.PagingHandler[privatedns.RecordSetsClientListResponse]{
    73  		More: func(resp privatedns.RecordSetsClientListResponse) bool {
    74  			return false
    75  		},
    76  		Fetcher: func(context.Context, *privatedns.RecordSetsClientListResponse) (privatedns.RecordSetsClientListResponse, error) {
    77  			return privatedns.RecordSetsClientListResponse{
    78  				RecordSetListResult: privatedns.RecordSetListResult{
    79  					Value: recordSets,
    80  				},
    81  			}, nil
    82  		},
    83  	}
    84  	return mockPrivateRecordSetsClient{
    85  		pagingHandler: pagingHandler,
    86  	}
    87  }
    88  
    89  func (client *mockPrivateRecordSetsClient) NewListPager(resourceGroupName string, privateZoneName string, options *privatedns.RecordSetsClientListOptions) *azcoreruntime.Pager[privatedns.RecordSetsClientListResponse] {
    90  	return azcoreruntime.NewPager(client.pagingHandler)
    91  }
    92  
    93  func (client *mockPrivateRecordSetsClient) Delete(ctx context.Context, resourceGroupName string, privateZoneName string, recordType privatedns.RecordType, relativeRecordSetName string, options *privatedns.RecordSetsClientDeleteOptions) (privatedns.RecordSetsClientDeleteResponse, error) {
    94  	client.deletedEndpoints = append(
    95  		client.deletedEndpoints,
    96  		endpoint.NewEndpoint(
    97  			formatAzureDNSName(relativeRecordSetName, privateZoneName),
    98  			string(recordType),
    99  			"",
   100  		),
   101  	)
   102  	return privatedns.RecordSetsClientDeleteResponse{}, nil
   103  }
   104  
   105  func (client *mockPrivateRecordSetsClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, recordType privatedns.RecordType, relativeRecordSetName string, parameters privatedns.RecordSet, options *privatedns.RecordSetsClientCreateOrUpdateOptions) (privatedns.RecordSetsClientCreateOrUpdateResponse, error) {
   106  	var ttl endpoint.TTL
   107  	if parameters.Properties.TTL != nil {
   108  		ttl = endpoint.TTL(*parameters.Properties.TTL)
   109  	}
   110  	client.updatedEndpoints = append(
   111  		client.updatedEndpoints,
   112  		endpoint.NewEndpointWithTTL(
   113  			formatAzureDNSName(relativeRecordSetName, privateZoneName),
   114  			string(recordType),
   115  			ttl,
   116  			extractAzurePrivateDNSTargets(&parameters)...,
   117  		),
   118  	)
   119  	return privatedns.RecordSetsClientCreateOrUpdateResponse{}, nil
   120  	//return parameters, nil
   121  }
   122  
   123  func createMockPrivateZone(zone string, id string) *privatedns.PrivateZone {
   124  	return &privatedns.PrivateZone{
   125  		ID:   to.Ptr(id),
   126  		Name: to.Ptr(zone),
   127  	}
   128  }
   129  
   130  func privateARecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
   131  	aRecords := make([]*privatedns.ARecord, len(values))
   132  	for i, value := range values {
   133  		aRecords[i] = &privatedns.ARecord{
   134  			IPv4Address: to.Ptr(value),
   135  		}
   136  	}
   137  	return &privatedns.RecordSetProperties{
   138  		TTL:      to.Ptr(ttl),
   139  		ARecords: aRecords,
   140  	}
   141  }
   142  
   143  func privateAAAARecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
   144  	aaaaRecords := make([]*privatedns.AaaaRecord, len(values))
   145  	for i, value := range values {
   146  		aaaaRecords[i] = &privatedns.AaaaRecord{
   147  			IPv6Address: to.Ptr(value),
   148  		}
   149  	}
   150  	return &privatedns.RecordSetProperties{
   151  		TTL:         to.Ptr(ttl),
   152  		AaaaRecords: aaaaRecords,
   153  	}
   154  }
   155  
   156  func privateCNameRecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
   157  	return &privatedns.RecordSetProperties{
   158  		TTL: to.Ptr(ttl),
   159  		CnameRecord: &privatedns.CnameRecord{
   160  			Cname: to.Ptr(values[0]),
   161  		},
   162  	}
   163  }
   164  
   165  func privateMXRecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
   166  	mxRecords := make([]*privatedns.MxRecord, len(values))
   167  	for i, target := range values {
   168  		mxRecord, _ := parseMxTarget[privatedns.MxRecord](target)
   169  		mxRecords[i] = &mxRecord
   170  	}
   171  	return &privatedns.RecordSetProperties{
   172  		TTL:       to.Ptr(ttl),
   173  		MxRecords: mxRecords,
   174  	}
   175  }
   176  
   177  func privateTxtRecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
   178  	return &privatedns.RecordSetProperties{
   179  		TTL: to.Ptr(ttl),
   180  		TxtRecords: []*privatedns.TxtRecord{
   181  			{
   182  				Value: []*string{&values[0]},
   183  			},
   184  		},
   185  	}
   186  }
   187  
   188  func privateOthersRecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
   189  	return &privatedns.RecordSetProperties{
   190  		TTL: to.Ptr(ttl),
   191  	}
   192  }
   193  
   194  func createPrivateMockRecordSet(name, recordType string, values ...string) *privatedns.RecordSet {
   195  	return createPrivateMockRecordSetMultiWithTTL(name, recordType, 0, values...)
   196  }
   197  
   198  func createPrivateMockRecordSetWithTTL(name, recordType, value string, ttl int64) *privatedns.RecordSet {
   199  	return createPrivateMockRecordSetMultiWithTTL(name, recordType, ttl, value)
   200  }
   201  
   202  func createPrivateMockRecordSetMultiWithTTL(name, recordType string, ttl int64, values ...string) *privatedns.RecordSet {
   203  	var getterFunc func(values []string, ttl int64) *privatedns.RecordSetProperties
   204  
   205  	switch recordType {
   206  	case endpoint.RecordTypeA:
   207  		getterFunc = privateARecordSetPropertiesGetter
   208  	case endpoint.RecordTypeAAAA:
   209  		getterFunc = privateAAAARecordSetPropertiesGetter
   210  	case endpoint.RecordTypeCNAME:
   211  		getterFunc = privateCNameRecordSetPropertiesGetter
   212  	case endpoint.RecordTypeMX:
   213  		getterFunc = privateMXRecordSetPropertiesGetter
   214  	case endpoint.RecordTypeTXT:
   215  		getterFunc = privateTxtRecordSetPropertiesGetter
   216  	default:
   217  		getterFunc = privateOthersRecordSetPropertiesGetter
   218  	}
   219  	return &privatedns.RecordSet{
   220  		Name:       to.Ptr(name),
   221  		Type:       to.Ptr("Microsoft.Network/privateDnsZones/" + recordType),
   222  		Properties: getterFunc(values, ttl),
   223  	}
   224  }
   225  
   226  // newMockedAzurePrivateDNSProvider creates an AzureProvider comprising the mocked clients for zones and recordsets
   227  func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, zones []*privatedns.PrivateZone, recordSets []*privatedns.RecordSet) (*AzurePrivateDNSProvider, error) {
   228  	zonesClient := newMockPrivateZonesClient(zones)
   229  	recordSetsClient := newMockPrivateRecordSectsClient(recordSets)
   230  	return newAzurePrivateDNSProvider(domainFilter, zoneIDFilter, dryRun, resourceGroup, &zonesClient, &recordSetsClient), nil
   231  }
   232  
   233  func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, privateZonesClient PrivateZonesClient, privateRecordsClient PrivateRecordSetsClient) *AzurePrivateDNSProvider {
   234  	return &AzurePrivateDNSProvider{
   235  		domainFilter:     domainFilter,
   236  		zoneIDFilter:     zoneIDFilter,
   237  		dryRun:           dryRun,
   238  		resourceGroup:    resourceGroup,
   239  		zonesClient:      privateZonesClient,
   240  		recordSetsClient: privateRecordsClient,
   241  	}
   242  }
   243  
   244  func TestAzurePrivateDNSRecord(t *testing.T) {
   245  	provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
   246  		[]*privatedns.PrivateZone{
   247  			createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
   248  		},
   249  		[]*privatedns.RecordSet{
   250  			createPrivateMockRecordSet("@", "NS", "ns1-03.azure-dns.com."),
   251  			createPrivateMockRecordSet("@", "SOA", "Email: azuredns-hostmaster.microsoft.com"),
   252  			createPrivateMockRecordSet("@", endpoint.RecordTypeA, "123.123.123.122"),
   253  			createPrivateMockRecordSet("@", endpoint.RecordTypeAAAA, "2001::123:123:123:122"),
   254  			createPrivateMockRecordSet("@", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"),
   255  			createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
   256  			createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeAAAA, "2001::123:123:123:123", 3600),
   257  			createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
   258  			createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
   259  			createPrivateMockRecordSetWithTTL("mail", endpoint.RecordTypeMX, "10 example.com", 4000),
   260  		})
   261  	if err != nil {
   262  		t.Fatal(err)
   263  	}
   264  
   265  	actual, err := provider.Records(context.Background())
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	expected := []*endpoint.Endpoint{
   270  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122"),
   271  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeAAAA, "2001::123:123:123:122"),
   272  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"),
   273  		endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
   274  		endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeAAAA, 3600, "2001::123:123:123:123"),
   275  		endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
   276  		endpoint.NewEndpointWithTTL("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
   277  		endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com"),
   278  	}
   279  
   280  	validateAzureEndpoints(t, actual, expected)
   281  }
   282  
   283  func TestAzurePrivateDNSMultiRecord(t *testing.T) {
   284  	provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
   285  		[]*privatedns.PrivateZone{
   286  			createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
   287  		},
   288  		[]*privatedns.RecordSet{
   289  			createPrivateMockRecordSet("@", "NS", "ns1-03.azure-dns.com."),
   290  			createPrivateMockRecordSet("@", "SOA", "Email: azuredns-hostmaster.microsoft.com"),
   291  			createPrivateMockRecordSet("@", endpoint.RecordTypeA, "123.123.123.122", "234.234.234.233"),
   292  			createPrivateMockRecordSet("@", endpoint.RecordTypeAAAA, "2001::123:123:123:122", "2001::234:234:234:233"),
   293  			createPrivateMockRecordSet("@", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"),
   294  			createPrivateMockRecordSetMultiWithTTL("nginx", endpoint.RecordTypeA, 3600, "123.123.123.123", "234.234.234.234"),
   295  			createPrivateMockRecordSetMultiWithTTL("nginx", endpoint.RecordTypeAAAA, 3600, "2001::123:123:123:123", "2001::234:234:234:234"),
   296  			createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
   297  			createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
   298  			createPrivateMockRecordSetMultiWithTTL("mail", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
   299  		})
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  
   304  	actual, err := provider.Records(context.Background())
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	expected := []*endpoint.Endpoint{
   309  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122", "234.234.234.233"),
   310  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeAAAA, "2001::123:123:123:122", "2001::234:234:234:233"),
   311  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"),
   312  		endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123", "234.234.234.234"),
   313  		endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeAAAA, 3600, "2001::123:123:123:123", "2001::234:234:234:234"),
   314  		endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
   315  		endpoint.NewEndpointWithTTL("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
   316  		endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
   317  	}
   318  
   319  	validateAzureEndpoints(t, actual, expected)
   320  }
   321  
   322  func TestAzurePrivateDNSApplyChanges(t *testing.T) {
   323  	recordsClient := mockPrivateRecordSetsClient{}
   324  
   325  	testAzurePrivateDNSApplyChangesInternal(t, false, &recordsClient)
   326  
   327  	validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{
   328  		endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""),
   329  		endpoint.NewEndpoint("deletedaaaa.example.com", endpoint.RecordTypeAAAA, ""),
   330  		endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""),
   331  	})
   332  
   333  	validateAzureEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{
   334  		endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
   335  		endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeAAAA, endpoint.TTL(recordTTL), "2001::1:2:3:4"),
   336  		endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
   337  		endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "1.2.3.5"),
   338  		endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeAAAA, endpoint.TTL(recordTTL), "2001::1:2:3:4", "2001::1:2:3:5"),
   339  		endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
   340  		endpoint.NewEndpointWithTTL("bar.example.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "other.com"),
   341  		endpoint.NewEndpointWithTTL("bar.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
   342  		endpoint.NewEndpointWithTTL("other.com", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "5.6.7.8"),
   343  		endpoint.NewEndpointWithTTL("other.com", endpoint.RecordTypeAAAA, endpoint.TTL(recordTTL), "2001::5:6:7:8"),
   344  		endpoint.NewEndpointWithTTL("other.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
   345  		endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
   346  		endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeAAAA, 3600, "2001::111:222:111:222"),
   347  		endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
   348  		endpoint.NewEndpointWithTTL("newmail.example.com", endpoint.RecordTypeMX, 7200, "40 bar.other.com"),
   349  		endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, endpoint.TTL(recordTTL), "10 other.com"),
   350  		endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
   351  	})
   352  }
   353  
   354  func TestAzurePrivateDNSApplyChangesDryRun(t *testing.T) {
   355  	recordsClient := mockRecordSetsClient{}
   356  
   357  	testAzureApplyChangesInternal(t, true, &recordsClient)
   358  
   359  	validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{})
   360  
   361  	validateAzureEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{})
   362  }
   363  
   364  func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, client PrivateRecordSetsClient) {
   365  	zones := []*privatedns.PrivateZone{
   366  		createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
   367  		createMockPrivateZone("other.com", "/privateDnsZones/other.com"),
   368  	}
   369  	zonesClient := newMockPrivateZonesClient(zones)
   370  
   371  	provider := newAzurePrivateDNSProvider(
   372  		endpoint.NewDomainFilter([]string{""}),
   373  		provider.NewZoneIDFilter([]string{""}),
   374  		dryRun,
   375  		"group",
   376  		&zonesClient,
   377  		client,
   378  	)
   379  
   380  	createRecords := []*endpoint.Endpoint{
   381  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"),
   382  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeAAAA, "2001::1:2:3:4"),
   383  		endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"),
   384  		endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.5", "1.2.3.4"),
   385  		endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeAAAA, "2001::1:2:3:5", "2001::1:2:3:4"),
   386  		endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"),
   387  		endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"),
   388  		endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"),
   389  		endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"),
   390  		endpoint.NewEndpoint("other.com", endpoint.RecordTypeAAAA, "2001::5:6:7:8"),
   391  		endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"),
   392  		endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
   393  		endpoint.NewEndpoint("nope.com", endpoint.RecordTypeAAAA, "2001::4:4:4:4"),
   394  		endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"),
   395  		endpoint.NewEndpoint("mail.example.com", endpoint.RecordTypeMX, "10 other.com"),
   396  		endpoint.NewEndpoint("mail.example.com", endpoint.RecordTypeTXT, "tag"),
   397  	}
   398  
   399  	currentRecords := []*endpoint.Endpoint{
   400  		endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"),
   401  		endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
   402  		endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"),
   403  		endpoint.NewEndpoint("oldmail.example.com", endpoint.RecordTypeMX, "20 foo.other.com"),
   404  	}
   405  	updatedRecords := []*endpoint.Endpoint{
   406  		endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
   407  		endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeAAAA, 3600, "2001::111:222:111:222"),
   408  		endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
   409  		endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
   410  		endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeAAAA, "2001::222:111:222:111"),
   411  		endpoint.NewEndpointWithTTL("newmail.example.com", endpoint.RecordTypeMX, 7200, "40 bar.other.com"),
   412  	}
   413  
   414  	deleteRecords := []*endpoint.Endpoint{
   415  		endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, "111.222.111.222"),
   416  		endpoint.NewEndpoint("deletedaaaa.example.com", endpoint.RecordTypeAAAA, "2001::111:222:111:222"),
   417  		endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
   418  		endpoint.NewEndpoint("deleted.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
   419  		endpoint.NewEndpoint("deleted.nope.com", endpoint.RecordTypeAAAA, "2001::222:111:222:111"),
   420  	}
   421  
   422  	changes := &plan.Changes{
   423  		Create:    createRecords,
   424  		UpdateNew: updatedRecords,
   425  		UpdateOld: currentRecords,
   426  		Delete:    deleteRecords,
   427  	}
   428  
   429  	if err := provider.ApplyChanges(context.Background(), changes); err != nil {
   430  		t.Fatal(err)
   431  	}
   432  }