sigs.k8s.io/external-dns@v0.14.1/provider/inmemory/inmemory_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 inmemory 18 19 import ( 20 "context" 21 "testing" 22 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 "sigs.k8s.io/external-dns/endpoint" 26 "sigs.k8s.io/external-dns/internal/testutils" 27 "sigs.k8s.io/external-dns/plan" 28 "sigs.k8s.io/external-dns/provider" 29 ) 30 31 var _ provider.Provider = &InMemoryProvider{} 32 33 func TestInMemoryProvider(t *testing.T) { 34 t.Run("Records", testInMemoryRecords) 35 t.Run("validateChangeBatch", testInMemoryValidateChangeBatch) 36 t.Run("ApplyChanges", testInMemoryApplyChanges) 37 t.Run("NewInMemoryProvider", testNewInMemoryProvider) 38 t.Run("CreateZone", testInMemoryCreateZone) 39 } 40 41 func testInMemoryRecords(t *testing.T) { 42 for _, ti := range []struct { 43 title string 44 zone string 45 expectError bool 46 init map[string]zone 47 expected []*endpoint.Endpoint 48 }{ 49 { 50 title: "no records, no zone", 51 zone: "", 52 init: map[string]zone{}, 53 expectError: false, 54 }, 55 { 56 title: "records, wrong zone", 57 zone: "net", 58 init: map[string]zone{ 59 "org": {}, 60 "com": {}, 61 }, 62 expectError: false, 63 }, 64 { 65 title: "records, zone with records", 66 zone: "org", 67 init: map[string]zone{ 68 "org": makeZone( 69 "example.org", "8.8.8.8", endpoint.RecordTypeA, 70 "example.org", "", endpoint.RecordTypeTXT, 71 "foo.org", "4.4.4.4", endpoint.RecordTypeCNAME, 72 ), 73 "com": makeZone("example.com", "4.4.4.4", endpoint.RecordTypeCNAME), 74 }, 75 expectError: false, 76 expected: []*endpoint.Endpoint{ 77 { 78 DNSName: "example.org", 79 Targets: endpoint.Targets{"8.8.8.8"}, 80 RecordType: endpoint.RecordTypeA, 81 }, 82 { 83 DNSName: "example.org", 84 RecordType: endpoint.RecordTypeTXT, 85 Targets: endpoint.Targets{""}, 86 }, 87 { 88 DNSName: "foo.org", 89 Targets: endpoint.Targets{"4.4.4.4"}, 90 RecordType: endpoint.RecordTypeCNAME, 91 }, 92 }, 93 }, 94 } { 95 t.Run(ti.title, func(t *testing.T) { 96 c := newInMemoryClient() 97 c.zones = ti.init 98 im := NewInMemoryProvider() 99 im.client = c 100 f := filter{domain: ti.zone} 101 im.filter = &f 102 records, err := im.Records(context.Background()) 103 if ti.expectError { 104 assert.Nil(t, records) 105 assert.EqualError(t, err, ErrZoneNotFound.Error()) 106 } else { 107 require.NoError(t, err) 108 assert.True(t, testutils.SameEndpoints(ti.expected, records), "Endpoints not the same: Expected: %+v Records: %+v", ti.expected, records) 109 } 110 }) 111 } 112 } 113 114 func testInMemoryValidateChangeBatch(t *testing.T) { 115 init := map[string]zone{ 116 "org": makeZone( 117 "example.org", "8.8.8.8", endpoint.RecordTypeA, 118 "example.org", "", endpoint.RecordTypeTXT, 119 "foo.org", "bar.org", endpoint.RecordTypeCNAME, 120 "foo.bar.org", "5.5.5.5", endpoint.RecordTypeA, 121 ), 122 "com": makeZone("example.com", "another-example.com", endpoint.RecordTypeCNAME), 123 } 124 for _, ti := range []struct { 125 title string 126 expectError bool 127 errorType error 128 init map[string]zone 129 changes *plan.Changes 130 zone string 131 }{ 132 { 133 title: "no zones, no update", 134 expectError: true, 135 zone: "", 136 init: map[string]zone{}, 137 changes: &plan.Changes{ 138 Create: []*endpoint.Endpoint{}, 139 UpdateNew: []*endpoint.Endpoint{}, 140 UpdateOld: []*endpoint.Endpoint{}, 141 Delete: []*endpoint.Endpoint{}, 142 }, 143 errorType: ErrZoneNotFound, 144 }, 145 { 146 title: "zones, no update", 147 expectError: true, 148 zone: "", 149 init: init, 150 changes: &plan.Changes{ 151 Create: []*endpoint.Endpoint{}, 152 UpdateNew: []*endpoint.Endpoint{}, 153 UpdateOld: []*endpoint.Endpoint{}, 154 Delete: []*endpoint.Endpoint{}, 155 }, 156 errorType: ErrZoneNotFound, 157 }, 158 { 159 title: "zones, update, wrong zone", 160 expectError: true, 161 zone: "test", 162 init: init, 163 changes: &plan.Changes{ 164 Create: []*endpoint.Endpoint{}, 165 UpdateNew: []*endpoint.Endpoint{}, 166 UpdateOld: []*endpoint.Endpoint{}, 167 Delete: []*endpoint.Endpoint{}, 168 }, 169 errorType: ErrZoneNotFound, 170 }, 171 { 172 title: "zones, update, right zone, invalid batch - already exists", 173 expectError: true, 174 zone: "org", 175 init: init, 176 changes: &plan.Changes{ 177 Create: []*endpoint.Endpoint{ 178 { 179 DNSName: "example.org", 180 Targets: endpoint.Targets{"8.8.8.8"}, 181 RecordType: endpoint.RecordTypeA, 182 }, 183 }, 184 UpdateNew: []*endpoint.Endpoint{}, 185 UpdateOld: []*endpoint.Endpoint{}, 186 Delete: []*endpoint.Endpoint{}, 187 }, 188 errorType: ErrRecordAlreadyExists, 189 }, 190 { 191 title: "zones, update, right zone, invalid batch - record not found for update", 192 expectError: true, 193 zone: "org", 194 init: init, 195 changes: &plan.Changes{ 196 Create: []*endpoint.Endpoint{ 197 { 198 DNSName: "foo.org", 199 Targets: endpoint.Targets{"4.4.4.4"}, 200 RecordType: endpoint.RecordTypeA, 201 }, 202 }, 203 UpdateNew: []*endpoint.Endpoint{ 204 { 205 DNSName: "foo.org", 206 Targets: endpoint.Targets{"4.4.4.4"}, 207 RecordType: endpoint.RecordTypeA, 208 }, 209 }, 210 UpdateOld: []*endpoint.Endpoint{}, 211 Delete: []*endpoint.Endpoint{}, 212 }, 213 errorType: ErrRecordNotFound, 214 }, 215 { 216 title: "zones, update, right zone, invalid batch - record not found for update", 217 expectError: true, 218 zone: "org", 219 init: init, 220 changes: &plan.Changes{ 221 Create: []*endpoint.Endpoint{ 222 { 223 DNSName: "foo.org", 224 Targets: endpoint.Targets{"4.4.4.4"}, 225 RecordType: endpoint.RecordTypeA, 226 }, 227 }, 228 UpdateNew: []*endpoint.Endpoint{ 229 { 230 DNSName: "foo.org", 231 Targets: endpoint.Targets{"4.4.4.4"}, 232 RecordType: endpoint.RecordTypeA, 233 }, 234 }, 235 UpdateOld: []*endpoint.Endpoint{}, 236 Delete: []*endpoint.Endpoint{}, 237 }, 238 errorType: ErrRecordNotFound, 239 }, 240 { 241 title: "zones, update, right zone, invalid batch - duplicated create", 242 expectError: true, 243 zone: "org", 244 init: init, 245 changes: &plan.Changes{ 246 Create: []*endpoint.Endpoint{ 247 { 248 DNSName: "foo.org", 249 Targets: endpoint.Targets{"4.4.4.4"}, 250 RecordType: endpoint.RecordTypeA, 251 }, 252 { 253 DNSName: "foo.org", 254 Targets: endpoint.Targets{"4.4.4.4"}, 255 RecordType: endpoint.RecordTypeA, 256 }, 257 }, 258 UpdateNew: []*endpoint.Endpoint{}, 259 UpdateOld: []*endpoint.Endpoint{}, 260 Delete: []*endpoint.Endpoint{}, 261 }, 262 errorType: ErrDuplicateRecordFound, 263 }, 264 { 265 title: "zones, update, right zone, invalid batch - duplicated update/delete", 266 expectError: true, 267 zone: "org", 268 init: init, 269 changes: &plan.Changes{ 270 Create: []*endpoint.Endpoint{}, 271 UpdateNew: []*endpoint.Endpoint{ 272 { 273 DNSName: "example.org", 274 Targets: endpoint.Targets{"8.8.8.8"}, 275 RecordType: endpoint.RecordTypeA, 276 }, 277 }, 278 UpdateOld: []*endpoint.Endpoint{}, 279 Delete: []*endpoint.Endpoint{ 280 { 281 DNSName: "example.org", 282 Targets: endpoint.Targets{"8.8.8.8"}, 283 RecordType: endpoint.RecordTypeA, 284 }, 285 }, 286 }, 287 errorType: ErrDuplicateRecordFound, 288 }, 289 { 290 title: "zones, update, right zone, invalid batch - duplicated update", 291 expectError: true, 292 zone: "org", 293 init: init, 294 changes: &plan.Changes{ 295 Create: []*endpoint.Endpoint{}, 296 UpdateNew: []*endpoint.Endpoint{ 297 { 298 DNSName: "example.org", 299 Targets: endpoint.Targets{"8.8.8.8"}, 300 RecordType: endpoint.RecordTypeA, 301 }, 302 { 303 DNSName: "example.org", 304 Targets: endpoint.Targets{"8.8.8.8"}, 305 RecordType: endpoint.RecordTypeA, 306 }, 307 }, 308 UpdateOld: []*endpoint.Endpoint{}, 309 Delete: []*endpoint.Endpoint{}, 310 }, 311 errorType: ErrDuplicateRecordFound, 312 }, 313 { 314 title: "zones, update, right zone, invalid batch - wrong update old", 315 expectError: true, 316 zone: "org", 317 init: init, 318 changes: &plan.Changes{ 319 Create: []*endpoint.Endpoint{}, 320 UpdateNew: []*endpoint.Endpoint{}, 321 UpdateOld: []*endpoint.Endpoint{ 322 { 323 DNSName: "new.org", 324 Targets: endpoint.Targets{"8.8.8.8"}, 325 RecordType: endpoint.RecordTypeA, 326 }, 327 }, 328 Delete: []*endpoint.Endpoint{}, 329 }, 330 errorType: ErrRecordNotFound, 331 }, 332 { 333 title: "zones, update, right zone, invalid batch - wrong delete", 334 expectError: true, 335 zone: "org", 336 init: init, 337 changes: &plan.Changes{ 338 Create: []*endpoint.Endpoint{}, 339 UpdateNew: []*endpoint.Endpoint{}, 340 UpdateOld: []*endpoint.Endpoint{}, 341 Delete: []*endpoint.Endpoint{ 342 { 343 DNSName: "new.org", 344 Targets: endpoint.Targets{"8.8.8.8"}, 345 RecordType: endpoint.RecordTypeA, 346 }, 347 }, 348 }, 349 errorType: ErrRecordNotFound, 350 }, 351 { 352 title: "zones, update, right zone, valid batch - delete", 353 expectError: false, 354 zone: "org", 355 init: init, 356 changes: &plan.Changes{ 357 Create: []*endpoint.Endpoint{}, 358 UpdateNew: []*endpoint.Endpoint{}, 359 UpdateOld: []*endpoint.Endpoint{}, 360 Delete: []*endpoint.Endpoint{ 361 { 362 DNSName: "foo.bar.org", 363 Targets: endpoint.Targets{"5.5.5.5"}, 364 RecordType: endpoint.RecordTypeA, 365 }, 366 }, 367 }, 368 }, 369 { 370 title: "zones, update, right zone, valid batch - update and create", 371 expectError: false, 372 zone: "org", 373 init: init, 374 changes: &plan.Changes{ 375 Create: []*endpoint.Endpoint{ 376 { 377 DNSName: "foo.bar.new.org", 378 Targets: endpoint.Targets{"4.8.8.9"}, 379 RecordType: endpoint.RecordTypeA, 380 }, 381 }, 382 UpdateNew: []*endpoint.Endpoint{ 383 { 384 DNSName: "foo.bar.org", 385 Targets: endpoint.Targets{"4.8.8.4"}, 386 RecordType: endpoint.RecordTypeA, 387 }, 388 }, 389 UpdateOld: []*endpoint.Endpoint{ 390 { 391 DNSName: "foo.bar.org", 392 Targets: endpoint.Targets{"5.5.5.5"}, 393 RecordType: endpoint.RecordTypeA, 394 }, 395 }, 396 Delete: []*endpoint.Endpoint{}, 397 }, 398 }, 399 } { 400 t.Run(ti.title, func(t *testing.T) { 401 c := &inMemoryClient{} 402 c.zones = ti.init 403 ichanges := &plan.Changes{ 404 Create: ti.changes.Create, 405 UpdateNew: ti.changes.UpdateNew, 406 UpdateOld: ti.changes.UpdateOld, 407 Delete: ti.changes.Delete, 408 } 409 err := c.validateChangeBatch(ti.zone, ichanges) 410 if ti.expectError { 411 assert.EqualError(t, err, ti.errorType.Error()) 412 } else { 413 assert.NoError(t, err) 414 } 415 }) 416 } 417 } 418 419 func getInitData() map[string]zone { 420 return map[string]zone{ 421 "org": makeZone("example.org", "8.8.8.8", endpoint.RecordTypeA, 422 "example.org", "", endpoint.RecordTypeTXT, 423 "foo.org", "4.4.4.4", endpoint.RecordTypeCNAME, 424 "foo.bar.org", "5.5.5.5", endpoint.RecordTypeA, 425 ), 426 "com": makeZone("example.com", "4.4.4.4", endpoint.RecordTypeCNAME), 427 } 428 } 429 430 func testInMemoryApplyChanges(t *testing.T) { 431 for _, ti := range []struct { 432 title string 433 expectError bool 434 init map[string]zone 435 changes *plan.Changes 436 expectedZonesState map[string]zone 437 }{ 438 { 439 title: "unmatched zone, should be ignored in the apply step", 440 expectError: false, 441 changes: &plan.Changes{ 442 Create: []*endpoint.Endpoint{{ 443 DNSName: "example.de", 444 Targets: endpoint.Targets{"8.8.8.8"}, 445 RecordType: endpoint.RecordTypeA, 446 }}, 447 UpdateNew: []*endpoint.Endpoint{}, 448 UpdateOld: []*endpoint.Endpoint{}, 449 Delete: []*endpoint.Endpoint{}, 450 }, 451 expectedZonesState: getInitData(), 452 }, 453 { 454 title: "expect error", 455 expectError: true, 456 changes: &plan.Changes{ 457 Create: []*endpoint.Endpoint{}, 458 UpdateNew: []*endpoint.Endpoint{ 459 { 460 DNSName: "example.org", 461 Targets: endpoint.Targets{"8.8.8.8"}, 462 RecordType: endpoint.RecordTypeA, 463 }, 464 }, 465 UpdateOld: []*endpoint.Endpoint{}, 466 Delete: []*endpoint.Endpoint{ 467 { 468 DNSName: "example.org", 469 Targets: endpoint.Targets{"8.8.8.8"}, 470 RecordType: endpoint.RecordTypeA, 471 }, 472 }, 473 }, 474 }, 475 { 476 title: "zones, update, right zone, valid batch - delete", 477 expectError: false, 478 changes: &plan.Changes{ 479 Create: []*endpoint.Endpoint{}, 480 UpdateNew: []*endpoint.Endpoint{}, 481 UpdateOld: []*endpoint.Endpoint{}, 482 Delete: []*endpoint.Endpoint{ 483 { 484 DNSName: "foo.bar.org", 485 Targets: endpoint.Targets{"5.5.5.5"}, 486 RecordType: endpoint.RecordTypeA, 487 }, 488 }, 489 }, 490 expectedZonesState: map[string]zone{ 491 "org": makeZone("example.org", "8.8.8.8", endpoint.RecordTypeA, 492 "example.org", "", endpoint.RecordTypeTXT, 493 "foo.org", "4.4.4.4", endpoint.RecordTypeCNAME, 494 ), 495 "com": makeZone("example.com", "4.4.4.4", endpoint.RecordTypeCNAME), 496 }, 497 }, 498 { 499 title: "zones, update, right zone, valid batch - update, create, delete", 500 expectError: false, 501 changes: &plan.Changes{ 502 Create: []*endpoint.Endpoint{ 503 { 504 DNSName: "foo.bar.new.org", 505 Targets: endpoint.Targets{"4.8.8.9"}, 506 RecordType: endpoint.RecordTypeA, 507 Labels: endpoint.NewLabels(), 508 }, 509 }, 510 UpdateNew: []*endpoint.Endpoint{ 511 { 512 DNSName: "foo.bar.org", 513 Targets: endpoint.Targets{"4.8.8.4"}, 514 RecordType: endpoint.RecordTypeA, 515 Labels: endpoint.NewLabels(), 516 }, 517 }, 518 UpdateOld: []*endpoint.Endpoint{ 519 { 520 DNSName: "foo.bar.org", 521 Targets: endpoint.Targets{"5.5.5.5"}, 522 RecordType: endpoint.RecordTypeA, 523 Labels: endpoint.NewLabels(), 524 }, 525 }, 526 Delete: []*endpoint.Endpoint{ 527 { 528 DNSName: "example.org", 529 Targets: endpoint.Targets{"8.8.8.8"}, 530 RecordType: endpoint.RecordTypeA, 531 Labels: endpoint.NewLabels(), 532 }, 533 }, 534 }, 535 expectedZonesState: map[string]zone{ 536 "org": makeZone( 537 "example.org", "", endpoint.RecordTypeTXT, 538 "foo.org", "4.4.4.4", endpoint.RecordTypeCNAME, 539 "foo.bar.org", "4.8.8.4", endpoint.RecordTypeA, 540 "foo.bar.new.org", "4.8.8.9", endpoint.RecordTypeA, 541 ), 542 "com": makeZone("example.com", "4.4.4.4", endpoint.RecordTypeCNAME), 543 }, 544 }, 545 } { 546 t.Run(ti.title, func(t *testing.T) { 547 im := NewInMemoryProvider() 548 c := &inMemoryClient{} 549 c.zones = getInitData() 550 im.client = c 551 552 err := im.ApplyChanges(context.Background(), ti.changes) 553 if ti.expectError { 554 assert.Error(t, err) 555 } else { 556 require.NoError(t, err) 557 assert.Equal(t, ti.expectedZonesState, c.zones) 558 } 559 }) 560 } 561 } 562 563 func testNewInMemoryProvider(t *testing.T) { 564 cfg := NewInMemoryProvider() 565 assert.NotNil(t, cfg.client) 566 } 567 568 func testInMemoryCreateZone(t *testing.T) { 569 im := NewInMemoryProvider() 570 err := im.CreateZone("zone") 571 assert.NoError(t, err) 572 err = im.CreateZone("zone") 573 assert.EqualError(t, err, ErrZoneAlreadyExists.Error()) 574 } 575 576 func makeZone(s ...string) map[endpoint.EndpointKey]*endpoint.Endpoint { 577 if len(s)%3 != 0 { 578 panic("makeZone arguments must be multiple of 3") 579 } 580 581 output := map[endpoint.EndpointKey]*endpoint.Endpoint{} 582 for i := 0; i < len(s); i += 3 { 583 ep := endpoint.NewEndpoint(s[i], s[i+2], s[i+1]) 584 output[ep.Key()] = ep 585 } 586 587 return output 588 }