sigs.k8s.io/external-dns@v0.14.1/plan/conflict_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 plan
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/suite"
    24  	"sigs.k8s.io/external-dns/endpoint"
    25  )
    26  
    27  var _ ConflictResolver = PerResource{}
    28  
    29  type ResolverSuite struct {
    30  	// resolvers
    31  	perResource PerResource
    32  	// endpoints
    33  	fooV1Cname          *endpoint.Endpoint
    34  	fooV2Cname          *endpoint.Endpoint
    35  	fooV2CnameDuplicate *endpoint.Endpoint
    36  	fooA5               *endpoint.Endpoint
    37  	fooAAAA5            *endpoint.Endpoint
    38  	bar127A             *endpoint.Endpoint
    39  	bar192A             *endpoint.Endpoint
    40  	bar127AAnother      *endpoint.Endpoint
    41  	legacyBar192A       *endpoint.Endpoint // record created in AWS now without resource label
    42  	suite.Suite
    43  }
    44  
    45  func (suite *ResolverSuite) SetupTest() {
    46  	suite.perResource = PerResource{}
    47  	// initialize endpoints used in tests
    48  	suite.fooV1Cname = &endpoint.Endpoint{
    49  		DNSName:    "foo",
    50  		Targets:    endpoint.Targets{"v1"},
    51  		RecordType: "CNAME",
    52  		Labels: map[string]string{
    53  			endpoint.ResourceLabelKey: "ingress/default/foo-v1",
    54  		},
    55  	}
    56  	suite.fooV2Cname = &endpoint.Endpoint{
    57  		DNSName:    "foo",
    58  		Targets:    endpoint.Targets{"v2"},
    59  		RecordType: "CNAME",
    60  		Labels: map[string]string{
    61  			endpoint.ResourceLabelKey: "ingress/default/foo-v2",
    62  		},
    63  	}
    64  	suite.fooV2CnameDuplicate = &endpoint.Endpoint{
    65  		DNSName:    "foo",
    66  		Targets:    endpoint.Targets{"v2"},
    67  		RecordType: "CNAME",
    68  		Labels: map[string]string{
    69  			endpoint.ResourceLabelKey: "ingress/default/foo-v2-duplicate",
    70  		},
    71  	}
    72  	suite.fooA5 = &endpoint.Endpoint{
    73  		DNSName:    "foo",
    74  		Targets:    endpoint.Targets{"5.5.5.5"},
    75  		RecordType: "A",
    76  		Labels: map[string]string{
    77  			endpoint.ResourceLabelKey: "ingress/default/foo-5",
    78  		},
    79  	}
    80  	suite.fooAAAA5 = &endpoint.Endpoint{
    81  		DNSName:    "foo",
    82  		Targets:    endpoint.Targets{"2001:DB8::1"},
    83  		RecordType: "AAAA",
    84  		Labels: map[string]string{
    85  			endpoint.ResourceLabelKey: "ingress/default/foo-5",
    86  		},
    87  	}
    88  	suite.bar127A = &endpoint.Endpoint{
    89  		DNSName:    "bar",
    90  		Targets:    endpoint.Targets{"127.0.0.1"},
    91  		RecordType: "A",
    92  		Labels: map[string]string{
    93  			endpoint.ResourceLabelKey: "ingress/default/bar-127",
    94  		},
    95  	}
    96  	suite.bar127AAnother = &endpoint.Endpoint{ // TODO: remove this once we move to multiple targets under same endpoint
    97  		DNSName:    "bar",
    98  		Targets:    endpoint.Targets{"8.8.8.8"},
    99  		RecordType: "A",
   100  		Labels: map[string]string{
   101  			endpoint.ResourceLabelKey: "ingress/default/bar-127",
   102  		},
   103  	}
   104  	suite.bar192A = &endpoint.Endpoint{
   105  		DNSName:    "bar",
   106  		Targets:    endpoint.Targets{"192.168.0.1"},
   107  		RecordType: "A",
   108  		Labels: map[string]string{
   109  			endpoint.ResourceLabelKey: "ingress/default/bar-192",
   110  		},
   111  	}
   112  	suite.legacyBar192A = &endpoint.Endpoint{
   113  		DNSName:    "bar",
   114  		Targets:    endpoint.Targets{"192.168.0.1"},
   115  		RecordType: "A",
   116  	}
   117  }
   118  
   119  func (suite *ResolverSuite) TestStrictResolver() {
   120  	// test that perResource resolver picks min for create list
   121  	suite.Equal(suite.bar127A, suite.perResource.ResolveCreate([]*endpoint.Endpoint{suite.bar127A, suite.bar192A}), "should pick min one")
   122  	suite.Equal(suite.fooA5, suite.perResource.ResolveCreate([]*endpoint.Endpoint{suite.fooA5, suite.fooV1Cname}), "should pick min one")
   123  	suite.Equal(suite.fooV1Cname, suite.perResource.ResolveCreate([]*endpoint.Endpoint{suite.fooV2Cname, suite.fooV1Cname}), "should pick min one")
   124  
   125  	// test that perResource resolver preserves resource if it still exists
   126  	suite.Equal(suite.bar127AAnother, suite.perResource.ResolveUpdate(suite.bar127A, []*endpoint.Endpoint{suite.bar127AAnother, suite.bar127A}), "should pick min for update when same resource endpoint occurs multiple times (remove after multiple-target support") // TODO:remove this test
   127  	suite.Equal(suite.bar127A, suite.perResource.ResolveUpdate(suite.bar127A, []*endpoint.Endpoint{suite.bar192A, suite.bar127A}), "should pick existing resource")
   128  	suite.Equal(suite.fooV2Cname, suite.perResource.ResolveUpdate(suite.fooV2Cname, []*endpoint.Endpoint{suite.fooV2Cname, suite.fooV2CnameDuplicate}), "should pick existing resource even if targets are same")
   129  	suite.Equal(suite.fooA5, suite.perResource.ResolveUpdate(suite.fooV1Cname, []*endpoint.Endpoint{suite.fooA5, suite.fooV2Cname}), "should pick new if resource was deleted")
   130  	// should actually get the updated record (note ttl is different)
   131  	newFooV1Cname := &endpoint.Endpoint{
   132  		DNSName:    suite.fooV1Cname.DNSName,
   133  		Targets:    suite.fooV1Cname.Targets,
   134  		Labels:     suite.fooV1Cname.Labels,
   135  		RecordType: suite.fooV1Cname.RecordType,
   136  		RecordTTL:  suite.fooV1Cname.RecordTTL + 1, // ttl is different
   137  	}
   138  	suite.Equal(newFooV1Cname, suite.perResource.ResolveUpdate(suite.fooV1Cname, []*endpoint.Endpoint{suite.fooA5, suite.fooV2Cname, newFooV1Cname}), "should actually pick same resource with updates")
   139  
   140  	// legacy record's resource value will not match any candidates resource label
   141  	// therefore pick minimum again
   142  	suite.Equal(suite.bar127A, suite.perResource.ResolveUpdate(suite.legacyBar192A, []*endpoint.Endpoint{suite.bar127A, suite.bar192A}), " legacy record's resource value will not match, should pick minimum")
   143  }
   144  
   145  func (suite *ResolverSuite) TestPerResource_ResolveRecordTypes() {
   146  	type args struct {
   147  		key planKey
   148  		row *planTableRow
   149  	}
   150  	tests := []struct {
   151  		name string
   152  		args args
   153  		want map[string]*domainEndpoints
   154  	}{
   155  		{
   156  			name: "no conflict: cname record",
   157  			args: args{
   158  				key: planKey{dnsName: "foo"},
   159  				row: &planTableRow{
   160  					candidates: []*endpoint.Endpoint{suite.fooV1Cname},
   161  					records: map[string]*domainEndpoints{
   162  						endpoint.RecordTypeCNAME: {
   163  							candidates: []*endpoint.Endpoint{suite.fooV1Cname},
   164  						},
   165  					},
   166  				},
   167  			},
   168  			want: map[string]*domainEndpoints{
   169  				endpoint.RecordTypeCNAME: {
   170  					candidates: []*endpoint.Endpoint{suite.fooV1Cname},
   171  				},
   172  			},
   173  		},
   174  		{
   175  			name: "no conflict: a record",
   176  			args: args{
   177  				key: planKey{dnsName: "foo"},
   178  				row: &planTableRow{
   179  					current:    []*endpoint.Endpoint{suite.fooA5},
   180  					candidates: []*endpoint.Endpoint{suite.fooA5},
   181  					records: map[string]*domainEndpoints{
   182  						endpoint.RecordTypeA: {
   183  							current:    suite.fooA5,
   184  							candidates: []*endpoint.Endpoint{suite.fooA5},
   185  						},
   186  					},
   187  				},
   188  			},
   189  			want: map[string]*domainEndpoints{
   190  				endpoint.RecordTypeA: {
   191  					current:    suite.fooA5,
   192  					candidates: []*endpoint.Endpoint{suite.fooA5},
   193  				},
   194  			},
   195  		},
   196  		{
   197  			name: "no conflict: a and aaaa records",
   198  			args: args{
   199  				key: planKey{dnsName: "foo"},
   200  				row: &planTableRow{
   201  					candidates: []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA5},
   202  					records: map[string]*domainEndpoints{
   203  						endpoint.RecordTypeA: {
   204  							candidates: []*endpoint.Endpoint{suite.fooA5},
   205  						},
   206  						endpoint.RecordTypeAAAA: {
   207  							candidates: []*endpoint.Endpoint{suite.fooAAAA5},
   208  						},
   209  					},
   210  				},
   211  			},
   212  			want: map[string]*domainEndpoints{
   213  				endpoint.RecordTypeA: {
   214  					candidates: []*endpoint.Endpoint{suite.fooA5},
   215  				},
   216  				endpoint.RecordTypeAAAA: {
   217  					candidates: []*endpoint.Endpoint{suite.fooAAAA5},
   218  				},
   219  			},
   220  		},
   221  		{
   222  			name: "conflict: cname and a records",
   223  			args: args{
   224  				key: planKey{dnsName: "foo"},
   225  				row: &planTableRow{
   226  					current:    []*endpoint.Endpoint{suite.fooV1Cname},
   227  					candidates: []*endpoint.Endpoint{suite.fooV1Cname, suite.fooA5},
   228  					records: map[string]*domainEndpoints{
   229  						endpoint.RecordTypeCNAME: {
   230  							current:    suite.fooV1Cname,
   231  							candidates: []*endpoint.Endpoint{suite.fooV1Cname},
   232  						},
   233  						endpoint.RecordTypeA: {
   234  							candidates: []*endpoint.Endpoint{suite.fooA5},
   235  						},
   236  					},
   237  				},
   238  			},
   239  			want: map[string]*domainEndpoints{
   240  				endpoint.RecordTypeCNAME: {
   241  					current:    suite.fooV1Cname,
   242  					candidates: []*endpoint.Endpoint{},
   243  				},
   244  				endpoint.RecordTypeA: {
   245  					candidates: []*endpoint.Endpoint{suite.fooA5},
   246  				},
   247  			},
   248  		},
   249  		{
   250  			name: "conflict: cname, a, and aaaa records",
   251  			args: args{
   252  				key: planKey{dnsName: "foo"},
   253  				row: &planTableRow{
   254  					current:    []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA5},
   255  					candidates: []*endpoint.Endpoint{suite.fooV1Cname, suite.fooA5, suite.fooAAAA5},
   256  					records: map[string]*domainEndpoints{
   257  						endpoint.RecordTypeCNAME: {
   258  							candidates: []*endpoint.Endpoint{suite.fooV1Cname},
   259  						},
   260  						endpoint.RecordTypeA: {
   261  							current:    suite.fooA5,
   262  							candidates: []*endpoint.Endpoint{suite.fooA5},
   263  						},
   264  						endpoint.RecordTypeAAAA: {
   265  							current:    suite.fooAAAA5,
   266  							candidates: []*endpoint.Endpoint{suite.fooAAAA5},
   267  						},
   268  					},
   269  				},
   270  			},
   271  			want: map[string]*domainEndpoints{
   272  				endpoint.RecordTypeCNAME: {
   273  					candidates: []*endpoint.Endpoint{},
   274  				},
   275  				endpoint.RecordTypeA: {
   276  					current:    suite.fooA5,
   277  					candidates: []*endpoint.Endpoint{suite.fooA5},
   278  				},
   279  				endpoint.RecordTypeAAAA: {
   280  					current:    suite.fooAAAA5,
   281  					candidates: []*endpoint.Endpoint{suite.fooAAAA5},
   282  				},
   283  			},
   284  		},
   285  	}
   286  	for _, tt := range tests {
   287  		suite.Run(tt.name, func() {
   288  			if got := suite.perResource.ResolveRecordTypes(tt.args.key, tt.args.row); !reflect.DeepEqual(got, tt.want) {
   289  				suite.T().Errorf("PerResource.ResolveRecordTypes() = %v, want %v", got, tt.want)
   290  			}
   291  		})
   292  	}
   293  }
   294  
   295  func TestConflictResolver(t *testing.T) {
   296  	suite.Run(t, new(ResolverSuite))
   297  }