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 }