sigs.k8s.io/external-dns@v0.14.1/provider/bluecat/bluecat_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  
    14  package bluecat
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"sigs.k8s.io/external-dns/endpoint"
    24  	"sigs.k8s.io/external-dns/internal/testutils"
    25  	"sigs.k8s.io/external-dns/plan"
    26  	"sigs.k8s.io/external-dns/provider"
    27  	api "sigs.k8s.io/external-dns/provider/bluecat/gateway"
    28  )
    29  
    30  type mockGatewayClient struct {
    31  	mockBluecatZones  *[]api.BluecatZone
    32  	mockBluecatHosts  *[]api.BluecatHostRecord
    33  	mockBluecatCNAMEs *[]api.BluecatCNAMERecord
    34  	mockBluecatTXTs   *[]api.BluecatTXTRecord
    35  }
    36  
    37  type Changes struct {
    38  	// Records that need to be created
    39  	Create []*endpoint.Endpoint
    40  	// Records that need to be updated (current data)
    41  	UpdateOld []*endpoint.Endpoint
    42  	// Records that need to be updated (desired data)
    43  	UpdateNew []*endpoint.Endpoint
    44  	// Records that need to be deleted
    45  	Delete []*endpoint.Endpoint
    46  }
    47  
    48  func (g mockGatewayClient) GetBluecatZones(zoneName string) ([]api.BluecatZone, error) {
    49  	return *g.mockBluecatZones, nil
    50  }
    51  
    52  func (g mockGatewayClient) GetHostRecords(zone string, records *[]api.BluecatHostRecord) error {
    53  	*records = *g.mockBluecatHosts
    54  	return nil
    55  }
    56  
    57  func (g mockGatewayClient) GetCNAMERecords(zone string, records *[]api.BluecatCNAMERecord) error {
    58  	*records = *g.mockBluecatCNAMEs
    59  	return nil
    60  }
    61  
    62  func (g mockGatewayClient) GetHostRecord(name string, record *api.BluecatHostRecord) error {
    63  	for _, currentRecord := range *g.mockBluecatHosts {
    64  		if currentRecord.Name == strings.Split(name, ".")[0] {
    65  			*record = currentRecord
    66  			return nil
    67  		}
    68  	}
    69  	return nil
    70  }
    71  
    72  func (g mockGatewayClient) GetCNAMERecord(name string, record *api.BluecatCNAMERecord) error {
    73  	for _, currentRecord := range *g.mockBluecatCNAMEs {
    74  		if currentRecord.Name == strings.Split(name, ".")[0] {
    75  			*record = currentRecord
    76  			return nil
    77  		}
    78  	}
    79  	return nil
    80  }
    81  
    82  func (g mockGatewayClient) CreateHostRecord(zone string, req *api.BluecatCreateHostRecordRequest) (err error) {
    83  	return nil
    84  }
    85  
    86  func (g mockGatewayClient) CreateCNAMERecord(zone string, req *api.BluecatCreateCNAMERecordRequest) (err error) {
    87  	return nil
    88  }
    89  
    90  func (g mockGatewayClient) DeleteHostRecord(name string, zone string) (err error) {
    91  	*g.mockBluecatHosts = nil
    92  	return nil
    93  }
    94  
    95  func (g mockGatewayClient) DeleteCNAMERecord(name string, zone string) (err error) {
    96  	*g.mockBluecatCNAMEs = nil
    97  	return nil
    98  }
    99  
   100  func (g mockGatewayClient) GetTXTRecords(zone string, records *[]api.BluecatTXTRecord) error {
   101  	*records = *g.mockBluecatTXTs
   102  	return nil
   103  }
   104  
   105  func (g mockGatewayClient) GetTXTRecord(name string, record *api.BluecatTXTRecord) error {
   106  	for _, currentRecord := range *g.mockBluecatTXTs {
   107  		if currentRecord.Name == name {
   108  			*record = currentRecord
   109  			return nil
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  func (g mockGatewayClient) CreateTXTRecord(zone string, req *api.BluecatCreateTXTRecordRequest) error {
   116  	return nil
   117  }
   118  
   119  func (g mockGatewayClient) DeleteTXTRecord(name string, zone string) error {
   120  	*g.mockBluecatTXTs = nil
   121  	return nil
   122  }
   123  
   124  func (g mockGatewayClient) ServerFullDeploy() error {
   125  	return nil
   126  }
   127  
   128  func createMockBluecatZone(fqdn string) api.BluecatZone {
   129  	props := "absoluteName=" + fqdn
   130  	return api.BluecatZone{
   131  		Properties: props,
   132  		Name:       fqdn,
   133  		ID:         3,
   134  	}
   135  }
   136  
   137  func createMockBluecatHostRecord(fqdn, target string, ttl int) api.BluecatHostRecord {
   138  	props := "absoluteName=" + fqdn + "|addresses=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
   139  	nameParts := strings.Split(fqdn, ".")
   140  	return api.BluecatHostRecord{
   141  		Name:       nameParts[0],
   142  		Properties: props,
   143  		ID:         3,
   144  	}
   145  }
   146  
   147  func createMockBluecatCNAME(alias, target string, ttl int) api.BluecatCNAMERecord {
   148  	props := "absoluteName=" + alias + "|linkedRecordName=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
   149  	nameParts := strings.Split(alias, ".")
   150  	return api.BluecatCNAMERecord{
   151  		Name:       nameParts[0],
   152  		Properties: props,
   153  	}
   154  }
   155  
   156  func createMockBluecatTXT(fqdn, txt string) api.BluecatTXTRecord {
   157  	return api.BluecatTXTRecord{
   158  		Name:       fqdn,
   159  		Properties: txt,
   160  	}
   161  }
   162  
   163  func newBluecatProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, client mockGatewayClient) *BluecatProvider {
   164  	return &BluecatProvider{
   165  		domainFilter:  domainFilter,
   166  		zoneIDFilter:  zoneIDFilter,
   167  		dryRun:        dryRun,
   168  		gatewayClient: client,
   169  	}
   170  }
   171  
   172  type bluecatTestData []struct {
   173  	TestDescription string
   174  	Endpoints       []*endpoint.Endpoint
   175  }
   176  
   177  var tests = bluecatTestData{
   178  	{
   179  		"first test case", // TODO: better test description
   180  		[]*endpoint.Endpoint{
   181  			{
   182  				DNSName:    "example.com",
   183  				RecordType: endpoint.RecordTypeA,
   184  				Targets:    endpoint.Targets{"123.123.123.122"},
   185  				RecordTTL:  endpoint.TTL(30),
   186  			},
   187  			{
   188  				DNSName:    "nginx.example.com",
   189  				RecordType: endpoint.RecordTypeA,
   190  				Targets:    endpoint.Targets{"123.123.123.123"},
   191  				RecordTTL:  endpoint.TTL(30),
   192  			},
   193  			{
   194  				DNSName:    "whitespace.example.com",
   195  				RecordType: endpoint.RecordTypeA,
   196  				Targets:    endpoint.Targets{"123.123.123.124"},
   197  				RecordTTL:  endpoint.TTL(30),
   198  			},
   199  			{
   200  				DNSName:    "hack.example.com",
   201  				RecordType: endpoint.RecordTypeCNAME,
   202  				Targets:    endpoint.Targets{"bluecatnetworks.com"},
   203  				RecordTTL:  endpoint.TTL(30),
   204  			},
   205  			{
   206  				DNSName:    "wack.example.com",
   207  				RecordType: endpoint.RecordTypeTXT,
   208  				Targets:    endpoint.Targets{"hello"},
   209  				Labels:     endpoint.Labels{"owner": ""},
   210  			},
   211  			{
   212  				DNSName:    "sack.example.com",
   213  				RecordType: endpoint.RecordTypeTXT,
   214  				Targets:    endpoint.Targets{""},
   215  				Labels:     endpoint.Labels{"owner": ""},
   216  			},
   217  			{
   218  				DNSName:    "kdb.example.com",
   219  				RecordType: endpoint.RecordTypeTXT,
   220  				Targets:    endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
   221  				Labels:     endpoint.Labels{"owner": "default"},
   222  			},
   223  		},
   224  	},
   225  }
   226  
   227  func TestBluecatRecords(t *testing.T) {
   228  	client := mockGatewayClient{
   229  		mockBluecatZones: &[]api.BluecatZone{
   230  			createMockBluecatZone("example.com"),
   231  		},
   232  		mockBluecatTXTs: &[]api.BluecatTXTRecord{
   233  			createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
   234  			createMockBluecatTXT("wack.example.com", "hello"),
   235  			createMockBluecatTXT("sack.example.com", ""),
   236  		},
   237  		mockBluecatHosts: &[]api.BluecatHostRecord{
   238  			createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
   239  			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
   240  			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
   241  		},
   242  		mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
   243  			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
   244  		},
   245  	}
   246  
   247  	provider := newBluecatProvider(
   248  		endpoint.NewDomainFilter([]string{"example.com"}),
   249  		provider.NewZoneIDFilter([]string{""}), false, client)
   250  
   251  	for _, ti := range tests {
   252  		actual, err := provider.Records(context.Background())
   253  		if err != nil {
   254  			t.Fatal(err)
   255  		}
   256  		validateEndpoints(t, actual, ti.Endpoints)
   257  	}
   258  }
   259  
   260  func TestBluecatApplyChangesCreate(t *testing.T) {
   261  	client := mockGatewayClient{
   262  		mockBluecatZones: &[]api.BluecatZone{
   263  			createMockBluecatZone("example.com"),
   264  		},
   265  		mockBluecatHosts:  &[]api.BluecatHostRecord{},
   266  		mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{},
   267  		mockBluecatTXTs:   &[]api.BluecatTXTRecord{},
   268  	}
   269  
   270  	provider := newBluecatProvider(
   271  		endpoint.NewDomainFilter([]string{"example.com"}),
   272  		provider.NewZoneIDFilter([]string{""}), false, client)
   273  
   274  	for _, ti := range tests {
   275  		err := provider.ApplyChanges(context.Background(), &plan.Changes{Create: ti.Endpoints})
   276  		if err != nil {
   277  			t.Fatal(err)
   278  		}
   279  
   280  		actual, err := provider.Records(context.Background())
   281  		if err != nil {
   282  			t.Fatal(err)
   283  		}
   284  		validateEndpoints(t, actual, []*endpoint.Endpoint{})
   285  	}
   286  }
   287  
   288  func TestBluecatApplyChangesDelete(t *testing.T) {
   289  	client := mockGatewayClient{
   290  		mockBluecatZones: &[]api.BluecatZone{
   291  			createMockBluecatZone("example.com"),
   292  		},
   293  		mockBluecatHosts: &[]api.BluecatHostRecord{
   294  			createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
   295  			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
   296  			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
   297  		},
   298  		mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
   299  			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
   300  		},
   301  		mockBluecatTXTs: &[]api.BluecatTXTRecord{
   302  			createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
   303  			createMockBluecatTXT("wack.example.com", "hello"),
   304  			createMockBluecatTXT("sack.example.com", ""),
   305  		},
   306  	}
   307  
   308  	provider := newBluecatProvider(
   309  		endpoint.NewDomainFilter([]string{"example.com"}),
   310  		provider.NewZoneIDFilter([]string{""}), false, client)
   311  
   312  	for _, ti := range tests {
   313  		err := provider.ApplyChanges(context.Background(), &plan.Changes{Delete: ti.Endpoints})
   314  		if err != nil {
   315  			t.Fatal(err)
   316  		}
   317  
   318  		actual, err := provider.Records(context.Background())
   319  		if err != nil {
   320  			t.Fatal(err)
   321  		}
   322  		validateEndpoints(t, actual, []*endpoint.Endpoint{})
   323  	}
   324  }
   325  
   326  func TestBluecatApplyChangesDeleteWithOwner(t *testing.T) {
   327  	client := mockGatewayClient{
   328  		mockBluecatZones: &[]api.BluecatZone{
   329  			createMockBluecatZone("example.com"),
   330  		},
   331  		mockBluecatHosts: &[]api.BluecatHostRecord{
   332  			createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
   333  			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
   334  			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
   335  		},
   336  		mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
   337  			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
   338  		},
   339  		mockBluecatTXTs: &[]api.BluecatTXTRecord{
   340  			createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
   341  			createMockBluecatTXT("wack.example.com", "hello"),
   342  			createMockBluecatTXT("sack.example.com", ""),
   343  		},
   344  	}
   345  
   346  	provider := newBluecatProvider(
   347  		endpoint.NewDomainFilter([]string{"example.com"}),
   348  		provider.NewZoneIDFilter([]string{""}), false, client)
   349  
   350  	for _, ti := range tests {
   351  		for _, ep := range ti.Endpoints {
   352  			if strings.Contains(ep.Targets.String(), "external-dns") {
   353  				owner, err := extractOwnerfromTXTRecord(ep.Targets.String())
   354  				if err != nil {
   355  					t.Logf("%v", err)
   356  				} else {
   357  					t.Logf("Owner %s", owner)
   358  				}
   359  			}
   360  		}
   361  		err := provider.ApplyChanges(context.Background(), &plan.Changes{Delete: ti.Endpoints})
   362  		if err != nil {
   363  			t.Fatal(err)
   364  		}
   365  		actual, err := provider.Records(context.Background())
   366  		if err != nil {
   367  			t.Fatal(err)
   368  		}
   369  		validateEndpoints(t, actual, []*endpoint.Endpoint{})
   370  	}
   371  }
   372  
   373  // TODO: ensure findZone method is tested
   374  // TODO: ensure zones method is tested
   375  // TODO: ensure createRecords method is tested
   376  // TODO: ensure deleteRecords method is tested
   377  // TODO: ensure recordSet method is tested
   378  
   379  // TODO: Figure out why recordSet.res is not being set properly
   380  func TestBluecatRecordset(t *testing.T) {
   381  	client := mockGatewayClient{
   382  		mockBluecatZones: &[]api.BluecatZone{
   383  			createMockBluecatZone("example.com"),
   384  		},
   385  		mockBluecatHosts: &[]api.BluecatHostRecord{
   386  			createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
   387  			createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
   388  			createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
   389  		},
   390  		mockBluecatCNAMEs: &[]api.BluecatCNAMERecord{
   391  			createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
   392  		},
   393  		mockBluecatTXTs: &[]api.BluecatTXTRecord{
   394  			createMockBluecatTXT("abc.example.com", "hello"),
   395  		},
   396  	}
   397  
   398  	provider := newBluecatProvider(
   399  		endpoint.NewDomainFilter([]string{"example.com"}),
   400  		provider.NewZoneIDFilter([]string{""}), false, client)
   401  
   402  	// Test txt records for recordSet function
   403  	testTxtEndpoint := endpoint.NewEndpoint("abc.example.com", endpoint.RecordTypeTXT, "hello")
   404  	txtObj := api.BluecatCreateTXTRecordRequest{
   405  		AbsoluteName: testTxtEndpoint.DNSName,
   406  		Text:         testTxtEndpoint.Targets[0],
   407  	}
   408  	txtRecords := []api.BluecatTXTRecord{
   409  		createMockBluecatTXT("abc.example.com", "hello"),
   410  	}
   411  	expected := bluecatRecordSet{
   412  		obj: &txtObj,
   413  		res: &txtRecords,
   414  	}
   415  	actual, err := provider.recordSet(testTxtEndpoint, true)
   416  	if err != nil {
   417  		t.Fatal(err)
   418  	}
   419  	assert.Equal(t, actual.obj, expected.obj)
   420  	assert.Equal(t, actual.res, expected.res)
   421  
   422  	// Test a records for recordSet function
   423  	testHostEndpoint := endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124")
   424  	hostObj := api.BluecatCreateHostRecordRequest{
   425  		AbsoluteName: testHostEndpoint.DNSName,
   426  		IP4Address:   testHostEndpoint.Targets[0],
   427  	}
   428  	hostRecords := []api.BluecatHostRecord{
   429  		createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
   430  	}
   431  	hostExpected := bluecatRecordSet{
   432  		obj: &hostObj,
   433  		res: &hostRecords,
   434  	}
   435  	hostActual, err := provider.recordSet(testHostEndpoint, true)
   436  	if err != nil {
   437  		t.Fatal(err)
   438  	}
   439  	assert.Equal(t, hostActual.obj, hostExpected.obj)
   440  	assert.Equal(t, hostActual.res, hostExpected.res)
   441  
   442  	// Test CName records for recordSet function
   443  	testCnameEndpoint := endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "bluecatnetworks.com")
   444  	cnameObj := api.BluecatCreateCNAMERecordRequest{
   445  		AbsoluteName: testCnameEndpoint.DNSName,
   446  		LinkedRecord: testCnameEndpoint.Targets[0],
   447  	}
   448  	cnameRecords := []api.BluecatCNAMERecord{
   449  		createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
   450  	}
   451  	cnameExpected := bluecatRecordSet{
   452  		obj: &cnameObj,
   453  		res: &cnameRecords,
   454  	}
   455  	cnameActual, err := provider.recordSet(testCnameEndpoint, true)
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  	assert.Equal(t, cnameActual.obj, cnameExpected.obj)
   460  	assert.Equal(t, cnameActual.res, cnameExpected.res)
   461  }
   462  
   463  func validateEndpoints(t *testing.T, actual, expected []*endpoint.Endpoint) {
   464  	assert.True(t, testutils.SameEndpoints(actual, expected), "actual and expected endpoints don't match. %s:%s", actual, expected)
   465  }