Skip to content

Commit

Permalink
tests: Add provider errors e2e specs
Browse files Browse the repository at this point in the history
Adds a provider errors set of specs to the e2e test suite. The purpose
of this is to test each supported provider returns a reasonable response
and updates the record status appropriately in cases where a user can
misconfigure a DNSRecord and cause a provider error. These mainly focus
on what can be set from the DNSPolicy (geo and weight) and the intention
isn't to exhaustively test all error scenarios.

Added as an e2e test since this can, and will be, different on every
provider. Tests also ensure that the records are not falling into a loop
of constantly being updated when a provider error is updating the
status.
  • Loading branch information
mikenairn committed May 16, 2024
1 parent 806a5bf commit c0b4a82
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 1 deletion.
2 changes: 1 addition & 1 deletion test/e2e/healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
)

// Test Cases covering multiple creation and deletion of health checks
var _ = Describe("Health Check Test", Serial, func() {
var _ = Describe("Health Check Test", Serial, Labels{"health_checks"}, func() {
// tests can be run in parallel in the same cluster
var testID string
// testDomainName generated domain for this test e.g. t-e2e-12345.e2e.hcpapps.net
Expand Down
255 changes: 255 additions & 0 deletions test/e2e/provider_errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
//go:build e2e

package e2e

import (
"context"
"strings"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
externaldnsendpoint "sigs.k8s.io/external-dns/endpoint"

"github.com/kuadrant/dns-operator/api/v1alpha1"
)

// Test Cases covering known provider errors that can be expected with misconfigured DNSRecord resource.
var _ = Describe("DNSRecord Provider Errors", Labels{"provider_errors"}, func() {
// testID is a randomly generated identifier for the test
// it is used to name resources and/or namespaces so different
// tests can be run in parallel in the same cluster
var testID string
// testDomainName generated domain for this test e.g. t-e2e-12345.e2e.hcpapps.net
var testDomainName string
// testHostname generated hostname for this test e.g. t-gw-mgc-12345.t-e2e-12345.e2e.hcpapps.net
var testHostname string

var dnsRecord *v1alpha1.DNSRecord

BeforeEach(func(ctx SpecContext) {
testID = "t-errors-" + GenerateName()
testDomainName = strings.Join([]string{testSuiteID, testZoneDomainName}, ".")
testHostname = strings.Join([]string{testID, testDomainName}, ".")
})

AfterEach(func(ctx SpecContext) {
if dnsRecord != nil {
err := k8sClient.Delete(ctx, dnsRecord,
client.PropagationPolicy(metav1.DeletePropagationForeground))
Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred())
}
})

It("correctly handles invalid geo", func(ctx SpecContext) {
var validGeoCode string
var expectedProviderErr string
if testDNSProvider == "gcp" {
//GCP
expectedProviderErr = "location': 'notageocode', invalid"
validGeoCode = "us-east1"
} else {
//AWS
expectedProviderErr = "Value 'notageocode' with length = '11' is not facet-valid with respect to length '2' for type 'ContinentCode'"
validGeoCode = "US"
}

invalidEndpoint := &externaldnsendpoint.Endpoint{
DNSName: testHostname,
Targets: []string{
"foo.example.com",
},
RecordType: "CNAME",
RecordTTL: 300,
SetIdentifier: "foo.example.com",
ProviderSpecific: externaldnsendpoint.ProviderSpecific{
{
Name: "geo-code",
Value: "notageocode", //invalid in all providers
},
},
}
testEndpoints := []*externaldnsendpoint.Endpoint{
invalidEndpoint,
}

dnsRecord = testBuildDNSRecord(testID, testNamespace, testManagedZoneName, "test-owner", testHostname)
dnsRecord.Spec.Endpoints = testEndpoints

By("creating dnsrecord " + dnsRecord.Name + " with invalid geo endpoint")
err := k8sClient.Create(ctx, dnsRecord)
Expect(err).ToNot(HaveOccurred())

By("checking " + dnsRecord.Name + " is not ready and has the expected provider error in the status")
Eventually(func(g Gomega, ctx context.Context) {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(dnsRecord.Status.Conditions).To(
ContainElement(MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(v1alpha1.ConditionTypeReady)),
"Status": Equal(metav1.ConditionFalse),
"Message": And(ContainSubstring("The DNS provider failed to ensure the record"), ContainSubstring(expectedProviderErr)),
})),
)
}, 10*time.Second, time.Second, ctx).Should(Succeed())

By("checking dnsrecord " + dnsRecord.Name + " is not being updated repeatedly")
tmpRecord := &v1alpha1.DNSRecord{}
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), tmpRecord)).To(Succeed())
Consistently(func(g Gomega, ctx context.Context) {
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)).To(Succeed())
g.Expect(dnsRecord.ResourceVersion).To(Equal(tmpRecord.ResourceVersion))
}, 10*time.Second, time.Second, ctx).Should(Succeed())

By("updating dnsrecord " + dnsRecord.Name + " with valid geo endpoint")
Eventually(func(g Gomega) {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
invalidEndpoint.ProviderSpecific = externaldnsendpoint.ProviderSpecific{
{
Name: "geo-code",
Value: validGeoCode, //valid for provider under test
},
}
dnsRecord.Spec.Endpoints = testEndpoints
err = k8sClient.Update(ctx, dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
}, TestTimeoutMedium, time.Second).Should(Succeed())

By("checking dnsrecord " + dnsRecord.Name + " no longer has provider error")
Eventually(func(g Gomega, ctx context.Context) {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(dnsRecord.Status.Conditions).To(
ContainElement(MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(v1alpha1.ConditionTypeReady)),
// Since this is an e2e test we have no idea how long it might take to become ready, so we can only really
// check that the message is one of the expected ones if it was accepted by the provider
"Message": Or(Equal("Provider ensured the dns record"), Equal("Awaiting validation")),
})),
)
}, TestTimeoutMedium, time.Second, ctx).Should(Succeed())

})

It("correctly handles invalid weight", func(ctx SpecContext) {
var expectedProviderErr string
if testDNSProvider == "gcp" {
//GCP
expectedProviderErr = "weight': '-1.0' Reason: backendError, Message: Invalid Value"
} else {
//AWS
expectedProviderErr = "weight' failed to satisfy constraint: Member must have value greater than or equal to 0"
}
validWeight := "100"

invalidEndpoint := &externaldnsendpoint.Endpoint{
DNSName: testHostname,
Targets: []string{
"foo.example.com",
},
RecordType: "CNAME",
RecordTTL: 300,
SetIdentifier: "foo.example.com",
ProviderSpecific: externaldnsendpoint.ProviderSpecific{
{
Name: "weight",
Value: "-1",
},
},
}
testEndpoints := []*externaldnsendpoint.Endpoint{
invalidEndpoint,
}

dnsRecord = testBuildDNSRecord(testID, testNamespace, testManagedZoneName, "test-owner", testHostname)
dnsRecord.Spec.Endpoints = testEndpoints

By("creating dnsrecord " + dnsRecord.Name + " with invalid weight endpoint")
err := k8sClient.Create(ctx, dnsRecord)
Expect(err).ToNot(HaveOccurred())

By("checking " + dnsRecord.Name + " is not ready and has the expected provider error in the status")
Eventually(func(g Gomega, ctx context.Context) {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(dnsRecord.Status.Conditions).To(
ContainElement(MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(v1alpha1.ConditionTypeReady)),
"Status": Equal(metav1.ConditionFalse),
"Message": And(ContainSubstring("The DNS provider failed to ensure the record"), ContainSubstring(expectedProviderErr)),
})),
)
}, 10*time.Second, time.Second, ctx).Should(Succeed())

By("checking dnsrecord " + dnsRecord.Name + " is not being updated repeatedly")
tmpRecord := &v1alpha1.DNSRecord{}
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), tmpRecord)).To(Succeed())
Consistently(func(g Gomega, ctx context.Context) {
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)).To(Succeed())
g.Expect(dnsRecord.ResourceVersion).To(Equal(tmpRecord.ResourceVersion))
}, 10*time.Second, time.Second, ctx).Should(Succeed())

By("updating dnsrecord " + dnsRecord.Name + " with valid weight endpoint")
Eventually(func(g Gomega) {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
invalidEndpoint.ProviderSpecific = externaldnsendpoint.ProviderSpecific{
{
Name: "weight",
Value: validWeight, //valid for provider under test
},
}
dnsRecord.Spec.Endpoints = testEndpoints
err = k8sClient.Update(ctx, dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
}, TestTimeoutMedium, time.Second).Should(Succeed())

By("checking dnsrecord " + dnsRecord.Name + " no longer has provider error")
Eventually(func(g Gomega, ctx context.Context) {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(dnsRecord.Status.Conditions).To(
ContainElement(MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(v1alpha1.ConditionTypeReady)),
// Since this is an e2e test we have no idea how long it might take to become ready, so we can only really
// check that the message is one of the expected ones if it was accepted by the provider
"Message": Or(Equal("Provider ensured the dns record"), Equal("Awaiting validation")),
})),
)
}, TestTimeoutMedium, time.Second, ctx).Should(Succeed())

})

})

// testBuildDNSRecord creates a valid dnsrecord with a single valid endpoint
func testBuildDNSRecord(name, ns, mzName, ownerID, rootHost string) *v1alpha1.DNSRecord {
return &v1alpha1.DNSRecord{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
},
Spec: v1alpha1.DNSRecordSpec{
OwnerID: ownerID,
RootHost: rootHost,
ManagedZoneRef: &v1alpha1.ManagedZoneReference{
Name: mzName,
},
Endpoints: []*externaldnsendpoint.Endpoint{
{
DNSName: rootHost,
Targets: []string{
"127.0.0.1",
},
RecordType: "A",
RecordTTL: 60,
},
},
},
}
}

0 comments on commit c0b4a82

Please sign in to comment.