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  }