sigs.k8s.io/external-dns@v0.14.1/provider/linode/linode_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 linode 18 19 import ( 20 "context" 21 "os" 22 "testing" 23 24 "github.com/linode/linodego" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/mock" 27 "github.com/stretchr/testify/require" 28 29 "sigs.k8s.io/external-dns/endpoint" 30 "sigs.k8s.io/external-dns/plan" 31 ) 32 33 type MockDomainClient struct { 34 mock.Mock 35 } 36 37 func (m *MockDomainClient) ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]linodego.DomainRecord, error) { 38 args := m.Called(ctx, domainID, opts) 39 return args.Get(0).([]linodego.DomainRecord), args.Error(1) 40 } 41 42 func (m *MockDomainClient) ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]linodego.Domain, error) { 43 args := m.Called(ctx, opts) 44 return args.Get(0).([]linodego.Domain), args.Error(1) 45 } 46 47 func (m *MockDomainClient) CreateDomainRecord(ctx context.Context, domainID int, opts linodego.DomainRecordCreateOptions) (*linodego.DomainRecord, error) { 48 args := m.Called(ctx, domainID, opts) 49 return args.Get(0).(*linodego.DomainRecord), args.Error(1) 50 } 51 52 func (m *MockDomainClient) DeleteDomainRecord(ctx context.Context, domainID int, recordID int) error { 53 args := m.Called(ctx, domainID, recordID) 54 return args.Error(0) 55 } 56 57 func (m *MockDomainClient) UpdateDomainRecord(ctx context.Context, domainID int, recordID int, opts linodego.DomainRecordUpdateOptions) (*linodego.DomainRecord, error) { 58 args := m.Called(ctx, domainID, recordID, opts) 59 return args.Get(0).(*linodego.DomainRecord), args.Error(1) 60 } 61 62 func createZones() []linodego.Domain { 63 return []linodego.Domain{ 64 {ID: 1, Domain: "foo.com"}, 65 {ID: 2, Domain: "bar.io"}, 66 {ID: 3, Domain: "baz.com"}, 67 } 68 } 69 70 func createFooRecords() []linodego.DomainRecord { 71 return []linodego.DomainRecord{{ 72 ID: 11, 73 Type: linodego.RecordTypeA, 74 Name: "", 75 Target: "targetFoo", 76 }, { 77 ID: 12, 78 Type: linodego.RecordTypeTXT, 79 Name: "", 80 Target: "txt", 81 }, { 82 ID: 13, 83 Type: linodego.RecordTypeCAA, 84 Name: "foo.com", 85 Target: "", 86 }} 87 } 88 89 func createBarRecords() []linodego.DomainRecord { 90 return []linodego.DomainRecord{} 91 } 92 93 func createBazRecords() []linodego.DomainRecord { 94 return []linodego.DomainRecord{{ 95 ID: 31, 96 Type: linodego.RecordTypeA, 97 Name: "", 98 Target: "targetBaz", 99 }, { 100 ID: 32, 101 Type: linodego.RecordTypeTXT, 102 Name: "", 103 Target: "txt", 104 }, { 105 ID: 33, 106 Type: linodego.RecordTypeA, 107 Name: "api", 108 Target: "targetBaz", 109 }, { 110 ID: 34, 111 Type: linodego.RecordTypeTXT, 112 Name: "api", 113 Target: "txt", 114 }} 115 } 116 117 func TestLinodeConvertRecordType(t *testing.T) { 118 record, err := convertRecordType("A") 119 require.NoError(t, err) 120 assert.Equal(t, linodego.RecordTypeA, record) 121 122 record, err = convertRecordType("AAAA") 123 require.NoError(t, err) 124 assert.Equal(t, linodego.RecordTypeAAAA, record) 125 126 record, err = convertRecordType("CNAME") 127 require.NoError(t, err) 128 assert.Equal(t, linodego.RecordTypeCNAME, record) 129 130 record, err = convertRecordType("TXT") 131 require.NoError(t, err) 132 assert.Equal(t, linodego.RecordTypeTXT, record) 133 134 record, err = convertRecordType("SRV") 135 require.NoError(t, err) 136 assert.Equal(t, linodego.RecordTypeSRV, record) 137 138 record, err = convertRecordType("NS") 139 require.NoError(t, err) 140 assert.Equal(t, linodego.RecordTypeNS, record) 141 142 _, err = convertRecordType("INVALID") 143 require.Error(t, err) 144 } 145 146 func TestNewLinodeProvider(t *testing.T) { 147 _ = os.Setenv("LINODE_TOKEN", "xxxxxxxxxxxxxxxxx") 148 _, err := NewLinodeProvider(endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true, "1.0") 149 require.NoError(t, err) 150 151 _ = os.Unsetenv("LINODE_TOKEN") 152 _, err = NewLinodeProvider(endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true, "1.0") 153 require.Error(t, err) 154 } 155 156 func TestLinodeStripRecordName(t *testing.T) { 157 assert.Equal(t, "api", getStrippedRecordName(linodego.Domain{ 158 Domain: "example.com", 159 }, endpoint.Endpoint{ 160 DNSName: "api.example.com", 161 })) 162 163 assert.Equal(t, "", getStrippedRecordName(linodego.Domain{ 164 Domain: "example.com", 165 }, endpoint.Endpoint{ 166 DNSName: "example.com", 167 })) 168 } 169 170 func TestLinodeFetchZonesNoFilters(t *testing.T) { 171 mockDomainClient := MockDomainClient{} 172 173 provider := &LinodeProvider{ 174 Client: &mockDomainClient, 175 domainFilter: endpoint.NewDomainFilter([]string{}), 176 DryRun: false, 177 } 178 179 mockDomainClient.On( 180 "ListDomains", 181 mock.Anything, 182 mock.Anything, 183 ).Return(createZones(), nil).Once() 184 185 expected := createZones() 186 actual, err := provider.fetchZones(context.Background()) 187 require.NoError(t, err) 188 189 mockDomainClient.AssertExpectations(t) 190 assert.Equal(t, expected, actual) 191 } 192 193 func TestLinodeFetchZonesWithFilter(t *testing.T) { 194 mockDomainClient := MockDomainClient{} 195 196 provider := &LinodeProvider{ 197 Client: &mockDomainClient, 198 domainFilter: endpoint.NewDomainFilter([]string{".com"}), 199 DryRun: false, 200 } 201 202 mockDomainClient.On( 203 "ListDomains", 204 mock.Anything, 205 mock.Anything, 206 ).Return(createZones(), nil).Once() 207 208 expected := []linodego.Domain{ 209 {ID: 1, Domain: "foo.com"}, 210 {ID: 3, Domain: "baz.com"}, 211 } 212 actual, err := provider.fetchZones(context.Background()) 213 require.NoError(t, err) 214 215 mockDomainClient.AssertExpectations(t) 216 assert.Equal(t, expected, actual) 217 } 218 219 func TestLinodeGetStrippedRecordName(t *testing.T) { 220 assert.Equal(t, "", getStrippedRecordName(linodego.Domain{ 221 Domain: "foo.com", 222 }, endpoint.Endpoint{ 223 DNSName: "foo.com", 224 })) 225 226 assert.Equal(t, "api", getStrippedRecordName(linodego.Domain{ 227 Domain: "foo.com", 228 }, endpoint.Endpoint{ 229 DNSName: "api.foo.com", 230 })) 231 } 232 233 func TestLinodeRecords(t *testing.T) { 234 mockDomainClient := MockDomainClient{} 235 236 provider := &LinodeProvider{ 237 Client: &mockDomainClient, 238 domainFilter: endpoint.NewDomainFilter([]string{}), 239 DryRun: false, 240 } 241 242 mockDomainClient.On( 243 "ListDomains", 244 mock.Anything, 245 mock.Anything, 246 ).Return(createZones(), nil).Once() 247 248 mockDomainClient.On( 249 "ListDomainRecords", 250 mock.Anything, 251 1, 252 mock.Anything, 253 ).Return(createFooRecords(), nil).Once() 254 mockDomainClient.On( 255 "ListDomainRecords", 256 mock.Anything, 257 2, 258 mock.Anything, 259 ).Return(createBarRecords(), nil).Once() 260 mockDomainClient.On( 261 "ListDomainRecords", 262 mock.Anything, 263 3, 264 mock.Anything, 265 ).Return(createBazRecords(), nil).Once() 266 267 actual, err := provider.Records(context.Background()) 268 require.NoError(t, err) 269 270 expected := []*endpoint.Endpoint{ 271 {DNSName: "foo.com", Targets: []string{"targetFoo"}, RecordType: "A", RecordTTL: 0, Labels: endpoint.NewLabels()}, 272 {DNSName: "foo.com", Targets: []string{"txt"}, RecordType: "TXT", RecordTTL: 0, Labels: endpoint.NewLabels()}, 273 {DNSName: "baz.com", Targets: []string{"targetBaz"}, RecordType: "A", RecordTTL: 0, Labels: endpoint.NewLabels()}, 274 {DNSName: "baz.com", Targets: []string{"txt"}, RecordType: "TXT", RecordTTL: 0, Labels: endpoint.NewLabels()}, 275 {DNSName: "api.baz.com", Targets: []string{"targetBaz"}, RecordType: "A", RecordTTL: 0, Labels: endpoint.NewLabels()}, 276 {DNSName: "api.baz.com", Targets: []string{"txt"}, RecordType: "TXT", RecordTTL: 0, Labels: endpoint.NewLabels()}, 277 } 278 279 mockDomainClient.AssertExpectations(t) 280 assert.Equal(t, expected, actual) 281 } 282 283 func TestLinodeApplyChanges(t *testing.T) { 284 mockDomainClient := MockDomainClient{} 285 286 provider := &LinodeProvider{ 287 Client: &mockDomainClient, 288 domainFilter: endpoint.NewDomainFilter([]string{}), 289 DryRun: false, 290 } 291 292 // Dummy Data 293 mockDomainClient.On( 294 "ListDomains", 295 mock.Anything, 296 mock.Anything, 297 ).Return(createZones(), nil).Once() 298 299 mockDomainClient.On( 300 "ListDomainRecords", 301 mock.Anything, 302 1, 303 mock.Anything, 304 ).Return(createFooRecords(), nil).Once() 305 mockDomainClient.On( 306 "ListDomainRecords", 307 mock.Anything, 308 2, 309 mock.Anything, 310 ).Return(createBarRecords(), nil).Once() 311 mockDomainClient.On( 312 "ListDomainRecords", 313 mock.Anything, 314 3, 315 mock.Anything, 316 ).Return(createBazRecords(), nil).Once() 317 318 // Apply Actions 319 mockDomainClient.On( 320 "DeleteDomainRecord", 321 mock.Anything, 322 3, 323 33, 324 ).Return(nil).Once() 325 326 mockDomainClient.On( 327 "DeleteDomainRecord", 328 mock.Anything, 329 3, 330 34, 331 ).Return(nil).Once() 332 333 mockDomainClient.On( 334 "UpdateDomainRecord", 335 mock.Anything, 336 1, 337 11, 338 linodego.DomainRecordUpdateOptions{ 339 Type: "A", Name: "", Target: "targetFoo", 340 Priority: getPriority(), Weight: getWeight(linodego.RecordTypeA), Port: getPort(), TTLSec: 300, 341 }, 342 ).Return(&linodego.DomainRecord{}, nil).Once() 343 344 mockDomainClient.On( 345 "CreateDomainRecord", 346 mock.Anything, 347 2, 348 linodego.DomainRecordCreateOptions{ 349 Type: "A", Name: "create", Target: "targetBar", 350 Priority: getPriority(), Weight: getWeight(linodego.RecordTypeA), Port: getPort(), TTLSec: 0, 351 }, 352 ).Return(&linodego.DomainRecord{}, nil).Once() 353 354 mockDomainClient.On( 355 "CreateDomainRecord", 356 mock.Anything, 357 2, 358 linodego.DomainRecordCreateOptions{ 359 Type: "A", Name: "", Target: "targetBar", 360 Priority: getPriority(), Weight: getWeight(linodego.RecordTypeA), Port: getPort(), TTLSec: 0, 361 }, 362 ).Return(&linodego.DomainRecord{}, nil).Once() 363 364 err := provider.ApplyChanges(context.Background(), &plan.Changes{ 365 Create: []*endpoint.Endpoint{{ 366 DNSName: "create.bar.io", 367 RecordType: "A", 368 Targets: []string{"targetBar"}, 369 }, { 370 DNSName: "bar.io", 371 RecordType: "A", 372 Targets: []string{"targetBar"}, 373 }, { 374 // This record should be skipped as it already exists 375 DNSName: "foo.com", 376 RecordType: "TXT", 377 Targets: []string{"txt"}, 378 }}, 379 Delete: []*endpoint.Endpoint{{ 380 DNSName: "api.baz.com", 381 RecordType: "A", 382 }, { 383 DNSName: "api.baz.com", 384 RecordType: "TXT", 385 }}, 386 UpdateNew: []*endpoint.Endpoint{{ 387 DNSName: "foo.com", 388 RecordType: "A", 389 RecordTTL: 300, 390 Targets: []string{"targetFoo"}, 391 }}, 392 UpdateOld: []*endpoint.Endpoint{}, 393 }) 394 require.NoError(t, err) 395 396 mockDomainClient.AssertExpectations(t) 397 } 398 399 func TestLinodeApplyChangesTargetAdded(t *testing.T) { 400 mockDomainClient := MockDomainClient{} 401 402 provider := &LinodeProvider{ 403 Client: &mockDomainClient, 404 domainFilter: endpoint.NewDomainFilter([]string{}), 405 DryRun: false, 406 } 407 408 // Dummy Data 409 mockDomainClient.On( 410 "ListDomains", 411 mock.Anything, 412 mock.Anything, 413 ).Return([]linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once() 414 415 mockDomainClient.On( 416 "ListDomainRecords", 417 mock.Anything, 418 1, 419 mock.Anything, 420 ).Return([]linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}}, nil).Once() 421 422 // Apply Actions 423 mockDomainClient.On( 424 "UpdateDomainRecord", 425 mock.Anything, 426 1, 427 11, 428 linodego.DomainRecordUpdateOptions{ 429 Type: "A", Name: "", Target: "targetA", 430 Priority: getPriority(), Weight: getWeight(linodego.RecordTypeA), Port: getPort(), 431 }, 432 ).Return(&linodego.DomainRecord{}, nil).Once() 433 434 mockDomainClient.On( 435 "CreateDomainRecord", 436 mock.Anything, 437 1, 438 linodego.DomainRecordCreateOptions{ 439 Type: "A", Name: "", Target: "targetB", 440 Priority: getPriority(), Weight: getWeight(linodego.RecordTypeA), Port: getPort(), 441 }, 442 ).Return(&linodego.DomainRecord{}, nil).Once() 443 444 err := provider.ApplyChanges(context.Background(), &plan.Changes{ 445 // From 1 target to 2 446 UpdateNew: []*endpoint.Endpoint{{ 447 DNSName: "example.com", 448 RecordType: "A", 449 Targets: []string{"targetA", "targetB"}, 450 }}, 451 UpdateOld: []*endpoint.Endpoint{}, 452 }) 453 require.NoError(t, err) 454 455 mockDomainClient.AssertExpectations(t) 456 } 457 458 func TestLinodeApplyChangesTargetRemoved(t *testing.T) { 459 mockDomainClient := MockDomainClient{} 460 461 provider := &LinodeProvider{ 462 Client: &mockDomainClient, 463 domainFilter: endpoint.NewDomainFilter([]string{}), 464 DryRun: false, 465 } 466 467 // Dummy Data 468 mockDomainClient.On( 469 "ListDomains", 470 mock.Anything, 471 mock.Anything, 472 ).Return([]linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once() 473 474 mockDomainClient.On( 475 "ListDomainRecords", 476 mock.Anything, 477 1, 478 mock.Anything, 479 ).Return([]linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}, {ID: 12, Type: "A", Name: "", Target: "targetB"}}, nil).Once() 480 481 // Apply Actions 482 mockDomainClient.On( 483 "UpdateDomainRecord", 484 mock.Anything, 485 1, 486 12, 487 linodego.DomainRecordUpdateOptions{ 488 Type: "A", Name: "", Target: "targetB", 489 Priority: getPriority(), Weight: getWeight(linodego.RecordTypeA), Port: getPort(), 490 }, 491 ).Return(&linodego.DomainRecord{}, nil).Once() 492 493 mockDomainClient.On( 494 "DeleteDomainRecord", 495 mock.Anything, 496 1, 497 11, 498 ).Return(nil).Once() 499 500 err := provider.ApplyChanges(context.Background(), &plan.Changes{ 501 // From 2 targets to 1 502 UpdateNew: []*endpoint.Endpoint{{ 503 DNSName: "example.com", 504 RecordType: "A", 505 Targets: []string{"targetB"}, 506 }}, 507 UpdateOld: []*endpoint.Endpoint{}, 508 }) 509 require.NoError(t, err) 510 511 mockDomainClient.AssertExpectations(t) 512 } 513 514 func TestLinodeApplyChangesNoChanges(t *testing.T) { 515 mockDomainClient := MockDomainClient{} 516 517 provider := &LinodeProvider{ 518 Client: &mockDomainClient, 519 domainFilter: endpoint.NewDomainFilter([]string{}), 520 DryRun: false, 521 } 522 523 // Dummy Data 524 mockDomainClient.On( 525 "ListDomains", 526 mock.Anything, 527 mock.Anything, 528 ).Return([]linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once() 529 530 mockDomainClient.On( 531 "ListDomainRecords", 532 mock.Anything, 533 1, 534 mock.Anything, 535 ).Return([]linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}}, nil).Once() 536 537 err := provider.ApplyChanges(context.Background(), &plan.Changes{}) 538 require.NoError(t, err) 539 540 mockDomainClient.AssertExpectations(t) 541 }