From e451f1bcc13ed54ae6fade7e0c613f5e2781981b Mon Sep 17 00:00:00 2001 From: Maskym Vavilov Date: Fri, 9 Aug 2024 14:30:41 +0100 Subject: [PATCH] used API to generate EPs and remove OCM Signed-off-by: Maskym Vavilov --- controllers/dns_helper.go | 205 +------ controllers/dnspolicy_dnsrecords.go | 64 +-- go.mod | 2 +- go.sum | 8 +- pkg/multicluster/gateway_wrapper.go | 203 ------- pkg/multicluster/target.go | 107 ---- ...nspolicy_controller_single_cluster_test.go | 422 -------------- .../dnspolicy/dnspolicy_controller_test.go | 520 ++++++++++-------- .../target_status_controller_test.go | 6 +- 9 files changed, 345 insertions(+), 1192 deletions(-) delete mode 100644 pkg/multicluster/gateway_wrapper.go delete mode 100644 pkg/multicluster/target.go delete mode 100644 tests/common/dnspolicy/dnspolicy_controller_single_cluster_test.go diff --git a/controllers/dns_helper.go b/controllers/dns_helper.go index ad9a63c12..a2aac7432 100644 --- a/controllers/dns_helper.go +++ b/controllers/dns_helper.go @@ -3,28 +3,21 @@ package controllers import ( "context" "fmt" - "sort" - "strconv" - "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - externaldns "sigs.k8s.io/external-dns/endpoint" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" + "github.com/kuadrant/dns-operator/pkg/builder" "github.com/kuadrant/kuadrant-operator/api/v1alpha1" - "github.com/kuadrant/kuadrant-operator/pkg/multicluster" ) const ( LabelGatewayReference = "kuadrant.io/gateway" LabelGatewayNSRef = "kuadrant.io/gateway-namespace" LabelListenerReference = "kuadrant.io/listener-name" - - DefaultTTL = 60 - DefaultCnameTTL = 300 ) type dnsHelper struct { @@ -56,182 +49,6 @@ func gatewayDNSRecordLabels(gwKey client.ObjectKey) map[string]string { } } -func (dh *dnsHelper) setEndpoints(mcgTarget *multicluster.GatewayTarget, dnsRecord *kuadrantdnsv1alpha1.DNSRecord, listener gatewayapiv1.Listener, loadBalancing *v1alpha1.LoadBalancingSpec) { - gwListenerHost := string(*listener.Hostname) - var endpoints []*externaldns.Endpoint - - //Health Checks currently modify endpoints, so we have to keep existing ones in order to not lose health check ids - currentEndpoints := make(map[string]*externaldns.Endpoint, len(dnsRecord.Spec.Endpoints)) - for _, endpoint := range dnsRecord.Spec.Endpoints { - currentEndpoints[getSetID(endpoint)] = endpoint - } - - if loadBalancing == nil { - endpoints = dh.getSimpleEndpoints(mcgTarget, gwListenerHost, currentEndpoints) - } else { - endpoints = dh.getLoadBalancedEndpoints(mcgTarget, gwListenerHost, currentEndpoints) - } - - sort.Slice(endpoints, func(i, j int) bool { - return getSetID(endpoints[i]) < getSetID(endpoints[j]) - }) - - dnsRecord.Spec.Endpoints = endpoints -} - -// getSimpleEndpoints returns the endpoints for the given GatewayTarget using the simple routing strategy - -func (dh *dnsHelper) getSimpleEndpoints(mcgTarget *multicluster.GatewayTarget, hostname string, currentEndpoints map[string]*externaldns.Endpoint) []*externaldns.Endpoint { - var ( - endpoints []*externaldns.Endpoint - ipValues []string - hostValues []string - ) - - for _, cgwTarget := range mcgTarget.ClusterGatewayTargets { - for _, gwa := range cgwTarget.Status.Addresses { - if *gwa.Type == gatewayapiv1.IPAddressType { - ipValues = append(ipValues, gwa.Value) - } else { - hostValues = append(hostValues, gwa.Value) - } - } - } - - if len(ipValues) > 0 { - endpoint := createOrUpdateEndpoint(hostname, ipValues, kuadrantdnsv1alpha1.ARecordType, "", DefaultTTL, currentEndpoints) - endpoints = append(endpoints, endpoint) - } - - //ToDO This could possibly result in an invalid record since you can't have multiple CNAME target values https://github.com/kuadrant/kuadrant-operator/issues/663 - if len(hostValues) > 0 { - endpoint := createOrUpdateEndpoint(hostname, hostValues, kuadrantdnsv1alpha1.CNAMERecordType, "", DefaultTTL, currentEndpoints) - endpoints = append(endpoints, endpoint) - } - - return endpoints -} - -// getLoadBalancedEndpoints returns the endpoints for the given GatewayTarget using the loadbalanced routing strategy -// -// Builds an array of externaldns.Endpoint resources and sets them on the given DNSRecord. The endpoints expected are calculated -// from the GatewayTarget using the target Gateway (GatewayTarget.Gateway), the LoadBalancing Spec -// from the DNSPolicy attached to the target gateway (GatewayTarget.LoadBalancing) and the list of clusters the -// target gateway is currently placed on (GatewayTarget.ClusterGatewayTargets). -// -// GatewayTarget.ClusterGatewayTarget are grouped by Geo, in the case of Geo not being defined in the -// LoadBalancing Spec (Weighted only) an internal only Geo Code of "default" is used and all clusters added to it. -// -// A CNAME record is created for the target host (DNSRecord.name), pointing to a generated gateway lb host. -// A CNAME record for the gateway lb host is created for every Geo, with appropriate Geo information, pointing to a geo -// specific host. -// A CNAME record for the geo specific host is created for every Geo, with weight information for that target added, -// pointing to a target cluster hostname. -// An A record for the target cluster hostname is created for any IP targets retrieved for that cluster. -// -// Example(Weighted only) -// -// www.example.com CNAME lb-1ab1.www.example.com -// lb-1ab1.www.example.com CNAME geolocation * default.lb-1ab1.www.example.com -// default.lb-1ab1.www.example.com CNAME weighted 100 1bc1.lb-1ab1.www.example.com -// default.lb-1ab1.www.example.com CNAME weighted 100 aws.lb.com -// 1bc1.lb-1ab1.www.example.com A 192.22.2.1 -// -// Example(Geo, default IE) -// -// shop.example.com CNAME lb-a1b2.shop.example.com -// lb-a1b2.shop.example.com CNAME geolocation ireland ie.lb-a1b2.shop.example.com -// lb-a1b2.shop.example.com geolocation australia aus.lb-a1b2.shop.example.com -// lb-a1b2.shop.example.com geolocation default ie.lb-a1b2.shop.example.com (set by the default geo option) -// ie.lb-a1b2.shop.example.com CNAME weighted 100 ab1.lb-a1b2.shop.example.com -// ie.lb-a1b2.shop.example.com CNAME weighted 100 aws.lb.com -// aus.lb-a1b2.shop.example.com CNAME weighted 100 ab2.lb-a1b2.shop.example.com -// aus.lb-a1b2.shop.example.com CNAME weighted 100 ab3.lb-a1b2.shop.example.com -// ab1.lb-a1b2.shop.example.com A 192.22.2.1 192.22.2.5 -// ab2.lb-a1b2.shop.example.com A 192.22.2.3 -// ab3.lb-a1b2.shop.example.com A 192.22.2.4 - -func (dh *dnsHelper) getLoadBalancedEndpoints(mcgTarget *multicluster.GatewayTarget, hostname string, currentEndpoints map[string]*externaldns.Endpoint) []*externaldns.Endpoint { - cnameHost := hostname - if isWildCardHost(hostname) { - cnameHost = strings.Replace(hostname, "*.", "", -1) - } - - var endpoint *externaldns.Endpoint - endpoints := make([]*externaldns.Endpoint, 0) - lbName := strings.ToLower(fmt.Sprintf("klb.%s", cnameHost)) - - for geoCode, cgwTargets := range mcgTarget.GroupTargetsByGeo() { - geoLbName := strings.ToLower(fmt.Sprintf("%s.%s", geoCode, lbName)) - var clusterEndpoints []*externaldns.Endpoint - for _, cgwTarget := range cgwTargets { - var ipValues []string - var hostValues []string - for _, gwa := range cgwTarget.Status.Addresses { - if *gwa.Type == gatewayapiv1.IPAddressType { - ipValues = append(ipValues, gwa.Value) - } else { - hostValues = append(hostValues, gwa.Value) - } - } - - if len(ipValues) > 0 { - clusterLbName := strings.ToLower(fmt.Sprintf("%s-%s.%s", cgwTarget.GetShortCode(), mcgTarget.GetShortCode(), lbName)) - endpoint = createOrUpdateEndpoint(clusterLbName, ipValues, kuadrantdnsv1alpha1.ARecordType, "", DefaultTTL, currentEndpoints) - clusterEndpoints = append(clusterEndpoints, endpoint) - hostValues = append(hostValues, clusterLbName) - } - - for _, hostValue := range hostValues { - endpoint = createOrUpdateEndpoint(geoLbName, []string{hostValue}, kuadrantdnsv1alpha1.CNAMERecordType, hostValue, DefaultTTL, currentEndpoints) - endpoint.SetProviderSpecificProperty(kuadrantdnsv1alpha1.ProviderSpecificWeight, strconv.Itoa(cgwTarget.GetWeight())) - clusterEndpoints = append(clusterEndpoints, endpoint) - } - } - if len(clusterEndpoints) == 0 { - continue - } - endpoints = append(endpoints, clusterEndpoints...) - - //Create lbName CNAME (lb-a1b2.shop.example.com -> .lb-a1b2.shop.example.com) - endpoint = createOrUpdateEndpoint(lbName, []string{geoLbName}, kuadrantdnsv1alpha1.CNAMERecordType, string(geoCode), DefaultCnameTTL, currentEndpoints) - endpoint.SetProviderSpecificProperty(kuadrantdnsv1alpha1.ProviderSpecificGeoCode, string(geoCode)) - endpoints = append(endpoints, endpoint) - - //Add a default geo (*) endpoint if the current target is the default geo - if mcgTarget.IsDefaultGeo() { - endpoint = createOrUpdateEndpoint(lbName, []string{geoLbName}, kuadrantdnsv1alpha1.CNAMERecordType, "default", DefaultCnameTTL, currentEndpoints) - endpoint.SetProviderSpecificProperty(kuadrantdnsv1alpha1.ProviderSpecificGeoCode, string(v1alpha1.WildcardGeo)) - endpoints = append(endpoints, endpoint) - } - } - - if len(endpoints) > 0 { - //Create gwListenerHost CNAME (shop.example.com -> lb-a1b2.shop.example.com) - endpoint = createOrUpdateEndpoint(hostname, []string{lbName}, kuadrantdnsv1alpha1.CNAMERecordType, "", DefaultCnameTTL, currentEndpoints) - endpoints = append(endpoints, endpoint) - } - - return endpoints -} - -func createOrUpdateEndpoint(dnsName string, targets externaldns.Targets, recordType kuadrantdnsv1alpha1.DNSRecordType, setIdentifier string, - recordTTL externaldns.TTL, currentEndpoints map[string]*externaldns.Endpoint) (endpoint *externaldns.Endpoint) { - ok := false - endpointID := dnsName + setIdentifier - if endpoint, ok = currentEndpoints[endpointID]; !ok { - endpoint = &externaldns.Endpoint{} - if setIdentifier != "" { - endpoint.SetIdentifier = setIdentifier - } - } - endpoint.DNSName = dnsName - endpoint.RecordType = string(recordType) - endpoint.Targets = targets - endpoint.RecordTTL = recordTTL - return endpoint -} - // removeDNSForDeletedListeners remove any DNSRecords that are associated with listeners that no longer exist in this gateway func (dh *dnsHelper) removeDNSForDeletedListeners(ctx context.Context, upstreamGateway *gatewayapiv1.Gateway) error { dnsList := &kuadrantdnsv1alpha1.DNSRecordList{} @@ -277,10 +94,22 @@ func (dh *dnsHelper) deleteDNSRecordForListener(ctx context.Context, owner metav return dh.Delete(ctx, &dnsRecord, &client.DeleteOptions{}) } -func isWildCardHost(host string) bool { - return strings.HasPrefix(host, "*") +// GatewayWrapper is a wrapper for gateway to implement interface form the builder +type GatewayWrapper struct { + *gatewayapiv1.Gateway +} + +func NewGatewayWrapper(gateway *gatewayapiv1.Gateway) *GatewayWrapper { + return &GatewayWrapper{Gateway: gateway} } -func getSetID(endpoint *externaldns.Endpoint) string { - return endpoint.DNSName + endpoint.SetIdentifier +func (g GatewayWrapper) GetAddresses() []builder.TargetAddress { + addresses := make([]builder.TargetAddress, len(g.Status.Addresses)) + for i, address := range g.Status.Addresses { + addresses[i] = builder.TargetAddress{ + Type: builder.AddressType(*address.Type), + Value: address.Value, + } + } + return addresses } diff --git a/controllers/dnspolicy_dnsrecords.go b/controllers/dnspolicy_dnsrecords.go index 4b5193c76..628b8918a 100644 --- a/controllers/dnspolicy_dnsrecords.go +++ b/controllers/dnspolicy_dnsrecords.go @@ -10,14 +10,15 @@ import ( "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" crlog "sigs.k8s.io/controller-runtime/pkg/log" + externaldns "sigs.k8s.io/external-dns/endpoint" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" + "github.com/kuadrant/dns-operator/pkg/builder" "github.com/kuadrant/kuadrant-operator/api/v1alpha1" reconcilerutils "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" "github.com/kuadrant/kuadrant-operator/pkg/library/utils" - "github.com/kuadrant/kuadrant-operator/pkg/multicluster" ) func (r *DNSPolicyReconciler) reconcileDNSRecords(ctx context.Context, dnsPolicy *v1alpha1.DNSPolicy, gwDiffObj *reconcilerutils.GatewayDiffs) error { @@ -41,54 +42,43 @@ func (r *DNSPolicyReconciler) reconcileDNSRecords(ctx context.Context, dnsPolicy return nil } -func (r *DNSPolicyReconciler) reconcileGatewayDNSRecords(ctx context.Context, gw *gatewayapiv1.Gateway, dnsPolicy *v1alpha1.DNSPolicy) error { +func (r *DNSPolicyReconciler) reconcileGatewayDNSRecords(ctx context.Context, gateway *gatewayapiv1.Gateway, dnsPolicy *v1alpha1.DNSPolicy) error { log := crlog.FromContext(ctx) clusterID, err := utils.GetClusterUID(ctx, r.Client()) if err != nil { return fmt.Errorf("failed to generate cluster ID: %w", err) } - gatewayWrapper := multicluster.NewGatewayWrapper(gw, clusterID) - if err := gatewayWrapper.Validate(); err != nil { - return err - } - if err := r.dnsHelper.removeDNSForDeletedListeners(ctx, gatewayWrapper.Gateway); err != nil { + if err = r.dnsHelper.removeDNSForDeletedListeners(ctx, gateway); err != nil { log.V(3).Info("error removing DNS for deleted listeners") return err } - clusterGateways := gatewayWrapper.GetClusterGateways() - - log.V(3).Info("checking gateway for attached routes ", "gateway", gatewayWrapper.Name, "clusterGateways", clusterGateways) + log.V(3).Info("checking gateway for attached routes ", "gateway", gateway.Name) - for _, listener := range gatewayWrapper.Spec.Listeners { + for _, listener := range gateway.Spec.Listeners { listenerHost := *listener.Hostname if listenerHost == "" { - log.Info("skipping listener no hostname assigned", listener.Name, "in ns ", gatewayWrapper.Namespace) + log.Info("skipping listener no hostname assigned", listener.Name, "in ns ", gateway.Namespace) continue } - - listenerGateways := utils.Filter(clusterGateways, func(cgw multicluster.ClusterGateway) bool { - hasAttachedRoute := false - for _, statusListener := range cgw.Status.Listeners { - if string(statusListener.Name) == string(listener.Name) { - hasAttachedRoute = int(statusListener.AttachedRoutes) > 0 - break - } + hasAttachedRoute := false + for _, statusListener := range gateway.Status.Listeners { + if string(listener.Name) == string(statusListener.Name) { + hasAttachedRoute = statusListener.AttachedRoutes > 0 } - return hasAttachedRoute - }) + } - if len(listenerGateways) == 0 { + if !hasAttachedRoute { // delete record log.V(1).Info("no cluster gateways, deleting DNS record", " for listener ", listener.Name) - if err := r.dnsHelper.deleteDNSRecordForListener(ctx, gatewayWrapper, listener); client.IgnoreNotFound(err) != nil { + if err := r.dnsHelper.deleteDNSRecordForListener(ctx, gateway, listener); client.IgnoreNotFound(err) != nil { return fmt.Errorf("failed to delete dns record for listener %s : %w", listener.Name, err) } continue } - dnsRecord, err := r.desiredDNSRecord(gatewayWrapper, dnsPolicy, listener, listenerGateways) + dnsRecord, err := r.desiredDNSRecord(gateway, clusterID, dnsPolicy, listener) if err != nil { return err } @@ -107,7 +97,7 @@ func (r *DNSPolicyReconciler) reconcileGatewayDNSRecords(ctx context.Context, gw return nil } -func (r *DNSPolicyReconciler) desiredDNSRecord(gateway *multicluster.GatewayWrapper, dnsPolicy *v1alpha1.DNSPolicy, targetListener gatewayapiv1.Listener, clusterGateways []multicluster.ClusterGateway) (*kuadrantdnsv1alpha1.DNSRecord, error) { +func (r *DNSPolicyReconciler) desiredDNSRecord(gateway *gatewayapiv1.Gateway, clusterID string, dnsPolicy *v1alpha1.DNSPolicy, targetListener gatewayapiv1.Listener) (*kuadrantdnsv1alpha1.DNSRecord, error) { rootHost := string(*targetListener.Hostname) var healthCheckSpec *kuadrantdnsv1alpha1.HealthCheckSpec @@ -136,13 +126,11 @@ func (r *DNSPolicyReconciler) desiredDNSRecord(gateway *multicluster.GatewayWrap } dnsRecord.Labels[LabelListenerReference] = string(targetListener.Name) - mcgTarget, err := multicluster.NewGatewayTarget(gateway.Gateway, clusterGateways, dnsPolicy.Spec.LoadBalancing) + endpoints, err := buildEndpoints(clusterID, string(*targetListener.Hostname), gateway, dnsPolicy) if err != nil { - return nil, fmt.Errorf("failed to create multi cluster gateway target for listener %s : %w", targetListener.Name, err) + return nil, fmt.Errorf("failed to generate dns record for a gateway %s in %s ns: %w", gateway.Name, gateway.Namespace, err) } - - r.dnsHelper.setEndpoints(mcgTarget, dnsRecord, targetListener, dnsPolicy.Spec.LoadBalancing) - + dnsRecord.Spec.Endpoints = endpoints return dnsRecord, nil } @@ -190,3 +178,17 @@ func dnsRecordBasicMutator(existingObj, desiredObj client.Object) (bool, error) return true, nil } + +func buildEndpoints(clusterID, hostname string, gateway *gatewayapiv1.Gateway, policy *v1alpha1.DNSPolicy) ([]*externaldns.Endpoint, error) { + endpointBuilder := builder.NewEndpointsBuilder(NewGatewayWrapper(gateway), hostname) + + if policy.Spec.LoadBalancing != nil { + endpointBuilder.WithLoadBalancingFor( + clusterID, + policy.Spec.LoadBalancing.Weight, + policy.Spec.LoadBalancing.Geo, + policy.Spec.LoadBalancing.DefaultGeo) + } + + return endpointBuilder.Build() +} diff --git a/go.mod b/go.mod index 41a95aef0..3db9e8f7e 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/google/uuid v1.6.0 github.com/kuadrant/authorino v0.17.2 github.com/kuadrant/authorino-operator v0.11.1 - github.com/kuadrant/dns-operator v0.0.0-20240809151102-e79ebbca8f70 + github.com/kuadrant/dns-operator v0.0.0-20240926100317-2e2497411ab3 github.com/kuadrant/limitador-operator v0.9.0 github.com/kuadrant/policy-machinery v0.2.0 github.com/martinlindhe/base36 v1.1.1 diff --git a/go.sum b/go.sum index 9308c8244..53d1bcae1 100644 --- a/go.sum +++ b/go.sum @@ -247,12 +247,10 @@ github.com/kuadrant/authorino v0.17.2 h1:UgWH4NY/n36IhoaU+ELUkoujaly1/9sx5mHY5vU github.com/kuadrant/authorino v0.17.2/go.mod h1:al71fN0FX6c9Orrhk9GR4CtjtC+CD/lUHJCs7drlRNM= github.com/kuadrant/authorino-operator v0.11.1 h1:jndTZhiHMU+2Dk0NU+KP2+MUSfvclrn+YtTCQDJj+1s= github.com/kuadrant/authorino-operator v0.11.1/go.mod h1:TeFFdX477vUTMushCojaHpvwPLga4DpErGI2oQbqFIs= -github.com/kuadrant/dns-operator v0.0.0-20240809151102-e79ebbca8f70 h1:Jiq7dZWaepPZAVrG3QsDfVAIyR3qdgTdqN5v2lTvO8k= -github.com/kuadrant/dns-operator v0.0.0-20240809151102-e79ebbca8f70/go.mod h1:Aq4LYFwhBzQYUew71KjtWPKr+e0jzgraX10Ki8wIKCY= +github.com/kuadrant/dns-operator v0.0.0-20240926100317-2e2497411ab3 h1:r5Ed62AetTJhbJGEinM/G7ugdxV6Kp/kcVIpxOVxduM= +github.com/kuadrant/dns-operator v0.0.0-20240926100317-2e2497411ab3/go.mod h1:IHAt2o/VH1c0GIZTprggUDZuxoH0I304R9DUErBNIhk= github.com/kuadrant/limitador-operator v0.9.0 h1:hTQ6CFPayf/sL7cIzwWjCoU8uTn6fzWdsJgKbDlnFts= github.com/kuadrant/limitador-operator v0.9.0/go.mod h1:DQOlg9qFOcnWPrwO529JRCMLLOEXJQxkmOes952S/Hw= -github.com/kuadrant/policy-machinery v0.1.1 h1:8NPwL5U79Y+amCpJpyAJBUqWfntfDrce0JK31ueRMns= -github.com/kuadrant/policy-machinery v0.1.1/go.mod h1:fbBqBlh7iyFdU6dRCzaPadypaI/BSHWKiUVl5kesrYY= github.com/kuadrant/policy-machinery v0.2.0 h1:6kACb+bdEwHXz2tvTs6dlLgvxFgFrowvGTZKMI9p0Qo= github.com/kuadrant/policy-machinery v0.2.0/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -526,8 +524,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/pkg/multicluster/gateway_wrapper.go b/pkg/multicluster/gateway_wrapper.go deleted file mode 100644 index 91326611f..000000000 --- a/pkg/multicluster/gateway_wrapper.go +++ /dev/null @@ -1,203 +0,0 @@ -package multicluster - -import ( - "fmt" - "strings" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" -) - -const ( - LabelPrefix = "kuadrant.io/" - ClustersLabelPrefix = "clusters." + LabelPrefix - MultiClusterIPAddressType gatewayapiv1.AddressType = LabelPrefix + "MultiClusterIPAddress" - MultiClusterHostnameAddressType gatewayapiv1.AddressType = LabelPrefix + "MultiClusterHostnameAddress" -) - -type GatewayWrapper struct { - *gatewayapiv1.Gateway - ClusterID string -} - -func NewGatewayWrapper(g *gatewayapiv1.Gateway, clusterID string) *GatewayWrapper { - return &GatewayWrapper{Gateway: g, ClusterID: clusterID} -} - -func isMultiClusterAddressType(addressType gatewayapiv1.AddressType) bool { - return addressType == MultiClusterIPAddressType || addressType == MultiClusterHostnameAddressType -} - -// IsMultiCluster reports a type of the first address in the Status block -// returns false if no addresses present -func (g *GatewayWrapper) IsMultiCluster() bool { - if len(g.Status.Addresses) > 0 { - return isMultiClusterAddressType(*g.Status.Addresses[0].Type) - } - return false -} - -// Validate ensures correctly configured underlying Gateway object -// Returns nil if validation passed -func (g *GatewayWrapper) Validate() error { - // Status.Addresses validation - // Compares all addresses against the first address to ensure the same type - for _, address := range g.Status.Addresses { - if g.IsMultiCluster() == isMultiClusterAddressType(*address.Type) { - continue - } - return fmt.Errorf("gateway is invalid: inconsistent status addresses") - } - return nil -} - -// GetClusterGatewayAddresses constructs a map from Status.Addresses of underlying Gateway -// with key being a cluster and value being an address in the cluster. -// In case of a single-cluster Gateway the key is the Gateway Name. -func (g *GatewayWrapper) GetClusterGatewayAddresses() map[string][]gatewayapiv1.GatewayStatusAddress { - if !g.IsMultiCluster() { - // Single Cluster (Normal Gateway Status) - return map[string][]gatewayapiv1.GatewayStatusAddress{g.GetName(): g.Status.Addresses} - } - - // Multi Cluster (MGC Gateway Status) - clusterAddrs := map[string][]gatewayapiv1.GatewayStatusAddress{} - for _, address := range g.Status.Addresses { - cluster, addressValue, found := strings.Cut(address.Value, "/") - //If this fails something is wrong and the value hasn't been set correctly - if !found { - continue - } - - if _, ok := clusterAddrs[cluster]; !ok { - clusterAddrs[cluster] = []gatewayapiv1.GatewayStatusAddress{} - } - - addressType, _ := AddressTypeToSingleCluster(gatewayapiv1.GatewayAddress(address)) - clusterAddrs[cluster] = append(clusterAddrs[cluster], gatewayapiv1.GatewayStatusAddress{ - Type: &addressType, - Value: addressValue, - }) - } - - return clusterAddrs -} - -// GetClusterGatewayLabels parses the labels of the wrapped Gateway and returns a list of labels for the given clusterName. -// In case of a single-cluster Gateway the wrapped Gateways labels are returned unmodified. -func (g *GatewayWrapper) GetClusterGatewayLabels(clusterName string) map[string]string { - if !g.IsMultiCluster() { - // Single Cluster (Normal Gateway Status) - return g.GetLabels() - } - - labels := map[string]string{} - for k, v := range g.GetLabels() { - if strings.HasPrefix(k, ClustersLabelPrefix) { - attr, found := strings.CutPrefix(k, ClustersLabelPrefix+clusterName+"_") - if found { - labels[LabelPrefix+attr] = v - } - continue - } - // Only add a label if we haven't already found a cluster specific version of it - if _, ok := labels[k]; !ok { - labels[k] = v - } - } - return labels -} - -// GetClusterGatewayListeners processes the wrapped Gateway and returns a ListenerStatus for the given clusterName. -// In case of a single-cluster Gateway the wrapped Gateways status listeners are returned unmodified. -func (g *GatewayWrapper) GetClusterGatewayListeners(clusterName string) []gatewayapiv1.ListenerStatus { - if !g.IsMultiCluster() { - // Single Cluster (Normal Gateway Status) - return g.Status.Listeners - } - - // Multi Cluster (MGC Gateway Status) - listeners := []gatewayapiv1.ListenerStatus{} - for _, specListener := range g.Spec.Listeners { - for _, statusListener := range g.Status.Listeners { - statusClusterName, statusListenerName, found := strings.Cut(string(statusListener.Name), ".") - if !found { - continue - } - if statusClusterName == clusterName && statusListenerName == string(specListener.Name) { - ls := gatewayapiv1.ListenerStatus{ - Name: specListener.Name, - AttachedRoutes: statusListener.AttachedRoutes, - } - listeners = append(listeners, ls) - } - } - } - return listeners -} - -// ClusterGateway contains a Gateway as it would be on a single cluster and the name of the cluster. -type ClusterGateway struct { - gatewayapiv1.Gateway - ClusterName string -} - -// GetClusterGateways parse the wrapped Gateway and returns a list of ClusterGateway resources. -// In case of a single-cluster Gateway a single ClusterGateway is returned with the unmodified wrapped Gateway and the -// Gateway name used as values. -func (g *GatewayWrapper) GetClusterGateways() []ClusterGateway { - if !g.IsMultiCluster() { - // Single Cluster (Normal Gateway Status) - return []ClusterGateway{ - { - Gateway: *g.Gateway, - ClusterName: g.ClusterID, - }, - } - } - - // Multi Cluster (MGC Gateway Status) - clusterAddrs := g.GetClusterGatewayAddresses() - clusterGateways := []ClusterGateway{} - for clusterName, addrs := range clusterAddrs { - gw := gatewayapiv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: g.GetName(), - Namespace: g.GetNamespace(), - Labels: g.GetClusterGatewayLabels(clusterName), - }, - Spec: g.Spec, - Status: gatewayapiv1.GatewayStatus{ - Addresses: addrs, - Listeners: g.GetClusterGatewayListeners(clusterName), - }, - } - clusterGateways = append(clusterGateways, ClusterGateway{ - Gateway: gw, - ClusterName: clusterName, - }) - } - return clusterGateways -} - -// AddressTypeToMultiCluster returns a multi cluster version of the address type -// and a bool to indicate that provided address type was converted. If not - original type is returned -func AddressTypeToMultiCluster(address gatewayapiv1.GatewayAddress) (gatewayapiv1.AddressType, bool) { - if *address.Type == gatewayapiv1.IPAddressType { - return MultiClusterIPAddressType, true - } else if *address.Type == gatewayapiv1.HostnameAddressType { - return MultiClusterHostnameAddressType, true - } - return *address.Type, false -} - -// AddressTypeToSingleCluster converts provided multicluster address to single cluster version -// the bool indicates a successful conversion -func AddressTypeToSingleCluster(address gatewayapiv1.GatewayAddress) (gatewayapiv1.AddressType, bool) { - if *address.Type == MultiClusterIPAddressType { - return gatewayapiv1.IPAddressType, true - } else if *address.Type == MultiClusterHostnameAddressType { - return gatewayapiv1.HostnameAddressType, true - } - return *address.Type, false -} diff --git a/pkg/multicluster/target.go b/pkg/multicluster/target.go deleted file mode 100644 index 6496fc1eb..000000000 --- a/pkg/multicluster/target.go +++ /dev/null @@ -1,107 +0,0 @@ -package multicluster - -import ( - "fmt" - - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - "github.com/kuadrant/kuadrant-operator/api/v1alpha1" - "github.com/kuadrant/kuadrant-operator/pkg/common" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" -) - -// GatewayTarget represents a Gateway that is placed on multiple clusters (ClusterGateway). -type GatewayTarget struct { - Gateway *gatewayapiv1.Gateway - ClusterGatewayTargets []ClusterGatewayTarget - LoadBalancing *v1alpha1.LoadBalancingSpec -} - -func NewGatewayTarget(gateway *gatewayapiv1.Gateway, clusterGateways []ClusterGateway, loadBalancing *v1alpha1.LoadBalancingSpec) (*GatewayTarget, error) { - mcg := &GatewayTarget{Gateway: gateway, LoadBalancing: loadBalancing} - err := mcg.setClusterGatewayTargets(clusterGateways) - return mcg, err -} - -func (t *GatewayTarget) GetName() string { - return fmt.Sprintf("%s-%s", t.Gateway.Name, t.Gateway.Namespace) -} - -func (t *GatewayTarget) GetShortCode() string { - return common.ToBase36HashLen(t.GetName(), utils.ClusterIDLength) -} - -// GroupTargetsByGeo groups targets based on Geo Code. -func (t *GatewayTarget) GroupTargetsByGeo() map[v1alpha1.GeoCode][]ClusterGatewayTarget { - geoTargets := make(map[v1alpha1.GeoCode][]ClusterGatewayTarget) - for _, target := range t.ClusterGatewayTargets { - geoTargets[target.GetGeo()] = append(geoTargets[target.GetGeo()], target) - } - return geoTargets -} - -func (t *GatewayTarget) GetGeo() v1alpha1.GeoCode { - if t.LoadBalancing != nil { - return v1alpha1.GeoCode(t.LoadBalancing.Geo) - } - return v1alpha1.DefaultGeo -} - -func (t *GatewayTarget) IsDefaultGeo() bool { - if t.LoadBalancing != nil { - return t.LoadBalancing.DefaultGeo - } - return false -} - -func (t *GatewayTarget) GetWeight() int { - if t.LoadBalancing != nil { - return t.LoadBalancing.Weight - } - return v1alpha1.DefaultWeight -} - -func (t *GatewayTarget) setClusterGatewayTargets(clusterGateways []ClusterGateway) error { - cgTargets := []ClusterGatewayTarget{} - for _, cg := range clusterGateways { - cgt, err := NewClusterGatewayTarget(cg, t.GetGeo(), t.GetWeight()) - if err != nil { - return err - } - cgTargets = append(cgTargets, cgt) - } - t.ClusterGatewayTargets = cgTargets - return nil -} - -// ClusterGatewayTarget represents a cluster Gateway with geo and weighting info calculated -type ClusterGatewayTarget struct { - *ClusterGateway - Geo v1alpha1.GeoCode - Weight int -} - -func NewClusterGatewayTarget(cg ClusterGateway, geoCode v1alpha1.GeoCode, weight int) (ClusterGatewayTarget, error) { - target := ClusterGatewayTarget{ - ClusterGateway: &cg, - Geo: geoCode, - Weight: weight, - } - return target, nil -} - -func (t *ClusterGatewayTarget) GetGeo() v1alpha1.GeoCode { - return t.Geo -} - -func (t *ClusterGatewayTarget) GetWeight() int { - return t.Weight -} - -func (t *ClusterGatewayTarget) GetName() string { - return t.ClusterName -} - -func (t *ClusterGatewayTarget) GetShortCode() string { - return common.ToBase36HashLen(t.GetName(), utils.ClusterIDLength) -} diff --git a/tests/common/dnspolicy/dnspolicy_controller_single_cluster_test.go b/tests/common/dnspolicy/dnspolicy_controller_single_cluster_test.go deleted file mode 100644 index 9f3d8717c..000000000 --- a/tests/common/dnspolicy/dnspolicy_controller_single_cluster_test.go +++ /dev/null @@ -1,422 +0,0 @@ -//go:build integration - -package dnspolicy - -import ( - "context" - "fmt" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - "k8s.io/apimachinery/pkg/util/rand" - externaldns "sigs.k8s.io/external-dns/endpoint" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" - - "github.com/kuadrant/kuadrant-operator/api/v1alpha1" - "github.com/kuadrant/kuadrant-operator/pkg/common" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" - "github.com/kuadrant/kuadrant-operator/tests" -) - -var _ = Describe("DNSPolicy Single Cluster", func() { - const ( - testTimeOut = SpecTimeout(1 * time.Minute) - afterEachTimeOut = NodeTimeout(2 * time.Minute) - ) - - var gatewayClass *gatewayapiv1.GatewayClass - var dnsProviderSecret *corev1.Secret - var testNamespace string - var gateway *gatewayapiv1.Gateway - var dnsPolicy *v1alpha1.DNSPolicy - var clusterHash, gwHash, recordName, wildcardRecordName string - var domain = fmt.Sprintf("example-%s.com", rand.String(6)) - - BeforeEach(func(ctx SpecContext) { - testNamespace = tests.CreateNamespace(ctx, testClient()) - - var err error - clusterUID, err := utils.GetClusterUID(ctx, k8sClient) - Expect(err).To(BeNil()) - - gatewayClass = tests.BuildGatewayClass("gwc-"+testNamespace, "default", "kuadrant.io/bar") - Expect(k8sClient.Create(ctx, gatewayClass)).To(Succeed()) - - dnsProviderSecret = tests.BuildInMemoryCredentialsSecret("inmemory-credentials", testNamespace, domain) - Expect(k8sClient.Create(ctx, dnsProviderSecret)).To(Succeed()) - - gateway = tests.NewGatewayBuilder(tests.GatewayName, gatewayClass.Name, testNamespace). - WithHTTPListener("foo", fmt.Sprintf("foo.%s", domain)). - WithHTTPListener(tests.ListenerNameOne, tests.HostOne(domain)). - WithHTTPListener(tests.ListenerNameWildcard, tests.HostWildcard(domain)). - Gateway - Expect(k8sClient.Create(ctx, gateway)).To(Succeed()) - - clusterHash = common.ToBase36HashLen(clusterUID, utils.ClusterIDLength) - - gwHash = common.ToBase36HashLen(gateway.Name+"-"+gateway.Namespace, 6) - - // refresh gateway - Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway)).To(Succeed()) - //Set single cluster gateway status - Eventually(func(g Gomega) { - g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway)).To(Succeed()) - gateway.Status.Addresses = []gatewayapiv1.GatewayStatusAddress{ - { - Type: ptr.To(gatewayapiv1.IPAddressType), - Value: tests.IPAddressOne, - }, - { - Type: ptr.To(gatewayapiv1.IPAddressType), - Value: tests.IPAddressTwo, - }, - } - gateway.Status.Listeners = []gatewayapiv1.ListenerStatus{ - { - Name: "foo", - SupportedKinds: []gatewayapiv1.RouteGroupKind{}, - AttachedRoutes: 0, - Conditions: []metav1.Condition{}, - }, - { - Name: tests.ListenerNameOne, - SupportedKinds: []gatewayapiv1.RouteGroupKind{}, - AttachedRoutes: 1, - Conditions: []metav1.Condition{}, - }, - { - Name: tests.ListenerNameWildcard, - SupportedKinds: []gatewayapiv1.RouteGroupKind{}, - AttachedRoutes: 1, - Conditions: []metav1.Condition{}, - }, - } - g.Expect(k8sClient.Status().Update(ctx, gateway)).To(Succeed()) - }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) - - recordName = fmt.Sprintf("%s-%s", tests.GatewayName, tests.ListenerNameOne) - wildcardRecordName = fmt.Sprintf("%s-%s", tests.GatewayName, tests.ListenerNameWildcard) - }) - - AfterEach(func(ctx SpecContext) { - if gateway != nil { - err := k8sClient.Delete(ctx, gateway) - Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) - } - if dnsPolicy != nil { - err := k8sClient.Delete(ctx, dnsPolicy) - Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) - // Wait until dns records are finished deleting since it can't finish deleting without the DNS provider secret - Eventually(func(g Gomega) { - dnsRecords := &kuadrantdnsv1alpha1.DNSRecordList{} - err := k8sClient.List(ctx, dnsRecords, client.InNamespace(testNamespace)) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(dnsRecords.Items).To(HaveLen(0)) - }).WithContext(ctx).Should(Succeed()) - - } - if dnsProviderSecret != nil { - err := k8sClient.Delete(ctx, dnsProviderSecret) - Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) - } - if gatewayClass != nil { - err := k8sClient.Delete(ctx, gatewayClass) - Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) - } - tests.DeleteNamespace(ctx, testClient(), testNamespace) - }, afterEachTimeOut) - - Context("simple routing strategy", func() { - - BeforeEach(func(ctx SpecContext) { - dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). - WithProviderSecret(*dnsProviderSecret). - WithTargetGateway(tests.GatewayName) - Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) - }) - - It("should create dns records", func(ctx SpecContext) { - Eventually(func(g Gomega, ctx context.Context) { - recordList := &kuadrantdnsv1alpha1.DNSRecordList{} - err := k8sClient.List(ctx, recordList, &client.ListOptions{Namespace: testNamespace}) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(recordList.Items).To(HaveLen(2)) - - dnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} - err = k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, dnsRecord) - g.Expect(err).NotTo(HaveOccurred()) - - wildcardDnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} - err = k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, wildcardDnsRecord) - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(dnsRecord.Name).To(Equal(recordName)) - g.Expect(dnsRecord.Spec.ProviderRef.Name).To(Equal(dnsProviderSecret.Name)) - g.Expect(dnsRecord.Spec.Endpoints).To(ConsistOf( - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(tests.HostOne(domain)), - "Targets": ContainElements(tests.IPAddressOne, tests.IPAddressTwo), - "RecordType": Equal("A"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(60)), - })), - )) - g.Expect(dnsRecord.Status.OwnerID).ToNot(BeEmpty()) - g.Expect(dnsRecord.Status.OwnerID).To(Equal(dnsRecord.GetUIDHash())) - g.Expect(tests.EndpointsTraversable(dnsRecord.Spec.Endpoints, tests.HostOne(domain), []string{tests.IPAddressOne, tests.IPAddressTwo})).To(BeTrue()) - - g.Expect(wildcardDnsRecord.Name).To(Equal(wildcardRecordName)) - g.Expect(wildcardDnsRecord.Spec.ProviderRef.Name).To(Equal(dnsProviderSecret.Name)) - g.Expect(wildcardDnsRecord.Spec.Endpoints).To(ConsistOf( - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(tests.HostWildcard(domain)), - "Targets": ContainElements(tests.IPAddressOne, tests.IPAddressTwo), - "RecordType": Equal("A"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(60)), - })), - )) - g.Expect(wildcardDnsRecord.Status.OwnerID).ToNot(BeEmpty()) - g.Expect(wildcardDnsRecord.Status.OwnerID).To(Equal(wildcardDnsRecord.GetUIDHash())) - g.Expect(tests.EndpointsTraversable(wildcardDnsRecord.Spec.Endpoints, tests.HostWildcard(domain), []string{tests.IPAddressOne, tests.IPAddressTwo})).To(BeTrue()) - }, tests.TimeoutMedium, tests.RetryIntervalMedium, ctx).Should(Succeed()) - }, testTimeOut) - - }) - - Context("loadbalanced routing strategy", func() { - - Context("with default geo", func() { - BeforeEach(func(ctx SpecContext) { - dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). - WithProviderSecret(*dnsProviderSecret). - WithTargetGateway(tests.GatewayName). - WithLoadBalancingFor(120, "IE", true) - Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) - }) - - It("should create dns records", func(ctx SpecContext) { - Eventually(func(g Gomega, ctx context.Context) { - recordList := &kuadrantdnsv1alpha1.DNSRecordList{} - err := k8sClient.List(ctx, recordList, &client.ListOptions{Namespace: testNamespace}) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(recordList.Items).To(HaveLen(2)) - - dnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} - err = k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, dnsRecord) - g.Expect(err).NotTo(HaveOccurred()) - - wildcardDnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} - err = k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, wildcardDnsRecord) - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(dnsRecord.Name).To(Equal(recordName)) - g.Expect(dnsRecord.Spec.ProviderRef.Name).To(Equal(dnsProviderSecret.Name)) - g.Expect(dnsRecord.Spec.Endpoints).To(ConsistOf( - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(clusterHash + "-" + gwHash + "." + "klb.test." + domain), - "Targets": ConsistOf(tests.IPAddressOne, tests.IPAddressTwo), - "RecordType": Equal("A"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(60)), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("ie.klb.test." + domain), - "Targets": ConsistOf(clusterHash + "-" + gwHash + "." + "klb.test." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(clusterHash + "-" + gwHash + "." + "klb.test." + domain), - "RecordTTL": Equal(externaldns.TTL(60)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "weight", Value: "120"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("klb.test." + domain), - "Targets": ConsistOf("ie.klb.test." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal("IE"), - "RecordTTL": Equal(externaldns.TTL(300)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "geo-code", Value: "IE"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("klb.test." + domain), - "Targets": ConsistOf("ie.klb.test." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal("default"), - "RecordTTL": Equal(externaldns.TTL(300)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "geo-code", Value: "*"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(tests.HostOne(domain)), - "Targets": ConsistOf("klb.test." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(300)), - })), - )) - g.Expect(dnsRecord.Status.OwnerID).ToNot(BeEmpty()) - g.Expect(dnsRecord.Status.OwnerID).To(Equal(dnsRecord.GetUIDHash())) - g.Expect(tests.EndpointsTraversable(dnsRecord.Spec.Endpoints, tests.HostOne(domain), []string{tests.IPAddressOne, tests.IPAddressTwo})).To(BeTrue()) - - g.Expect(wildcardDnsRecord.Name).To(Equal(wildcardRecordName)) - g.Expect(wildcardDnsRecord.Spec.ProviderRef.Name).To(Equal(dnsProviderSecret.Name)) - g.Expect(wildcardDnsRecord.Spec.Endpoints).To(ContainElements( - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(clusterHash + "-" + gwHash + "." + "klb." + domain), - "Targets": ConsistOf(tests.IPAddressOne, tests.IPAddressTwo), - "RecordType": Equal("A"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(60)), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("ie.klb." + domain), - "Targets": ConsistOf(clusterHash + "-" + gwHash + "." + "klb." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(clusterHash + "-" + gwHash + "." + "klb." + domain), - "RecordTTL": Equal(externaldns.TTL(60)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "weight", Value: "120"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("klb." + domain), - "Targets": ConsistOf("ie.klb." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal("default"), - "RecordTTL": Equal(externaldns.TTL(300)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "geo-code", Value: "*"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("klb." + domain), - "Targets": ConsistOf("ie.klb." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal("IE"), - "RecordTTL": Equal(externaldns.TTL(300)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "geo-code", Value: "IE"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(tests.HostWildcard(domain)), - "Targets": ConsistOf("klb." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(300)), - })), - )) - g.Expect(wildcardDnsRecord.Status.OwnerID).ToNot(BeEmpty()) - g.Expect(wildcardDnsRecord.Status.OwnerID).To(Equal(wildcardDnsRecord.GetUIDHash())) - g.Expect(tests.EndpointsTraversable(wildcardDnsRecord.Spec.Endpoints, tests.HostWildcard(domain), []string{tests.IPAddressOne, tests.IPAddressTwo})).To(BeTrue()) - }, tests.TimeoutMedium, tests.RetryIntervalMedium, ctx).Should(Succeed()) - }, testTimeOut) - - }) - - Context("without default geo", func() { - BeforeEach(func(ctx SpecContext) { - dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). - WithProviderSecret(*dnsProviderSecret). - WithTargetGateway(tests.GatewayName). - WithLoadBalancingFor(120, "IE", false) - Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) - }) - - It("should create dns records", func(ctx SpecContext) { - Eventually(func(g Gomega, ctx context.Context) { - recordList := &kuadrantdnsv1alpha1.DNSRecordList{} - err := k8sClient.List(ctx, recordList, &client.ListOptions{Namespace: testNamespace}) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(recordList.Items).To(HaveLen(2)) - - dnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} - err = k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, dnsRecord) - g.Expect(err).NotTo(HaveOccurred()) - - wildcardDnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} - err = k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, wildcardDnsRecord) - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(dnsRecord.Name).To(Equal(recordName)) - g.Expect(dnsRecord.Spec.ProviderRef.Name).To(Equal(dnsProviderSecret.Name)) - g.Expect(dnsRecord.Spec.Endpoints).To(ConsistOf( - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(clusterHash + "-" + gwHash + "." + "klb.test." + domain), - "Targets": ConsistOf(tests.IPAddressOne, tests.IPAddressTwo), - "RecordType": Equal("A"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(60)), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("ie.klb.test." + domain), - "Targets": ConsistOf(clusterHash + "-" + gwHash + "." + "klb.test." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(clusterHash + "-" + gwHash + "." + "klb.test." + domain), - "RecordTTL": Equal(externaldns.TTL(60)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "weight", Value: "120"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("klb.test." + domain), - "Targets": ConsistOf("ie.klb.test." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal("IE"), - "RecordTTL": Equal(externaldns.TTL(300)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "geo-code", Value: "IE"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(tests.HostOne(domain)), - "Targets": ConsistOf("klb.test." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(300)), - })), - )) - g.Expect(dnsRecord.Status.OwnerID).ToNot(BeEmpty()) - g.Expect(dnsRecord.Status.OwnerID).To(Equal(dnsRecord.GetUIDHash())) - g.Expect(tests.EndpointsTraversable(dnsRecord.Spec.Endpoints, tests.HostOne(domain), []string{tests.IPAddressOne, tests.IPAddressTwo})).To(BeTrue()) - - g.Expect(wildcardDnsRecord.Name).To(Equal(wildcardRecordName)) - g.Expect(wildcardDnsRecord.Spec.ProviderRef.Name).To(Equal(dnsProviderSecret.Name)) - g.Expect(wildcardDnsRecord.Spec.Endpoints).To(ContainElements( - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(clusterHash + "-" + gwHash + "." + "klb." + domain), - "Targets": ConsistOf(tests.IPAddressOne, tests.IPAddressTwo), - "RecordType": Equal("A"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(60)), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("ie.klb." + domain), - "Targets": ConsistOf(clusterHash + "-" + gwHash + "." + "klb." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(clusterHash + "-" + gwHash + "." + "klb." + domain), - "RecordTTL": Equal(externaldns.TTL(60)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "weight", Value: "120"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal("klb." + domain), - "Targets": ConsistOf("ie.klb." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal("IE"), - "RecordTTL": Equal(externaldns.TTL(300)), - "ProviderSpecific": Equal(externaldns.ProviderSpecific{{Name: "geo-code", Value: "IE"}}), - })), - PointTo(MatchFields(IgnoreExtras, Fields{ - "DNSName": Equal(tests.HostWildcard(domain)), - "Targets": ConsistOf("klb." + domain), - "RecordType": Equal("CNAME"), - "SetIdentifier": Equal(""), - "RecordTTL": Equal(externaldns.TTL(300)), - })), - )) - g.Expect(wildcardDnsRecord.Status.OwnerID).ToNot(BeEmpty()) - g.Expect(wildcardDnsRecord.Status.OwnerID).To(Equal(wildcardDnsRecord.GetUIDHash())) - g.Expect(tests.EndpointsTraversable(wildcardDnsRecord.Spec.Endpoints, tests.HostWildcard(domain), []string{tests.IPAddressOne, tests.IPAddressTwo})).To(BeTrue()) - }, tests.TimeoutMedium, tests.RetryIntervalMedium, ctx).Should(Succeed()) - }, testTimeOut) - - }) - }) -}) diff --git a/tests/common/dnspolicy/dnspolicy_controller_test.go b/tests/common/dnspolicy/dnspolicy_controller_test.go index 5b8b688fa..b5b39888e 100644 --- a/tests/common/dnspolicy/dnspolicy_controller_test.go +++ b/tests/common/dnspolicy/dnspolicy_controller_test.go @@ -220,7 +220,7 @@ var _ = Describe("DNSPolicy controller", func() { WithTargetGateway("test-gateway1") Expect(k8sClient.Create(ctx, dnsPolicy1)).To(Succeed()) - // the policy 1 should succeed + // policy 1 should succeed Eventually(func(g Gomega) { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy1), dnsPolicy1)).To(Succeed()) @@ -295,7 +295,7 @@ var _ = Describe("DNSPolicy controller", func() { }))) }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) - // check that error is also displayed in the gateway + // check that error is also displayed in the record Eventually(func(g Gomega) { dnsRecord2 := &kuadrantdnsv1alpha1.DNSRecord{ ObjectMeta: metav1.ObjectMeta{ @@ -452,7 +452,6 @@ var _ = Describe("DNSPolicy controller", func() { }).WithContext(ctx).Should(Succeed()) }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) }, testTimeOut) - }) Context("valid target with no gateway status", func() { @@ -470,16 +469,14 @@ var _ = Describe("DNSPolicy controller", func() { Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) }) - It("should not create a dns record", func(ctx SpecContext) { + It("should not create a dns record and should have accepted and not enforced status and hould set gateway back reference", func(ctx SpecContext) { Consistently(func() []kuadrantdnsv1alpha1.DNSRecord { // DNS record exists dnsRecords := kuadrantdnsv1alpha1.DNSRecordList{} err := k8sClient.List(ctx, &dnsRecords, client.InNamespace(dnsPolicy.GetNamespace())) Expect(err).ToNot(HaveOccurred()) return dnsRecords.Items }, time.Second*15, time.Second).Should(BeEmpty()) - }, testTimeOut) - It("should have accepted and not enforced status", func(ctx SpecContext) { Eventually(func(g Gomega) { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) g.Expect(err).NotTo(HaveOccurred()) @@ -499,9 +496,7 @@ var _ = Describe("DNSPolicy controller", func() { })), ) }, tests.TimeoutMedium, time.Second).Should(Succeed()) - }, testTimeOut) - It("should set gateway back reference", func(ctx SpecContext) { policyBackRefValue := testNamespace + "/" + dnsPolicy.Name refs, _ := json.Marshal([]client.ObjectKey{{Name: dnsPolicy.Name, Namespace: testNamespace}}) policiesBackRefValue := string(refs) @@ -528,7 +523,6 @@ var _ = Describe("DNSPolicy controller", func() { WithTargetGateway(tests.GatewayName) Expect(k8sClient.Create(ctx, gateway)).To(Succeed()) - Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) Eventually(func(g Gomega) { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway)).To(Succeed()) @@ -563,252 +557,314 @@ var _ = Describe("DNSPolicy controller", func() { wildcardRecordName = fmt.Sprintf("%s-%s", tests.GatewayName, tests.ListenerNameWildcard) }) - It("should have correct status", func(ctx SpecContext) { - Eventually(func(g Gomega) { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(dnsPolicy.Finalizers).To(ContainElement(controllers.DNSPolicyFinalizer)) - g.Expect(dnsPolicy.Status.Conditions).To( - ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), - "Status": Equal(metav1.ConditionTrue), - "Reason": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), - "Message": Equal("DNSPolicy has been accepted"), - }), - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(kuadrant.PolicyConditionEnforced)), - "Status": Equal(metav1.ConditionTrue), - "Reason": Equal(string(kuadrant.PolicyReasonEnforced)), - "Message": Equal("DNSPolicy has been successfully enforced"), - })), - ) - }, tests.TimeoutLong, time.Second).Should(Succeed()) - - // ensure there are no policies with not accepted condition - // in this case the "enforced" on the policy should be false - Eventually(func(g Gomega) { - dnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} - err := k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, dnsRecord) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(dnsRecord.Status.Conditions).ToNot(ContainElement(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), - "Status": Equal(metav1.ConditionFalse), - }))) - }, tests.TimeoutMedium, time.Second).Should(Succeed()) - }, testTimeOut) + Context("with simple routing strategy", func() { + BeforeEach(func(ctx SpecContext) { + dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). + WithProviderSecret(*dnsProviderSecret). + WithTargetGateway(tests.GatewayName) + Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) + }) - It("should set gateway back reference", func(ctx SpecContext) { - policyBackRefValue := testNamespace + "/" + dnsPolicy.Name - refs, _ := json.Marshal([]client.ObjectKey{{Name: dnsPolicy.Name, Namespace: testNamespace}}) - policiesBackRefValue := string(refs) + It("should have correct status and should set gateway back reference", func(ctx SpecContext) { - Eventually(func(g Gomega) { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyDirectReferenceAnnotationName, policyBackRefValue)) - g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyBackReferenceAnnotationName, policiesBackRefValue)) - }, tests.TimeoutMedium, time.Second).Should(Succeed()) - }, testTimeOut) + // here we are checking for the presence of DNS Record. We are not checking the presence of the wildcard one + // as it will be ensured later in the cleanup test. This speeds up tests. + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsPolicy.Finalizers).To(ContainElement(controllers.DNSPolicyFinalizer)) + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElements( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), + "Message": Equal("DNSPolicy has been accepted"), + }), + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal(string(kuadrant.PolicyReasonEnforced)), + "Message": Equal("DNSPolicy has been successfully enforced"), + })), + ) + + dnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, dnsRecord)).To(Succeed()) + g.Expect(dnsRecord.Status.OwnerID).ToNot(BeEmpty()) + g.Expect(dnsRecord.Status.OwnerID).To(Equal(dnsRecord.GetUIDHash())) + // we are not checking endpoints themselves here but only the fact of generation + g.Expect(dnsRecord.Spec.Endpoints).ToNot(BeEmpty()) + + wildcardRecord := &kuadrantdnsv1alpha1.DNSRecord{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, wildcardRecord)).To(Succeed()) + g.Expect(wildcardRecord.Status.OwnerID).ToNot(BeEmpty()) + g.Expect(wildcardRecord.Status.OwnerID).To(Equal(wildcardRecord.GetUIDHash())) + // we are not checking endpoints themselves here but only the fact of generation + g.Expect(wildcardRecord.Spec.Endpoints).ToNot(BeEmpty()) + + }, tests.TimeoutLong, time.Second).Should(Succeed()) + + // ensure there are no policies with not accepted condition + // in this case the "enforced" on the policy should be false + Eventually(func(g Gomega) { + // refreshing record + dnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, dnsRecord) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsRecord.Status.Conditions).ToNot(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), + "Status": Equal(metav1.ConditionFalse), + }))) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) - It("should remove dns records when listener removed", func(ctx SpecContext) { - //get the gateway and remove the listeners + policyBackRefValue := testNamespace + "/" + dnsPolicy.Name + refs, _ := json.Marshal([]client.ObjectKey{{Name: dnsPolicy.Name, Namespace: testNamespace}}) + policiesBackRefValue := string(refs) - Eventually(func() error { - existingGateway := &gatewayapiv1.Gateway{} - if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), existingGateway); err != nil { - return err - } - newListeners := []gatewayapiv1.Listener{} - for _, existing := range existingGateway.Spec.Listeners { - if existing.Name == tests.ListenerNameWildcard { + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyDirectReferenceAnnotationName, policyBackRefValue)) + g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyBackReferenceAnnotationName, policiesBackRefValue)) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) + }, testTimeOut) + + It("should re-create dns record when listener hostname changes", func(ctx SpecContext) { + //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( + ContainElements( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, currentWildcardRec)).To(Succeed()) + g.Expect(currentRec.Status.Conditions).To( + ContainElements( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + }, tests.TimeoutLong, time.Second).Should(BeNil()) + + //get the gateway and change the hostname of the listener that corresponds to the dnsrecord + newHostname := gatewayapiv1.Hostname(tests.HostTwo(domain)) + Eventually(func() error { + existingGateway := &gatewayapiv1.Gateway{} + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), existingGateway); err != nil { + return err + } + var newListeners []gatewayapiv1.Listener + for _, existing := range existingGateway.Spec.Listeners { + if existing.Name == tests.ListenerNameOne { + existing.Hostname = &newHostname + } newListeners = append(newListeners, existing) } - } + patch := client.MergeFrom(existingGateway.DeepCopy()) + existingGateway.Spec.Listeners = newListeners + return k8sClient.Patch(ctx, existingGateway, patch) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) - patch := client.MergeFrom(existingGateway.DeepCopy()) - existingGateway.Spec.Listeners = newListeners - rec := &kuadrantdnsv1alpha1.DNSRecord{} - if err := k8sClient.Patch(ctx, existingGateway, patch); err != nil { - return err - } - //dns record should be removed for non wildcard - if err := k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, rec); err != nil && !k8serrors.IsNotFound(err) { - return err - } - return k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, rec) - }, time.Second*10, time.Second).Should(BeNil()) - }, testTimeOut) + //get the dnsrecord again and verify it's no longer the same DNSRecord resource, and the rootHost 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(string(newHostname))) + g.Expect(newRec.Spec.RootHost).ToNot(Equal(currentRec.Spec.RootHost)) + g.Expect(newRec.UID).ToNot(Equal(currentRec.UID)) + g.Expect(newRec.Status.Conditions).To( + ContainElements( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + 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( + ContainElements( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + currentRec = newRec + currentWildcardRec = newWildcardRec + }, tests.TimeoutLong, time.Second).Should(BeNil()) + + //get the gateway and change the hostname of the listener that corresponds to the wildcard dnsrecord + newWildcardHostname := gatewayapiv1.Hostname(tests.HostWildcard(tests.HostTwo(domain))) + Eventually(func() error { + existingGateway := &gatewayapiv1.Gateway{} + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), existingGateway); err != nil { + return err + } + var newListeners []gatewayapiv1.Listener + for _, existing := range existingGateway.Spec.Listeners { + if existing.Name == tests.ListenerNameWildcard { + existing.Hostname = &newWildcardHostname + } + newListeners = append(newListeners, existing) + } + patch := client.MergeFrom(existingGateway.DeepCopy()) + existingGateway.Spec.Listeners = newListeners + return k8sClient.Patch(ctx, existingGateway, patch) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) - It("should re-create dns record when listener hostname changes", func(ctx SpecContext) { - //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( - ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), - "Status": Equal(metav1.ConditionTrue), - })), - ) - g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, currentWildcardRec)).To(Succeed()) - g.Expect(currentRec.Status.Conditions).To( - ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), - "Status": Equal(metav1.ConditionTrue), - })), - ) - }, tests.TimeoutLong, time.Second).Should(BeNil()) - - //get the gateway and change the hostname of the listener that corresponds to the dnsrecord - newHostname := gatewayapiv1.Hostname(tests.HostTwo(domain)) - Eventually(func() error { - existingGateway := &gatewayapiv1.Gateway{} - if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), existingGateway); err != nil { - return err - } - newListeners := []gatewayapiv1.Listener{} - for _, existing := range existingGateway.Spec.Listeners { - if existing.Name == tests.ListenerNameOne { - existing.Hostname = &newHostname + //get the dnsrecord again and verify the DNSRecord resource is unchanged + //get the wildcard dnsrecord again and verify it's no longer the same DNSRecord resource and the rootHost 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( + ContainElements( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + newWildcardRec := &kuadrantdnsv1alpha1.DNSRecord{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, newWildcardRec)).To(Succeed()) + g.Expect(newWildcardRec.Spec.RootHost).To(Equal(string(newWildcardHostname))) + g.Expect(newWildcardRec.Spec.RootHost).ToNot(Equal(currentWildcardRec.Spec.RootHost)) + g.Expect(newWildcardRec.UID).ToNot(Equal(currentWildcardRec.UID)) + g.Expect(newWildcardRec.Status.Conditions).To( + ContainElements( + MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + currentRec = newRec + currentWildcardRec = newWildcardRec + }, tests.TimeoutLong, time.Second).Should(BeNil()) + }, testTimeOut) + + It("should cleanup correctly", func(ctx SpecContext) { + By("should remove dns records when listener removed") + // get the gateway and remove the listeners + Eventually(func() error { + existingGateway := &gatewayapiv1.Gateway{} + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), existingGateway); err != nil { + return err + } + var newListeners []gatewayapiv1.Listener + for _, existing := range existingGateway.Spec.Listeners { + if existing.Name == tests.ListenerNameWildcard { + newListeners = append(newListeners, existing) + } } - newListeners = append(newListeners, existing) - } - patch := client.MergeFrom(existingGateway.DeepCopy()) - existingGateway.Spec.Listeners = newListeners - return k8sClient.Patch(ctx, existingGateway, patch) - }, tests.TimeoutMedium, time.Second).Should(Succeed()) - //get the dnsrecord again and verify it's no longer the same DNSRecord resource and the rootHost 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(string(newHostname))) - g.Expect(newRec.Spec.RootHost).ToNot(Equal(currentRec.Spec.RootHost)) - g.Expect(newRec.UID).ToNot(Equal(currentRec.UID)) - g.Expect(newRec.Status.Conditions).To( - ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), - "Status": Equal(metav1.ConditionTrue), - })), - ) - 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( - ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), - "Status": Equal(metav1.ConditionTrue), - })), - ) - currentRec = newRec - currentWildcardRec = newWildcardRec - }, tests.TimeoutLong, time.Second).Should(BeNil()) - - //get the gateway and change the hostname of the listener that corresponds to the wildcard dnsrecord - newWildcardHostname := gatewayapiv1.Hostname(tests.HostWildcard(tests.HostTwo(domain))) - Eventually(func() error { - existingGateway := &gatewayapiv1.Gateway{} - if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), existingGateway); err != nil { - return err - } - newListeners := []gatewayapiv1.Listener{} - for _, existing := range existingGateway.Spec.Listeners { - if existing.Name == tests.ListenerNameWildcard { - existing.Hostname = &newWildcardHostname + patch := client.MergeFrom(existingGateway.DeepCopy()) + existingGateway.Spec.Listeners = newListeners + rec := &kuadrantdnsv1alpha1.DNSRecord{} + if err := k8sClient.Patch(ctx, existingGateway, patch); err != nil { + return err } - newListeners = append(newListeners, existing) - } - patch := client.MergeFrom(existingGateway.DeepCopy()) - existingGateway.Spec.Listeners = newListeners - return k8sClient.Patch(ctx, existingGateway, patch) - }, tests.TimeoutMedium, time.Second).Should(Succeed()) + //dns record should be removed for non-wildcard + if err := k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, rec); err != nil && !k8serrors.IsNotFound(err) { + return err + } + return k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, rec) + }, time.Second*10, time.Second).Should(BeNil()) - //get the dnsrecord again and verify the DNSRecord resource is unchanged - //get the wildcard dnsrecord again and verify it's no longer the same DNSRecord resource and the rootHost 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( - ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), - "Status": Equal(metav1.ConditionTrue), - })), - ) - newWildcardRec := &kuadrantdnsv1alpha1.DNSRecord{} - g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, newWildcardRec)).To(Succeed()) - g.Expect(newWildcardRec.Spec.RootHost).To(Equal(string(newWildcardHostname))) - g.Expect(newWildcardRec.Spec.RootHost).ToNot(Equal(currentWildcardRec.Spec.RootHost)) - g.Expect(newWildcardRec.UID).ToNot(Equal(currentWildcardRec.UID)) - g.Expect(newWildcardRec.Status.Conditions).To( - ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Type": Equal(string(kuadrantdnsv1alpha1.ConditionTypeReady)), - "Status": Equal(metav1.ConditionTrue), - })), - ) - currentRec = newRec - currentWildcardRec = newWildcardRec - }, tests.TimeoutLong, time.Second).Should(BeNil()) - }, testTimeOut) + By("should remove gateway back reference on policy deletion") + policyBackRefValue := testNamespace + "/" + dnsPolicy.Name + refs, _ := json.Marshal([]client.ObjectKey{{Name: dnsPolicy.Name, Namespace: testNamespace}}) + policiesBackRefValue := string(refs) - It("should remove gateway back reference on policy deletion", func(ctx SpecContext) { - policyBackRefValue := testNamespace + "/" + dnsPolicy.Name - refs, _ := json.Marshal([]client.ObjectKey{{Name: dnsPolicy.Name, Namespace: testNamespace}}) - policiesBackRefValue := string(refs) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyDirectReferenceAnnotationName, policyBackRefValue)) + g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyBackReferenceAnnotationName, policiesBackRefValue)) - Eventually(func(g Gomega) { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyDirectReferenceAnnotationName, policyBackRefValue)) - g.Expect(gateway.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyBackReferenceAnnotationName, policiesBackRefValue)) + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsPolicy.Finalizers).To(ContainElement(controllers.DNSPolicyFinalizer)) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) - err = k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(dnsPolicy.Finalizers).To(ContainElement(controllers.DNSPolicyFinalizer)) - }, tests.TimeoutMedium, time.Second).Should(Succeed()) + // deleting the dns policy + Expect(k8sClient.Delete(ctx, dnsPolicy)).To(BeNil()) - By("deleting the dns policy") - Expect(k8sClient.Delete(ctx, dnsPolicy)).To(BeNil()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(gateway.Annotations).ToNot(HaveKey(v1alpha1.DNSPolicyDirectReferenceAnnotationName)) + g.Expect(gateway.Annotations).ToNot(HaveKeyWithValue(v1alpha1.DNSPolicyBackReferenceAnnotationName, policiesBackRefValue)) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) + }, testTimeOut) - Eventually(func(g Gomega) { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(gateway.Annotations).ToNot(HaveKey(v1alpha1.DNSPolicyDirectReferenceAnnotationName)) - g.Expect(gateway.Annotations).ToNot(HaveKeyWithValue(v1alpha1.DNSPolicyBackReferenceAnnotationName, policiesBackRefValue)) - }, tests.TimeoutMedium, time.Second).Should(Succeed()) - }, testTimeOut) + It("should remove dns record reference on policy deletion even if gateway is removed", func(ctx SpecContext) { + Eventually(func() error { // DNS record exists + return k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, &kuadrantdnsv1alpha1.DNSRecord{}) + }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) - It("should remove dns record reference on policy deletion even if gateway is removed", func(ctx SpecContext) { + err := k8sClient.Delete(ctx, gateway) + Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) - Eventually(func() error { // DNS record exists - return k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, &kuadrantdnsv1alpha1.DNSRecord{}) - }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) + err = k8sClient.Delete(ctx, dnsPolicy) + Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) - err := k8sClient.Delete(ctx, gateway) - Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) + // We cant assume that a dnsrecord reconciler is running that will remove the record or that it will be removed + // It's not the responsibility of this operator to do this, so we should just check if it's gone + // (in case we are running on a cluster that actually has a dnsrecord reconciler running), or that it is marked for deletion + Eventually(func(g Gomega) { + record := &kuadrantdnsv1alpha1.DNSRecord{} + // DNS record was removed + g.Expect(client.IgnoreNotFound(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, record))).To(Succeed()) + // DNS record was only marked for deletion + if record.Name != "" { + // ensure it was marked for deletion + g.Expect(record.DeletionTimestamp).ToNot(BeNil()) + } + }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) + }, testTimeOut) - err = k8sClient.Delete(ctx, dnsPolicy) - Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) + }) - //ToDo We cant assume that a dnsrecord reconciler is running that will remove the record or that it will be removed - // It's not the responsibility of this operator to do this, so we should just check if it's gone - // (in case we are running on a cluster that actually has a dnsrecord reconciler running), or that it is marked for deletion - //Eventually(func() error { // DNS record removed - // return k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, &kuadrantdnsv1alpha1.DNSRecord{}) - //}, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(MatchError(ContainSubstring("not found"))) + // The only difference of simple vs. lb strategy is endpoints that aren't checked here. + // The reason to include is to ensure the fact of creating records for lb strategy (lb strategy is processed) + Context("with lb routing strategy", func() { + BeforeEach(func(ctx SpecContext) { + dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). + WithProviderSecret(*dnsProviderSecret). + WithTargetGateway(tests.GatewayName). + WithLoadBalancingFor(120, "IE", true) + Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) + }) + + It("ensure DNS records", func(ctx SpecContext) { + // ensure records exist. The only difference from simple policy is an array of endpoints + // this operator is not responsible for producing endpoints so not checking for them + Eventually(func(g Gomega) { + dnsRecord := &kuadrantdnsv1alpha1.DNSRecord{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, dnsRecord)).To(Succeed()) + g.Expect(dnsRecord.Status.OwnerID).ToNot(BeEmpty()) + g.Expect(dnsRecord.Status.OwnerID).To(Equal(dnsRecord.GetUIDHash())) + g.Expect(dnsRecord.Spec.Endpoints).ToNot(BeEmpty()) + + wildcardRecord := &kuadrantdnsv1alpha1.DNSRecord{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: wildcardRecordName, Namespace: testNamespace}, wildcardRecord)).To(Succeed()) + g.Expect(wildcardRecord.Status.OwnerID).ToNot(BeEmpty()) + g.Expect(wildcardRecord.Status.OwnerID).To(Equal(wildcardRecord.GetUIDHash())) + g.Expect(wildcardRecord.Status.Endpoints).ToNot(BeEmpty()) + }, tests.TimeoutLong, time.Second).Should(Succeed()) + }) + }) - }, testTimeOut) }) Context("cel validation", func() { diff --git a/tests/common/targetstatus/target_status_controller_test.go b/tests/common/targetstatus/target_status_controller_test.go index b8cb2f5d8..c2cf7b01f 100644 --- a/tests/common/targetstatus/target_status_controller_test.go +++ b/tests/common/targetstatus/target_status_controller_test.go @@ -8,12 +8,14 @@ import ( "strings" "time" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + certmanv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" certmanmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" authorinoapi "github.com/kuadrant/authorino/api/v1beta2" kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"