Skip to content

Commit

Permalink
dnspolicy section name support
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Nairn <[email protected]>
  • Loading branch information
mikenairn committed Oct 30, 2024
1 parent a7d28f6 commit bc3b91a
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 14 deletions.
24 changes: 17 additions & 7 deletions api/v1alpha1/dnspolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
Expand Down Expand Up @@ -58,7 +59,7 @@ type DNSPolicySpec struct {
// targetRef identifies an API object to apply policy to.
// +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'",message="Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'"
// +kubebuilder:validation:XValidation:rule="self.kind == 'Gateway'",message="Invalid targetRef.kind. The only supported values are 'Gateway'"
TargetRef gatewayapiv1alpha2.LocalPolicyTargetReference `json:"targetRef"`
TargetRef gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRef"`

// +optional
HealthCheck *dnsv1alpha1.HealthCheckSpec `json:"healthCheck,omitempty"`
Expand Down Expand Up @@ -190,7 +191,7 @@ func (p *DNSPolicy) GetRulesHostnames() []string {
}

func (p *DNSPolicy) GetTargetRef() gatewayapiv1alpha2.LocalPolicyTargetReference {
return p.Spec.TargetRef
return p.Spec.TargetRef.LocalPolicyTargetReference
}

func (p *DNSPolicy) GetStatus() kuadrantgatewayapi.PolicyStatus {
Expand Down Expand Up @@ -252,7 +253,7 @@ func NewDNSPolicy(name, ns string) *DNSPolicy {
}
}

func (p *DNSPolicy) WithTargetRef(targetRef gatewayapiv1alpha2.LocalPolicyTargetReference) *DNSPolicy {
func (p *DNSPolicy) WithTargetRef(targetRef gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName) *DNSPolicy {
p.Spec.TargetRef = targetRef
return p
}
Expand Down Expand Up @@ -290,13 +291,22 @@ func (p *DNSPolicy) WithExcludeAddresses(excluded []string) *DNSPolicy {
//TargetRef

func (p *DNSPolicy) WithTargetGateway(gwName string) *DNSPolicy {
return p.WithTargetRef(gatewayapiv1alpha2.LocalPolicyTargetReference{
Group: gatewayapiv1.GroupName,
Kind: "Gateway",
Name: gatewayapiv1.ObjectName(gwName),
return p.WithTargetRef(gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{
LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{
Group: gatewayapiv1.GroupName,
Kind: "Gateway",
Name: gatewayapiv1.ObjectName(gwName),
},
SectionName: nil,
})
}

func (p *DNSPolicy) WithTargetGatewayListener(gwName string, lName string) *DNSPolicy {
p.WithTargetGateway(gwName)
p.Spec.TargetRef.SectionName = ptr.To(gatewayapiv1.SectionName(lName))
return p
}

//HealthCheck

func (p *DNSPolicy) WithHealthCheckFor(endpoint string, port int, protocol string, failureThreshold int) *DNSPolicy {
Expand Down
6 changes: 3 additions & 3 deletions api/v1alpha1/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ var _ machinery.Policy = &DNSPolicy{}

func (p *DNSPolicy) GetTargetRefs() []machinery.PolicyTargetReference {
return []machinery.PolicyTargetReference{
machinery.LocalPolicyTargetReference{
LocalPolicyTargetReference: p.Spec.TargetRef,
PolicyNamespace: p.Namespace,
machinery.LocalPolicyTargetReferenceWithSectionName{
LocalPolicyTargetReferenceWithSectionName: p.Spec.TargetRef,
PolicyNamespace: p.Namespace,
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ metadata:
capabilities: Basic Install
categories: Integration & Delivery
containerImage: quay.io/kuadrant/kuadrant-operator:latest
createdAt: "2024-10-22T09:01:33Z"
createdAt: "2024-10-25T15:27:18Z"
description: A Kubernetes Operator to manage the lifecycle of the Kuadrant system
operators.operatorframework.io/builder: operator-sdk-v1.32.0
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
Expand Down
19 changes: 19 additions & 0 deletions bundle/manifests/kuadrant.io_dnspolicies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,25 @@ spec:
maxLength: 253
minLength: 1
type: string
sectionName:
description: |-
SectionName is the name of a section within the target resource. When
unspecified, this targetRef targets the entire resource. In the following
resources, SectionName is interpreted as the following:
* Gateway: Listener name
* HTTPRoute: HTTPRouteRule name
* Service: Port name
If a SectionName is specified, but does not exist on the targeted object,
the Policy must fail to attach, and the policy implementation should record
a `ResolvedRefs` or similar Condition in the Policy's status.
maxLength: 253
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
required:
- group
- kind
Expand Down
19 changes: 19 additions & 0 deletions charts/kuadrant-operator/templates/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6962,6 +6962,25 @@ spec:
maxLength: 253
minLength: 1
type: string
sectionName:
description: |-
SectionName is the name of a section within the target resource. When
unspecified, this targetRef targets the entire resource. In the following
resources, SectionName is interpreted as the following:


* Gateway: Listener name
* HTTPRoute: HTTPRouteRule name
* Service: Port name


If a SectionName is specified, but does not exist on the targeted object,
the Policy must fail to attach, and the policy implementation should record
a `ResolvedRefs` or similar Condition in the Policy's status.
maxLength: 253
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
required:
- group
- kind
Expand Down
19 changes: 19 additions & 0 deletions config/crd/bases/kuadrant.io_dnspolicies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,25 @@ spec:
maxLength: 253
minLength: 1
type: string
sectionName:
description: |-
SectionName is the name of a section within the target resource. When
unspecified, this targetRef targets the entire resource. In the following
resources, SectionName is interpreted as the following:
* Gateway: Listener name
* HTTPRoute: HTTPRouteRule name
* Service: Port name
If a SectionName is specified, but does not exist on the targeted object,
the Policy must fail to attach, and the policy implementation should record
a `ResolvedRefs` or similar Condition in the Policy's status.
maxLength: 253
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
required:
- group
- kind
Expand Down
142 changes: 141 additions & 1 deletion tests/common/dnspolicy/dnspolicy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ var _ = Describe("DNSPolicy controller", func() {
})),
)
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, currentWildcardRec)).To(Succeed())
g.Expect(currentRec.Status.Conditions).To(
g.Expect(currentWildcardRec.Status.Conditions).To(
ContainElements(
MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)),
Expand Down Expand Up @@ -1071,6 +1071,146 @@ var _ = Describe("DNSPolicy controller", func() {
})
})

Context("section name", func() {
It("should handle policy with section name", func(ctx SpecContext) {
containReadyCondition := ContainElements(
MatchFields(IgnoreExtras, Fields{
"Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)),
"Status": Equal(metav1.ConditionTrue),
}))

rootEndpointMatcher := func(dnsName, recordType, setID string, ttl int64, targets ...string) types.GomegaMatcher {
return PointTo(MatchFields(IgnoreExtras, Fields{
"DNSName": Equal(dnsName),
"Targets": ConsistOf(targets),
"RecordType": Equal(recordType),
"SetIdentifier": Equal(setID),
"RecordTTL": Equal(externaldns.TTL(ttl)),
}))
}

//listener 1 & 2 - Default gateway policy has a loadbalancing section so will create loadbalanced records with CNAME Records for the rootHost
currentRecRootEndpoint := rootEndpointMatcher(tests.HostOne(domain), "CNAME", "", 300, "klb.test."+domain)
currentWildcardRecRootEndpoint := rootEndpointMatcher(tests.HostWildcard(domain), "CNAME", "", 300, "klb."+domain)

//get the current dnsrecord and wildcard dnsrecord
currentRec := &kuadrantdnsv1alpha1.DNSRecord{}
currentWildcardRec := &kuadrantdnsv1alpha1.DNSRecord{}
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, currentRec)).To(Succeed())
g.Expect(currentRec.Status.Conditions).To(containReadyCondition)
g.Expect(currentRec.Spec.Endpoints).To(ContainElement(currentRecRootEndpoint))

g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, currentWildcardRec)).To(Succeed())
g.Expect(currentWildcardRec.Status.Conditions).To(containReadyCondition)
g.Expect(currentWildcardRec.Spec.Endpoints).To(ContainElement(currentWildcardRecRootEndpoint))
}, tests.TimeoutLong, time.Second).Should(BeNil())

By("creating a dnspolicy with section name for listener one")
dnsPolicyWithSection := tests.NewDNSPolicy("test-dns-policy-with-section-name", testNamespace).
WithProviderSecret(*dnsProviderSecret).
WithTargetGatewayListener(tests.GatewayName, tests.ListenerNameOne)
Expect(k8sClient.Create(ctx, dnsPolicyWithSection)).To(Succeed())

//listener 1 - Listener policy has no loadbalancing section so will create simple records with A Records for the rootHost
newRecRootEndpoint := rootEndpointMatcher(tests.HostOne(domain), "A", "", 60, tests.IPAddressOne, tests.IPAddressTwo)
//listener 2 - Default gateway policy has a loadbalancing section so will create loadbalanced records with CNAME Records for the rootHost
newWildcardRecRootEndpoint := currentWildcardRecRootEndpoint

//get the dnsrecord again and verify it's no longer the same DNSRecord resource and the record type for the root host has changed
//get the wildcard dnsrecord again and verify the DNSRecord resource is unchanged
Eventually(func(g Gomega) {
newRec := &kuadrantdnsv1alpha1.DNSRecord{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, newRec)).To(Succeed())
g.Expect(newRec.Spec.RootHost).To(Equal(currentRec.Spec.RootHost))
g.Expect(newRec.UID).ToNot(Equal(currentRec.UID)) // if/when we remove the need for record re-creation on policy changes, these assertions can be removed
g.Expect(newRec.Status.Conditions).To(containReadyCondition)
g.Expect(newRec.Spec.Endpoints).To(ContainElement(newRecRootEndpoint))

newWildcardRec := &kuadrantdnsv1alpha1.DNSRecord{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, newWildcardRec)).To(Succeed())
g.Expect(newWildcardRec.Spec.RootHost).To(Equal(currentWildcardRec.Spec.RootHost))
g.Expect(newWildcardRec.UID).To(Equal(currentWildcardRec.UID))
g.Expect(newWildcardRec.Status.Conditions).To(containReadyCondition)
g.Expect(newWildcardRec.Spec.Endpoints).To(ContainElement(newWildcardRecRootEndpoint))

//ToDo Add checks for policy affected by on gateway when possible, will require discoverability changes

currentRec = newRec
currentWildcardRec = newWildcardRec
}, tests.TimeoutLong, time.Second).Should(BeNil())

By("updating dnspolicy section name to listener two")
Eventually(func() error {
existingDNSpolicy := &v1alpha1.DNSPolicy{}
if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicyWithSection), existingDNSpolicy); err != nil {
return err
}
patch := client.MergeFrom(existingDNSpolicy.DeepCopy())
existingDNSpolicy.Spec.TargetRef.SectionName = ptr.To(gatewayapiv1.SectionName(tests.ListenerNameWildcard))
return k8sClient.Patch(ctx, existingDNSpolicy, patch)
}, tests.TimeoutMedium, time.Second).Should(Succeed())

//listener 1 - Default gateway policy has a loadbalancing section so will create loadbalanced records with CNAME Records for the rootHost
newRecRootEndpoint = rootEndpointMatcher(tests.HostOne(domain), "CNAME", "", 300, "klb.test."+domain)
//listener 2 - Listener policy has no loadbalancing section so will create simple records with A Records for the rootHost
newWildcardRecRootEndpoint = rootEndpointMatcher(tests.HostWildcard(domain), "A", "", 60, tests.IPAddressOne, tests.IPAddressTwo)

//get the dnsrecord again and verify it's no longer the same DNSRecord resource and the record type for the root host has changed
//get the wildcard dnsrecord and verify it's no longer the same DNSRecord resource and the record type for the root host has changed
Eventually(func(g Gomega) {
newRec := &kuadrantdnsv1alpha1.DNSRecord{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, newRec)).To(Succeed())
g.Expect(newRec.Spec.RootHost).To(Equal(currentRec.Spec.RootHost))
g.Expect(newRec.UID).ToNot(Equal(currentRec.UID))
g.Expect(newRec.Status.Conditions).To(containReadyCondition)
g.Expect(newRec.Spec.Endpoints).To(ContainElement(newRecRootEndpoint))

newWildcardRec := &kuadrantdnsv1alpha1.DNSRecord{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, newWildcardRec)).To(Succeed())
g.Expect(newWildcardRec.Spec.RootHost).To(Equal(currentWildcardRec.Spec.RootHost))
g.Expect(newWildcardRec.UID).ToNot(Equal(currentWildcardRec.UID))
g.Expect(newWildcardRec.Status.Conditions).To(containReadyCondition)
g.Expect(newWildcardRec.Spec.Endpoints).To(ContainElement(newWildcardRecRootEndpoint))

//ToDo Add checks for policy affected by on gateway when possible, will require discoverability changes

currentRec = newRec
currentWildcardRec = newWildcardRec
}, tests.TimeoutLong, time.Second).Should(BeNil())

By("deleting the dnspolicy with section name")
Expect(k8sClient.Delete(ctx, dnsPolicyWithSection)).To(Succeed())

//listener 1 & 2 - Default gateway policy has a loadbalancing section so will create loadbalanced records with CNAME Records for the rootHost
newRecRootEndpoint = rootEndpointMatcher(tests.HostOne(domain), "CNAME", "", 300, "klb.test."+domain)
newWildcardRecRootEndpoint = rootEndpointMatcher(tests.HostWildcard(domain), "CNAME", "", 300, "klb."+domain)

//get the dnsrecord again and verify the DNSRecord resource is unchanged
//get the wildcard dnsrecord and verify it's no longer the same DNSRecord resource and the record type for the root host has changed
Eventually(func(g Gomega) {
newRec := &kuadrantdnsv1alpha1.DNSRecord{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, newRec)).To(Succeed())
g.Expect(newRec.Spec.RootHost).To(Equal(currentRec.Spec.RootHost))
g.Expect(newRec.UID).To(Equal(currentRec.UID))
g.Expect(newRec.Status.Conditions).To(containReadyCondition)
g.Expect(newRec.Spec.Endpoints).To(ContainElement(newRecRootEndpoint))

newWildcardRec := &kuadrantdnsv1alpha1.DNSRecord{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, newWildcardRec)).To(Succeed())
g.Expect(newWildcardRec.Spec.RootHost).To(Equal(currentWildcardRec.Spec.RootHost))
g.Expect(newWildcardRec.UID).ToNot(Equal(currentWildcardRec.UID))
g.Expect(newWildcardRec.Status.Conditions).To(containReadyCondition)
g.Expect(newWildcardRec.Spec.Endpoints).To(ContainElement(newWildcardRecRootEndpoint))

//ToDo Add checks for policy affected by on gateway when possible, will require discoverability changes

currentRec = newRec
currentWildcardRec = newWildcardRec
}, tests.TimeoutLong, time.Second).Should(BeNil())
})
})

})

Context("cel validation", func() {
Expand Down
2 changes: 1 addition & 1 deletion tests/common/targetstatus/target_status_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ var _ = Describe("Target status reconciler", func() {
policyAcceptedAndTargetsAffected := func(ctx context.Context, policy *kuadrantv1alpha1.DNSPolicy) func() bool {
return func() bool {
policyKey := client.ObjectKeyFromObject(policy)
return isDNSPolicyAccepted(ctx, policyKey) && targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef)
return isDNSPolicyAccepted(ctx, policyKey) && targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef.LocalPolicyTargetReference)
}
}

Expand Down

0 comments on commit bc3b91a

Please sign in to comment.