add unit tests for plural provider (#11)

This commit is contained in:
Lukasz Zajaczkowski 2022-08-01 16:32:03 +02:00
parent 3f5ea38814
commit 79b7fe93d8
3 changed files with 429 additions and 10 deletions

View File

@ -1,3 +1,19 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plural package plural
import ( import (
@ -20,6 +36,12 @@ func (t *authedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.wrapped.RoundTrip(req) return t.wrapped.RoundTrip(req)
} }
type Client interface {
DnsRecords() ([]*DnsRecord, error)
CreateRecord(record *DnsRecord) (*DnsRecord, error)
DeleteRecord(name, ttype string) error
}
type Config struct { type Config struct {
Token string Token string
Endpoint string Endpoint string
@ -27,7 +49,7 @@ type Config struct {
Provider string Provider string
} }
type Client struct { type client struct {
ctx context.Context ctx context.Context
pluralClient *gqlclient.Client pluralClient *gqlclient.Client
config *Config config *Config
@ -39,7 +61,7 @@ type DnsRecord struct {
Records []string Records []string
} }
func NewClient(conf *Config) *Client { func NewClient(conf *Config) Client {
base := conf.BaseUrl() base := conf.BaseUrl()
httpClient := http.Client{ httpClient := http.Client{
Transport: &authedTransport{ Transport: &authedTransport{
@ -48,7 +70,7 @@ func NewClient(conf *Config) *Client {
}, },
} }
endpoint := base + "/gql" endpoint := base + "/gql"
return &Client{ return &client{
ctx: context.Background(), ctx: context.Background(),
pluralClient: gqlclient.NewClient(&httpClient, endpoint), pluralClient: gqlclient.NewClient(&httpClient, endpoint),
config: conf, config: conf,
@ -63,7 +85,7 @@ func (c *Config) BaseUrl() string {
return host return host
} }
func (client *Client) DnsRecords() ([]*DnsRecord, error) { func (client *client) DnsRecords() ([]*DnsRecord, error) {
resp, err := client.pluralClient.GetDNSRecords(client.ctx, client.config.Cluster, gqlclient.Provider(strings.ToUpper(client.config.Provider))) resp, err := client.pluralClient.GetDNSRecords(client.ctx, client.config.Cluster, gqlclient.Provider(strings.ToUpper(client.config.Provider)))
if err != nil { if err != nil {
return nil, err return nil, err
@ -83,7 +105,7 @@ func (client *Client) DnsRecords() ([]*DnsRecord, error) {
return records, nil return records, nil
} }
func (client *Client) CreateRecord(record *DnsRecord) (*DnsRecord, error) { func (client *client) CreateRecord(record *DnsRecord) (*DnsRecord, error) {
provider := gqlclient.Provider(strings.ToUpper(client.config.Provider)) provider := gqlclient.Provider(strings.ToUpper(client.config.Provider))
cluster := client.config.Cluster cluster := client.config.Cluster
attr := gqlclient.DNSRecordAttributes{ attr := gqlclient.DNSRecordAttributes{
@ -108,7 +130,7 @@ func (client *Client) CreateRecord(record *DnsRecord) (*DnsRecord, error) {
}, nil }, nil
} }
func (client *Client) DeleteRecord(name, ttype string) error { func (client *client) DeleteRecord(name, ttype string) error {
if _, err := client.pluralClient.DeleteDNSRecord(client.ctx, name, gqlclient.DNSRecordType(ttype)); err != nil { if _, err := client.pluralClient.DeleteDNSRecord(client.ctx, name, gqlclient.DNSRecordType(ttype)); err != nil {
return err return err
} }

View File

@ -1,3 +1,19 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plural package plural
import ( import (
@ -18,7 +34,7 @@ const (
type PluralProvider struct { type PluralProvider struct {
provider.BaseProvider provider.BaseProvider
Client *Client Client Client
} }
type RecordChange struct { type RecordChange struct {
@ -47,7 +63,7 @@ func NewPluralProvider(cluster, provider string) (*PluralProvider, error) {
return prov, nil return prov, nil
} }
func (p *PluralProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) { func (p *PluralProvider) Records(_ context.Context) (endpoints []*endpoint.Endpoint, err error) {
records, err := p.Client.DnsRecords() records, err := p.Client.DnsRecords()
if err != nil { if err != nil {
return return
@ -68,8 +84,8 @@ func (p *PluralProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endp
return endpoints return endpoints
} }
func (p *PluralProvider) ApplyChanges(ctx context.Context, diffs *plan.Changes) error { func (p *PluralProvider) ApplyChanges(_ context.Context, diffs *plan.Changes) error {
changes := []*RecordChange{} var changes []*RecordChange
for _, endpoint := range diffs.Create { for _, endpoint := range diffs.Create {
changes = append(changes, makeChange(CreateAction, endpoint.Targets, endpoint)) changes = append(changes, makeChange(CreateAction, endpoint.Targets, endpoint))
} }

View File

@ -0,0 +1,381 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plural
import (
"context"
"testing"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/external-dns/provider"
)
type ClientStub struct {
mockDnsRecords []*DnsRecord
}
// CreateRecord provides a mock function with given fields: record
func (c *ClientStub) CreateRecord(record *DnsRecord) (*DnsRecord, error) {
c.mockDnsRecords = append(c.mockDnsRecords, record)
return record, nil
}
// DeleteRecord provides a mock function with given fields: name, ttype
func (c *ClientStub) DeleteRecord(name string, ttype string) error {
newRecords := make([]*DnsRecord, 0)
for _, record := range c.mockDnsRecords {
if record.Name == name && record.Type == ttype {
continue
}
newRecords = append(newRecords, record)
}
c.mockDnsRecords = newRecords
return nil
}
// DnsRecords provides a mock function with given fields:
func (c *ClientStub) DnsRecords() ([]*DnsRecord, error) {
return c.mockDnsRecords, nil
}
func newPluralProvider(pluralDNSRecord []*DnsRecord) *PluralProvider {
if pluralDNSRecord == nil {
pluralDNSRecord = make([]*DnsRecord, 0)
}
return &PluralProvider{
BaseProvider: provider.BaseProvider{},
Client: &ClientStub{
mockDnsRecords: pluralDNSRecord,
},
}
}
func TestPluralRecords(t *testing.T) {
tests := []struct {
name string
expectedEndpoints []*endpoint.Endpoint
records []*DnsRecord
}{
{
name: "check records",
records: []*DnsRecord{
{
Type: endpoint.RecordTypeA,
Name: "example.com",
Records: []string{"123.123.123.122"},
},
{
Type: endpoint.RecordTypeA,
Name: "nginx.example.com",
Records: []string{"123.123.123.123"},
},
{
Type: endpoint.RecordTypeCNAME,
Name: "hack.example.com",
Records: []string{"bluecatnetworks.com"},
},
{
Type: endpoint.RecordTypeTXT,
Name: "kdb.example.com",
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
expectedEndpoints: []*endpoint.Endpoint{
{
DNSName: "example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.122"},
},
{
DNSName: "nginx.example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.123"},
},
{
DNSName: "hack.example.com",
RecordType: endpoint.RecordTypeCNAME,
Targets: endpoint.Targets{"bluecatnetworks.com"},
},
{
DNSName: "kdb.example.com",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
provider := newPluralProvider(test.records)
actual, err := provider.Records(context.Background())
if err != nil {
t.Fatal(err)
}
validateEndpoints(t, actual, test.expectedEndpoints)
})
}
}
func TestPluralApplyChangesCreate(t *testing.T) {
tests := []struct {
name string
expectedEndpoints []*endpoint.Endpoint
}{
{
name: "create new endpoints",
expectedEndpoints: []*endpoint.Endpoint{
{
DNSName: "example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.122"},
},
{
DNSName: "nginx.example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.123"},
},
{
DNSName: "hack.example.com",
RecordType: endpoint.RecordTypeCNAME,
Targets: endpoint.Targets{"bluecatnetworks.com"},
},
{
DNSName: "kdb.example.com",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
provider := newPluralProvider(nil)
// no records
actual, err := provider.Records(context.Background())
if err != nil {
t.Fatal(err)
}
assert.Equal(t, len(actual), 0, "expected no entries")
err = provider.ApplyChanges(context.Background(), &plan.Changes{Create: test.expectedEndpoints})
if err != nil {
t.Fatal(err)
}
actual, err = provider.Records(context.Background())
if err != nil {
t.Fatal(err)
}
validateEndpoints(t, actual, test.expectedEndpoints)
})
}
}
func TestPluralApplyChangesDelete(t *testing.T) {
tests := []struct {
name string
records []*DnsRecord
deleteEndpoints []*endpoint.Endpoint
expectedEndpoints []*endpoint.Endpoint
}{
{
name: "delete not existing record",
records: []*DnsRecord{
{
Type: endpoint.RecordTypeA,
Name: "example.com",
Records: []string{"123.123.123.122"},
},
{
Type: endpoint.RecordTypeA,
Name: "nginx.example.com",
Records: []string{"123.123.123.123"},
},
{
Type: endpoint.RecordTypeCNAME,
Name: "hack.example.com",
Records: []string{"bluecatnetworks.com"},
},
{
Type: endpoint.RecordTypeTXT,
Name: "kdb.example.com",
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
deleteEndpoints: []*endpoint.Endpoint{
{
DNSName: "fake.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
expectedEndpoints: []*endpoint.Endpoint{
{
DNSName: "example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.122"},
},
{
DNSName: "nginx.example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.123"},
},
{
DNSName: "hack.example.com",
RecordType: endpoint.RecordTypeCNAME,
Targets: endpoint.Targets{"bluecatnetworks.com"},
},
{
DNSName: "kdb.example.com",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
},
{
name: "delete one record",
records: []*DnsRecord{
{
Type: endpoint.RecordTypeA,
Name: "example.com",
Records: []string{"123.123.123.122"},
},
{
Type: endpoint.RecordTypeA,
Name: "nginx.example.com",
Records: []string{"123.123.123.123"},
},
{
Type: endpoint.RecordTypeCNAME,
Name: "hack.example.com",
Records: []string{"bluecatnetworks.com"},
},
{
Type: endpoint.RecordTypeTXT,
Name: "kdb.example.com",
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
deleteEndpoints: []*endpoint.Endpoint{
{
DNSName: "kdb.example.com",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
expectedEndpoints: []*endpoint.Endpoint{
{
DNSName: "example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.122"},
},
{
DNSName: "nginx.example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.123"},
},
{
DNSName: "hack.example.com",
RecordType: endpoint.RecordTypeCNAME,
Targets: endpoint.Targets{"bluecatnetworks.com"},
},
},
},
{
name: "delete all records",
records: []*DnsRecord{
{
Type: endpoint.RecordTypeA,
Name: "example.com",
Records: []string{"123.123.123.122"},
},
{
Type: endpoint.RecordTypeA,
Name: "nginx.example.com",
Records: []string{"123.123.123.123"},
},
{
Type: endpoint.RecordTypeCNAME,
Name: "hack.example.com",
Records: []string{"bluecatnetworks.com"},
},
{
Type: endpoint.RecordTypeTXT,
Name: "kdb.example.com",
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
},
deleteEndpoints: []*endpoint.Endpoint{
{
DNSName: "kdb.example.com",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
},
{
DNSName: "example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.122"},
},
{
DNSName: "nginx.example.com",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"123.123.123.123"},
},
{
DNSName: "hack.example.com",
RecordType: endpoint.RecordTypeCNAME,
Targets: endpoint.Targets{"bluecatnetworks.com"},
},
},
expectedEndpoints: []*endpoint.Endpoint{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
provider := newPluralProvider(test.records)
err := provider.ApplyChanges(context.Background(), &plan.Changes{Delete: test.deleteEndpoints})
if err != nil {
t.Fatal(err)
}
actual, err := provider.Records(context.Background())
if err != nil {
t.Fatal(err)
}
validateEndpoints(t, actual, test.expectedEndpoints)
})
}
}
func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) {
assert.True(t, testutils.SameEndpoints(endpoints, expected), "expected and actual endpoints don't match. %s:%s", endpoints, expected)
}