sigs.k8s.io/external-dns@v0.14.1/provider/designate/designate_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 designate 18 19 import ( 20 "context" 21 "encoding/pem" 22 "fmt" 23 "net/http" 24 "net/http/httptest" 25 "os" 26 "reflect" 27 "sort" 28 "sync/atomic" 29 "testing" 30 31 "github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets" 32 "github.com/gophercloud/gophercloud/openstack/dns/v2/zones" 33 34 "sigs.k8s.io/external-dns/endpoint" 35 "sigs.k8s.io/external-dns/plan" 36 "sigs.k8s.io/external-dns/provider" 37 ) 38 39 var lastGeneratedDesignateID int32 40 41 func generateDesignateID() string { 42 return fmt.Sprintf("id-%d", atomic.AddInt32(&lastGeneratedDesignateID, 1)) 43 } 44 45 type fakeDesignateClient struct { 46 managedZones map[string]*struct { 47 zone *zones.Zone 48 recordSets map[string]*recordsets.RecordSet 49 } 50 } 51 52 func (c fakeDesignateClient) AddZone(zone zones.Zone) string { 53 if zone.ID == "" { 54 zone.ID = zone.Name 55 } 56 c.managedZones[zone.ID] = &struct { 57 zone *zones.Zone 58 recordSets map[string]*recordsets.RecordSet 59 }{ 60 zone: &zone, 61 recordSets: make(map[string]*recordsets.RecordSet), 62 } 63 return zone.ID 64 } 65 66 func (c fakeDesignateClient) ForEachZone(handler func(zone *zones.Zone) error) error { 67 for _, zone := range c.managedZones { 68 if err := handler(zone.zone); err != nil { 69 return err 70 } 71 } 72 return nil 73 } 74 75 func (c fakeDesignateClient) ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error { 76 zone := c.managedZones[zoneID] 77 if zone == nil { 78 return fmt.Errorf("unknown zone %s", zoneID) 79 } 80 for _, recordSet := range zone.recordSets { 81 if err := handler(recordSet); err != nil { 82 return err 83 } 84 } 85 return nil 86 } 87 88 func (c fakeDesignateClient) CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error) { 89 zone := c.managedZones[zoneID] 90 if zone == nil { 91 return "", fmt.Errorf("unknown zone %s", zoneID) 92 } 93 rs := &recordsets.RecordSet{ 94 ID: generateDesignateID(), 95 ZoneID: zoneID, 96 Name: opts.Name, 97 Description: opts.Description, 98 Records: opts.Records, 99 TTL: opts.TTL, 100 Type: opts.Type, 101 } 102 zone.recordSets[rs.ID] = rs 103 return rs.ID, nil 104 } 105 106 func (c fakeDesignateClient) UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error { 107 zone := c.managedZones[zoneID] 108 if zone == nil { 109 return fmt.Errorf("unknown zone %s", zoneID) 110 } 111 rs := zone.recordSets[recordSetID] 112 if rs == nil { 113 return fmt.Errorf("unknown record-set %s", recordSetID) 114 } 115 if opts.Description != nil { 116 rs.Description = *opts.Description 117 } 118 rs.TTL = *opts.TTL 119 120 rs.Records = opts.Records 121 return nil 122 } 123 124 func (c fakeDesignateClient) DeleteRecordSet(zoneID, recordSetID string) error { 125 zone := c.managedZones[zoneID] 126 if zone == nil { 127 return fmt.Errorf("unknown zone %s", zoneID) 128 } 129 delete(zone.recordSets, recordSetID) 130 return nil 131 } 132 133 func (c fakeDesignateClient) ToProvider() provider.Provider { 134 return &designateProvider{client: c} 135 } 136 137 func newFakeDesignateClient() *fakeDesignateClient { 138 return &fakeDesignateClient{ 139 make(map[string]*struct { 140 zone *zones.Zone 141 recordSets map[string]*recordsets.RecordSet 142 }), 143 } 144 } 145 146 func TestNewDesignateProvider(t *testing.T) { 147 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 148 w.WriteHeader(http.StatusAccepted) 149 w.Write([]byte(`{ 150 "token": { 151 "catalog": [ 152 { 153 "id": "9615c2dfac3b4b19935226d4c9d4afce", 154 "name": "designate", 155 "type": "dns", 156 "endpoints": [ 157 { 158 "id": "3d3cc3a273b54d0490ac43d6572e4c48", 159 "region": "RegionOne", 160 "region_id": "RegionOne", 161 "interface": "public", 162 "url": "https://example.com:9001" 163 } 164 ] 165 } 166 ] 167 } 168 }`)) 169 })) 170 defer ts.Close() 171 172 block := &pem.Block{ 173 Type: "CERTIFICATE", 174 Bytes: ts.Certificate().Raw, 175 } 176 tmpfile, err := os.CreateTemp("", "os-test.crt") 177 if err != nil { 178 t.Fatal(err) 179 } 180 defer os.Remove(tmpfile.Name()) 181 if err := pem.Encode(tmpfile, block); err != nil { 182 t.Fatal(err) 183 } 184 if err := tmpfile.Close(); err != nil { 185 t.Fatal(err) 186 } 187 188 os.Setenv("OS_AUTH_URL", ts.URL+"/v3") 189 os.Setenv("OS_USERNAME", "username") 190 os.Setenv("OS_PASSWORD", "password") 191 os.Setenv("OS_USER_DOMAIN_NAME", "Default") 192 os.Setenv("OPENSTACK_CA_FILE", tmpfile.Name()) 193 194 if _, err := NewDesignateProvider(endpoint.DomainFilter{}, true); err != nil { 195 t.Fatalf("Failed to initialize Designate provider: %s", err) 196 } 197 } 198 199 func TestDesignateRecords(t *testing.T) { 200 client := newFakeDesignateClient() 201 202 zone1ID := client.AddZone(zones.Zone{ 203 Name: "example.com.", 204 Type: "PRIMARY", 205 Status: "ACTIVE", 206 }) 207 rs11ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{ 208 Name: "www.example.com.", 209 Type: endpoint.RecordTypeA, 210 Records: []string{"10.1.1.1"}, 211 }) 212 rs12ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{ 213 Name: "www.example.com.", 214 Type: endpoint.RecordTypeTXT, 215 Records: []string{"text1"}, 216 }) 217 client.CreateRecordSet(zone1ID, recordsets.CreateOpts{ 218 Name: "xxx.example.com.", 219 Type: "SRV", 220 Records: []string{"http://test.com:1234"}, 221 }) 222 rs14ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{ 223 Name: "ftp.example.com.", 224 Type: endpoint.RecordTypeA, 225 Records: []string{"10.1.1.2"}, 226 }) 227 228 zone2ID := client.AddZone(zones.Zone{ 229 Name: "test.net.", 230 Type: "PRIMARY", 231 Status: "ACTIVE", 232 }) 233 rs21ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{ 234 Name: "srv.test.net.", 235 Type: endpoint.RecordTypeA, 236 Records: []string{"10.2.1.1", "10.2.1.2"}, 237 }) 238 rs22ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{ 239 Name: "db.test.net.", 240 Type: endpoint.RecordTypeCNAME, 241 Records: []string{"sql.test.net."}, 242 }) 243 expected := []*endpoint.Endpoint{ 244 { 245 DNSName: "www.example.com", 246 RecordType: endpoint.RecordTypeA, 247 Targets: endpoint.Targets{"10.1.1.1"}, 248 Labels: map[string]string{ 249 designateRecordSetID: rs11ID, 250 designateZoneID: zone1ID, 251 designateOriginalRecords: "10.1.1.1", 252 }, 253 }, 254 { 255 DNSName: "www.example.com", 256 RecordType: endpoint.RecordTypeTXT, 257 Targets: endpoint.Targets{"text1"}, 258 Labels: map[string]string{ 259 designateRecordSetID: rs12ID, 260 designateZoneID: zone1ID, 261 designateOriginalRecords: "text1", 262 }, 263 }, 264 { 265 DNSName: "ftp.example.com", 266 RecordType: endpoint.RecordTypeA, 267 Targets: endpoint.Targets{"10.1.1.2"}, 268 Labels: map[string]string{ 269 designateRecordSetID: rs14ID, 270 designateZoneID: zone1ID, 271 designateOriginalRecords: "10.1.1.2", 272 }, 273 }, 274 { 275 DNSName: "srv.test.net", 276 RecordType: endpoint.RecordTypeA, 277 Targets: endpoint.Targets{"10.2.1.1", "10.2.1.2"}, 278 Labels: map[string]string{ 279 designateRecordSetID: rs21ID, 280 designateZoneID: zone2ID, 281 designateOriginalRecords: "10.2.1.1\00010.2.1.2", 282 }, 283 }, 284 { 285 DNSName: "db.test.net", 286 RecordType: endpoint.RecordTypeCNAME, 287 Targets: endpoint.Targets{"sql.test.net"}, 288 Labels: map[string]string{ 289 designateRecordSetID: rs22ID, 290 designateZoneID: zone2ID, 291 designateOriginalRecords: "sql.test.net.", 292 }, 293 }, 294 } 295 296 endpoints, err := client.ToProvider().Records(context.Background()) 297 if err != nil { 298 t.Fatal(err) 299 } 300 out: 301 for _, ep := range endpoints { 302 for i, ex := range expected { 303 if reflect.DeepEqual(ep, ex) { 304 expected = append(expected[:i], expected[i+1:]...) 305 continue out 306 } 307 } 308 t.Errorf("unexpected endpoint %s/%s -> %s", ep.DNSName, ep.RecordType, ep.Targets) 309 } 310 if len(expected) != 0 { 311 t.Errorf("not all expected endpoints were returned. Remained: %v", expected) 312 } 313 } 314 315 func TestDesignateCreateRecords(t *testing.T) { 316 client := newFakeDesignateClient() 317 testDesignateCreateRecords(t, client) 318 } 319 320 func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet { 321 for i, zoneName := range []string{"example.com.", "test.net."} { 322 client.AddZone(zones.Zone{ 323 ID: fmt.Sprintf("zone-%d", i+1), 324 Name: zoneName, 325 Type: "PRIMARY", 326 Status: "ACTIVE", 327 }) 328 } 329 330 _, err := client.CreateRecordSet("zone-1", recordsets.CreateOpts{ 331 Name: "www.example.com.", 332 Description: "", 333 Records: []string{"foo"}, 334 TTL: 60, 335 Type: endpoint.RecordTypeTXT, 336 }) 337 338 if err != nil { 339 t.Fatal("failed to prefil records") 340 } 341 342 endpoints := []*endpoint.Endpoint{ 343 { 344 DNSName: "www.example.com", 345 RecordType: endpoint.RecordTypeA, 346 Targets: endpoint.Targets{"10.1.1.1"}, 347 Labels: map[string]string{}, 348 }, 349 { 350 DNSName: "www.example.com", 351 RecordType: endpoint.RecordTypeTXT, 352 Targets: endpoint.Targets{"text1"}, 353 Labels: map[string]string{}, 354 }, 355 { 356 DNSName: "ftp.example.com", 357 RecordType: endpoint.RecordTypeA, 358 Targets: endpoint.Targets{"10.1.1.2"}, 359 Labels: map[string]string{}, 360 }, 361 { 362 DNSName: "srv.test.net", 363 RecordType: endpoint.RecordTypeA, 364 Targets: endpoint.Targets{"10.2.1.1"}, 365 Labels: map[string]string{}, 366 }, 367 { 368 DNSName: "srv.test.net", 369 RecordType: endpoint.RecordTypeA, 370 Targets: endpoint.Targets{"10.2.1.2"}, 371 Labels: map[string]string{}, 372 }, 373 { 374 DNSName: "db.test.net", 375 RecordType: endpoint.RecordTypeCNAME, 376 Targets: endpoint.Targets{"sql.test.net"}, 377 Labels: map[string]string{}, 378 }, 379 } 380 expected := []*recordsets.RecordSet{ 381 { 382 Name: "www.example.com.", 383 Type: endpoint.RecordTypeA, 384 Records: []string{"10.1.1.1"}, 385 ZoneID: "zone-1", 386 }, 387 { 388 Name: "www.example.com.", 389 Type: endpoint.RecordTypeTXT, 390 Records: []string{"text1"}, 391 ZoneID: "zone-1", 392 }, 393 { 394 Name: "ftp.example.com.", 395 Type: endpoint.RecordTypeA, 396 Records: []string{"10.1.1.2"}, 397 ZoneID: "zone-1", 398 }, 399 { 400 Name: "srv.test.net.", 401 Type: endpoint.RecordTypeA, 402 Records: []string{"10.2.1.1", "10.2.1.2"}, 403 ZoneID: "zone-2", 404 }, 405 { 406 Name: "db.test.net.", 407 Type: endpoint.RecordTypeCNAME, 408 Records: []string{"sql.test.net."}, 409 ZoneID: "zone-2", 410 }, 411 } 412 expectedCopy := make([]*recordsets.RecordSet, len(expected)) 413 copy(expectedCopy, expected) 414 415 err = client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{Create: endpoints}) 416 if err != nil { 417 t.Fatal(err) 418 } 419 420 client.ForEachZone(func(zone *zones.Zone) error { 421 client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error { 422 id := recordSet.ID 423 recordSet.ID = "" 424 for i, ex := range expected { 425 sort.Strings(recordSet.Records) 426 if reflect.DeepEqual(ex, recordSet) { 427 ex.ID = id 428 recordSet.ID = id 429 expected = append(expected[:i], expected[i+1:]...) 430 return nil 431 } 432 } 433 t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records) 434 return nil 435 }) 436 return nil 437 }) 438 439 if len(expected) != 0 { 440 t.Errorf("not all expected record-sets were created. Remained: %v", expected) 441 } 442 return expectedCopy 443 } 444 445 func TestDesignateUpdateRecords(t *testing.T) { 446 client := newFakeDesignateClient() 447 testDesignateUpdateRecords(t, client) 448 } 449 450 func testDesignateUpdateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet { 451 expected := testDesignateCreateRecords(t, client) 452 453 updatesOld := []*endpoint.Endpoint{ 454 { 455 DNSName: "ftp.example.com", 456 RecordType: endpoint.RecordTypeA, 457 Targets: endpoint.Targets{"10.1.1.2"}, 458 Labels: map[string]string{ 459 designateZoneID: "zone-1", 460 designateRecordSetID: expected[2].ID, 461 designateOriginalRecords: "10.1.1.2", 462 }, 463 }, 464 { 465 DNSName: "srv.test.net.", 466 RecordType: endpoint.RecordTypeA, 467 Targets: endpoint.Targets{"10.2.1.2"}, 468 Labels: map[string]string{ 469 designateZoneID: "zone-2", 470 designateRecordSetID: expected[3].ID, 471 designateOriginalRecords: "10.2.1.1\00010.2.1.2", 472 }, 473 }, 474 } 475 updatesNew := []*endpoint.Endpoint{ 476 { 477 DNSName: "ftp.example.com", 478 RecordType: endpoint.RecordTypeA, 479 Targets: endpoint.Targets{"10.3.3.1"}, 480 Labels: map[string]string{ 481 designateZoneID: "zone-1", 482 designateRecordSetID: expected[2].ID, 483 designateOriginalRecords: "10.1.1.2", 484 }, 485 }, 486 { 487 DNSName: "srv.test.net.", 488 RecordType: endpoint.RecordTypeA, 489 Targets: endpoint.Targets{"10.3.3.2"}, 490 Labels: map[string]string{ 491 designateZoneID: "zone-2", 492 designateRecordSetID: expected[3].ID, 493 designateOriginalRecords: "10.2.1.1\00010.2.1.2", 494 }, 495 }, 496 } 497 expectedCopy := make([]*recordsets.RecordSet, len(expected)) 498 copy(expectedCopy, expected) 499 500 expected[2].Records = []string{"10.3.3.1"} 501 expected[3].Records = []string{"10.2.1.1", "10.3.3.2"} 502 503 err := client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{UpdateOld: updatesOld, UpdateNew: updatesNew}) 504 if err != nil { 505 t.Fatal(err) 506 } 507 508 client.ForEachZone(func(zone *zones.Zone) error { 509 client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error { 510 for i, ex := range expected { 511 sort.Strings(recordSet.Records) 512 if reflect.DeepEqual(ex, recordSet) { 513 expected = append(expected[:i], expected[i+1:]...) 514 return nil 515 } 516 } 517 t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records) 518 return nil 519 }) 520 return nil 521 }) 522 523 if len(expected) != 0 { 524 t.Errorf("not all expected record-sets were updated. Remained: %v", expected) 525 } 526 return expectedCopy 527 } 528 529 func TestDesignateDeleteRecords(t *testing.T) { 530 client := newFakeDesignateClient() 531 testDesignateDeleteRecords(t, client) 532 } 533 534 func testDesignateDeleteRecords(t *testing.T, client *fakeDesignateClient) { 535 expected := testDesignateUpdateRecords(t, client) 536 deletes := []*endpoint.Endpoint{ 537 { 538 DNSName: "www.example.com.", 539 RecordType: endpoint.RecordTypeA, 540 Targets: endpoint.Targets{"10.1.1.1"}, 541 Labels: map[string]string{ 542 designateZoneID: "zone-1", 543 designateRecordSetID: expected[0].ID, 544 designateOriginalRecords: "10.1.1.1", 545 }, 546 }, 547 { 548 DNSName: "srv.test.net.", 549 RecordType: endpoint.RecordTypeA, 550 Targets: endpoint.Targets{"10.2.1.1"}, 551 Labels: map[string]string{ 552 designateZoneID: "zone-2", 553 designateRecordSetID: expected[3].ID, 554 designateOriginalRecords: "10.2.1.1\00010.3.3.2", 555 }, 556 }, 557 } 558 expected[3].Records = []string{"10.3.3.2"} 559 expected = expected[1:] 560 561 err := client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{Delete: deletes}) 562 if err != nil { 563 t.Fatal(err) 564 } 565 566 client.ForEachZone(func(zone *zones.Zone) error { 567 client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error { 568 for i, ex := range expected { 569 sort.Strings(recordSet.Records) 570 if reflect.DeepEqual(ex, recordSet) { 571 expected = append(expected[:i], expected[i+1:]...) 572 return nil 573 } 574 } 575 t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records) 576 return nil 577 }) 578 return nil 579 }) 580 581 if len(expected) != 0 { 582 t.Errorf("not all expected record-sets were deleted. Remained: %v", expected) 583 } 584 }