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  }