diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 2b6b48bb..f25440bb 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -125,6 +125,12 @@ func main() { klog.InfoS("Started leading: ", LeaseLockName, leaseLockId) c := controller.NewController(coreClient, crdClient, istioClient, certClient, certManagerClient, dnsClient, promClient) + + checkDone := make(chan bool, 1) + go checkDRs(checkDone, istioClient, crdClient) + <-checkDone + klog.InfoS("check & update of DestinationRules done") + // Update the controller's concurrency config before starting the controller maps.Copy(controller.DefaultConcurrentReconciles, concurrencyConfig) go c.Start(ctx) diff --git a/cmd/controller/tmp.go b/cmd/controller/tmp.go new file mode 100644 index 00000000..980b9f1a --- /dev/null +++ b/cmd/controller/tmp.go @@ -0,0 +1,122 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and cap-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "context" + "crypto/sha1" + "fmt" + "strings" + "time" + + "github.com/sap/cap-operator/pkg/apis/sme.sap.com/v1alpha1" + "github.com/sap/cap-operator/pkg/client/clientset/versioned" + "google.golang.org/protobuf/types/known/durationpb" + networkingv1 "istio.io/api/networking/v1" + "istio.io/api/networking/v1alpha3" + istionwv1 "istio.io/client-go/pkg/apis/networking/v1" + istio "istio.io/client-go/pkg/clientset/versioned" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/klog/v2" +) + +const ( + LabelOwnerIdentifierHash = "sme.sap.com/owner-identifier-hash" +) + +func checkDRs(checkDone chan bool, istioClient istio.Interface, crdClient versioned.Interface) { + // Always set the channel to true in the end + defer func() { + checkDone <- true + }() + + // Create new DestinationRule for Router workload of each CAPApplicationVersion + appVersions, err := crdClient.SmeV1alpha1().CAPApplicationVersions(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + klog.ErrorS(err, "Failed to list application versions") + return + } + for _, cav := range appVersions.Items { + drName := getDRName(cav) + // Ignore application versions without router workload + if drName != "" { + _, err := istioClient.NetworkingV1().DestinationRules(cav.Namespace).Get(context.TODO(), drName, metav1.GetOptions{}) + if errors.IsNotFound(err) { + err = nil + dr := &istionwv1.DestinationRule{ + ObjectMeta: metav1.ObjectMeta{ + Name: drName, + Namespace: cav.Namespace, + Labels: map[string]string{}, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&cav, v1alpha1.SchemeGroupVersion.WithKind(v1alpha1.CAPApplicationVersionKind))}, + }, + Spec: networkingv1.DestinationRule{ + Host: drName + "-svc" + "." + cav.Namespace + ".svc.cluster.local", + TrafficPolicy: &networkingv1.TrafficPolicy{ + LoadBalancer: &networkingv1.LoadBalancerSettings{ + LbPolicy: &networkingv1.LoadBalancerSettings_ConsistentHash{ + ConsistentHash: &networkingv1.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &v1alpha3.LoadBalancerSettings_ConsistentHashLB_HttpCookie{ + HttpCookie: &networkingv1.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{ + Name: "CAPOP_ROUTER_STICKY", + Path: "/", + Ttl: durationpb.New(0 * time.Second), + }, + }, + }, + }, + }, + }, + }, + } + _, err = istioClient.NetworkingV1().DestinationRules(cav.Namespace).Create(context.TODO(), dr, metav1.CreateOptions{}) + } + if err != nil { + klog.ErrorS(err, "Error managing DestinationRule: ", drName) + continue + } + } + } + + // Get all tenants + tenants, err := crdClient.SmeV1alpha1().CAPTenants(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + klog.ErrorS(err, "Failed to list tenants") + return + } + // Delete all DestinationRules in the tenant's namespace that have the relevant ownerId label + for _, tenant := range tenants.Items { + ownerIdentifierHash := sha1Sum(tenant.Namespace, tenant.Name) + ownerLabelHashReq, _ := labels.NewRequirement(LabelOwnerIdentifierHash, selection.Equals, []string{ownerIdentifierHash}) + ownerLabelHashReqSelector := labels.NewSelector().Add(*ownerLabelHashReq) + err := istioClient.NetworkingV1().DestinationRules(tenant.Namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{ + LabelSelector: ownerLabelHashReqSelector.String(), + }) + if err != nil { + klog.ErrorS(err, "Failed to delete DestinationRules for tenant: ", tenant.Name) + } + } +} + +func getDRName(cav v1alpha1.CAPApplicationVersion) string { + drName := "" + for _, workload := range cav.Spec.Workloads { + if workload.DeploymentDefinition != nil && workload.DeploymentDefinition.Type == v1alpha1.DeploymentRouter { + drName = fmt.Sprintf("%s-%s", cav.Name, strings.ToLower(workload.Name)) + break + } + } + return drName +} + +// Returns an sha1 checksum for a given source string +func sha1Sum(source ...string) string { + sum := sha1.Sum([]byte(strings.Join(source, ""))) + return fmt.Sprintf("%x", sum) +} diff --git a/crds/sme.sap.com_capapplicationversions.yaml b/crds/sme.sap.com_capapplicationversions.yaml index 75f6a136..d233eeca 100644 --- a/crds/sme.sap.com_capapplicationversions.yaml +++ b/crds/sme.sap.com_capapplicationversions.yaml @@ -2201,6 +2201,45 @@ spec: type: object serviceAccountName: type: string + stickiness: + properties: + hash: + properties: + httpCookie: + properties: + attributes: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + name: + type: string + path: + type: string + ttl: + type: string + type: object + httpHeaderName: + type: string + httpQueryParameterName: + type: string + useSourceIp: + type: boolean + type: object + x-kubernetes-validations: + - message: at most one of the fields in [httpHeaderName + httpCookie useSourceIp httpQueryParameterName] may + be set + rule: '[has(self.httpHeaderName),has(self.httpCookie),has(self.useSourceIp),has(self.httpQueryParameterName)].filter(x,x==true).size() + <= 1' + type: object tolerations: items: properties: diff --git a/internal/controller/informers.go b/internal/controller/informers.go index 44bcd428..08f1d79a 100644 --- a/internal/controller/informers.go +++ b/internal/controller/informers.go @@ -62,7 +62,7 @@ var QueueMapping map[int]map[int]string = map[int]map[int]string{ ResourceCertificate: {ResourceDomain: v1alpha1.DomainKind, ResourceClusterDomain: v1alpha1.ClusterDomainKind}, ResourceDNSEntry: {ResourceDomain: v1alpha1.DomainKind, ResourceClusterDomain: v1alpha1.ClusterDomainKind}, ResourceVirtualService: {ResourceCAPTenant: v1alpha1.CAPTenantKind}, - ResourceDestinationRule: {ResourceCAPTenant: v1alpha1.CAPTenantKind}, + ResourceDestinationRule: {ResourceCAPApplicationVersion: v1alpha1.CAPApplicationVersionKind}, } type QueueItem struct { diff --git a/internal/controller/reconcile-capapplicationversion.go b/internal/controller/reconcile-capapplicationversion.go index 0b058d14..4275d09a 100644 --- a/internal/controller/reconcile-capapplicationversion.go +++ b/internal/controller/reconcile-capapplicationversion.go @@ -709,22 +709,47 @@ func (c *Controller) updateDeployment(ca *v1alpha1.CAPApplication, cav *v1alpha1 // Create PDB for the deployment if configured if err == nil && workload.DeploymentDefinition.PodDisruptionBudget != nil { err = c.createOrUpdatePodDisruptionBudget(workload, cav, ca) - if err != nil { - return nil, err - } } // Create HPA for the deployment if configured if err == nil && workload.DeploymentDefinition.HorizontalPodAutoscaler != nil { err = c.createOrUpdateHorizontalPodAutoscaler(deploymentName, workload, cav, ca) - if err != nil { - return nil, err - } + } + + // Create DestinationRule for the deployment based on stickiness configuration (if any) + if err == nil { + err = c.createOrUpdateDestinationRule(deploymentName, workload, cav) } return workloadDeployment, doChecks(err, workloadDeployment, cav, workload.Name) } +func (c *Controller) createOrUpdateDestinationRule(deploymentName string, workload *v1alpha1.WorkloadDetails, cav *v1alpha1.CAPApplicationVersion) error { + // Only create DestinationRule if stickiness is configured for the workload + stickiness := getStickinessForWorkload(workload) + if stickiness == nil { + return nil + } + return c.handleDestinationRule(context.TODO(), deploymentName, stickiness, cav) +} + +func getStickinessForWorkload(workload *v1alpha1.WorkloadDetails) *v1alpha1.Stickiness { + if workload.DeploymentDefinition.Stickiness != nil { + return workload.DeploymentDefinition.Stickiness + } else if workload.DeploymentDefinition.Type == v1alpha1.DeploymentRouter { + return &v1alpha1.Stickiness{ + Hash: &v1alpha1.StickinessHash{ + HttpCookie: &v1alpha1.HTTPCookie{ + Name: RouterHttpCookieName, + Path: "/", + Ttl: &metav1.Duration{Duration: 0 * time.Second}, // session cookie + }, + }, + } + } + return nil +} + func (c *Controller) createOrUpdateHorizontalPodAutoscaler(deploymentName string, workload *v1alpha1.WorkloadDetails, cav *v1alpha1.CAPApplicationVersion, ca *v1alpha1.CAPApplication) error { hpaName := deploymentName // Get the HPA which should exist for this deployment diff --git a/internal/controller/reconcile-capapplicationversion_test.go b/internal/controller/reconcile-capapplicationversion_test.go index 62d8ccdc..dcb8f0bd 100644 --- a/internal/controller/reconcile-capapplicationversion_test.go +++ b/internal/controller/reconcile-capapplicationversion_test.go @@ -1061,3 +1061,75 @@ func TestCAV_HorizontalPodAutoscaler(t *testing.T) { }, ) } + +func TestCAV_StickinessHttpHeader(t *testing.T) { + reconcileTestItem( + context.TODO(), t, + QueueItem{Key: ResourceCAPApplicationVersion, ResourceKey: NamespacedResourceKey{Namespace: "default", Name: "test-cap-01-cav-v1"}}, + TestData{ + description: "capapplication version with http header stickiness on CAP workload", + initialResources: []string{ + "testdata/common/capapplication.yaml", + "testdata/common/credential-secrets.yaml", + "testdata/capapplicationversion/content-job-completed.yaml", + "testdata/capapplicationversion/cav-stickiness-http-header.yaml", + }, + expectedResources: "testdata/capapplicationversion/expected/cav-ready-stickiness-http-header.yaml", + expectedRequeue: map[int][]NamespacedResourceKey{ResourceCAPApplicationVersion: {{Namespace: "default", Name: "test-cap-01-cav-v1"}}}, + }, + ) +} + +func TestCAV_StickinessSourceIp(t *testing.T) { + reconcileTestItem( + context.TODO(), t, + QueueItem{Key: ResourceCAPApplicationVersion, ResourceKey: NamespacedResourceKey{Namespace: "default", Name: "test-cap-01-cav-v1"}}, + TestData{ + description: "capapplication version with source IP stickiness on CAP workload", + initialResources: []string{ + "testdata/common/capapplication.yaml", + "testdata/common/credential-secrets.yaml", + "testdata/capapplicationversion/content-job-completed.yaml", + "testdata/capapplicationversion/cav-stickiness-source-ip.yaml", + }, + expectedResources: "testdata/capapplicationversion/expected/cav-ready-stickiness-source-ip.yaml", + expectedRequeue: map[int][]NamespacedResourceKey{ResourceCAPApplicationVersion: {{Namespace: "default", Name: "test-cap-01-cav-v1"}}}, + }, + ) +} + +func TestCAV_StickinessQueryParam(t *testing.T) { + reconcileTestItem( + context.TODO(), t, + QueueItem{Key: ResourceCAPApplicationVersion, ResourceKey: NamespacedResourceKey{Namespace: "default", Name: "test-cap-01-cav-v1"}}, + TestData{ + description: "capapplication version with HTTP query parameter stickiness on CAP workload", + initialResources: []string{ + "testdata/common/capapplication.yaml", + "testdata/common/credential-secrets.yaml", + "testdata/capapplicationversion/content-job-completed.yaml", + "testdata/capapplicationversion/cav-stickiness-query-param.yaml", + }, + expectedResources: "testdata/capapplicationversion/expected/cav-ready-stickiness-query-param.yaml", + expectedRequeue: map[int][]NamespacedResourceKey{ResourceCAPApplicationVersion: {{Namespace: "default", Name: "test-cap-01-cav-v1"}}}, + }, + ) +} + +func TestCAV_StickinessHttpCookieCustom(t *testing.T) { + reconcileTestItem( + context.TODO(), t, + QueueItem{Key: ResourceCAPApplicationVersion, ResourceKey: NamespacedResourceKey{Namespace: "default", Name: "test-cap-01-cav-v1"}}, + TestData{ + description: "capapplication version with custom HTTP cookie stickiness (TTL and attributes) on CAP workload", + initialResources: []string{ + "testdata/common/capapplication.yaml", + "testdata/common/credential-secrets.yaml", + "testdata/capapplicationversion/content-job-completed.yaml", + "testdata/capapplicationversion/cav-stickiness-http-cookie-custom.yaml", + }, + expectedResources: "testdata/capapplicationversion/expected/cav-ready-stickiness-http-cookie-custom.yaml", + expectedRequeue: map[int][]NamespacedResourceKey{ResourceCAPApplicationVersion: {{Namespace: "default", Name: "test-cap-01-cav-v1"}}}, + }, + ) +} diff --git a/internal/controller/reconcile-captenant.go b/internal/controller/reconcile-captenant.go index d5ecfe8a..cf5371c6 100644 --- a/internal/controller/reconcile-captenant.go +++ b/internal/controller/reconcile-captenant.go @@ -53,21 +53,20 @@ const ( ) const ( - CAPTenantEventProcessingStarted = "ProcessingStarted" - CAPTenantEventProvisioningFailed = "ProvisioningFailed" - CAPTenantEventProvisioningCompleted = "ProvisioningCompleted" - CAPTenantEventProvisioningOperationCreated = "ProvisioningOperationCreated" - CAPTenantEventDeprovisioningFailed = "DeprovisioningFailed" - CAPTenantEventDeprovisioningCompleted = "DeprovisioningCompleted" - CAPTenantEventDeprovisioningOperationCreated = "DeprovisioningOperationCreated" - CAPTenantEventUpgradeFailed = "UpgradeFailed" - CAPTenantEventUpgradeCompleted = "UpgradeCompleted" - CAPTenantEventUpgradeOperationCreated = "UpgradeOperationCreated" - CAPTenantEventTenantNetworkingModified = "TenantNetworkingModified" - CAPTenantEventVirtualServiceModificationFailed = "VirtualServiceModificationFailed" - CAPTenantEventDestinationRuleModificationFailed = "DestinationRuleModificationFailed" - CAPTenantEventInvalidReference = "InvalidReference" - CAPTenantEventAutoVersionUpdate = "AutoVersionUpdate" + CAPTenantEventProcessingStarted = "ProcessingStarted" + CAPTenantEventProvisioningFailed = "ProvisioningFailed" + CAPTenantEventProvisioningCompleted = "ProvisioningCompleted" + CAPTenantEventProvisioningOperationCreated = "ProvisioningOperationCreated" + CAPTenantEventDeprovisioningFailed = "DeprovisioningFailed" + CAPTenantEventDeprovisioningCompleted = "DeprovisioningCompleted" + CAPTenantEventDeprovisioningOperationCreated = "DeprovisioningOperationCreated" + CAPTenantEventUpgradeFailed = "UpgradeFailed" + CAPTenantEventUpgradeCompleted = "UpgradeCompleted" + CAPTenantEventUpgradeOperationCreated = "UpgradeOperationCreated" + CAPTenantEventTenantNetworkingModified = "TenantNetworkingModified" + CAPTenantEventVirtualServiceModificationFailed = "VirtualServiceModificationFailed" + CAPTenantEventInvalidReference = "InvalidReference" + CAPTenantEventAutoVersionUpdate = "AutoVersionUpdate" ) const ( diff --git a/internal/controller/reconcile-captenant_test.go b/internal/controller/reconcile-captenant_test.go index b6e8fd15..f4c43f03 100644 --- a/internal/controller/reconcile-captenant_test.go +++ b/internal/controller/reconcile-captenant_test.go @@ -108,29 +108,6 @@ func TestCAPTenantProvisioningCompleted(t *testing.T) { ) } -func TestCAPTenantProvisioningCompletedDestinationRuleModificationFailure(t *testing.T) { - err := reconcileTestItem( - context.TODO(), t, - QueueItem{Key: ResourceCAPTenant, ResourceKey: NamespacedResourceKey{Namespace: "default", Name: "test-cap-01-provider"}}, - TestData{ - description: "captenant provisioning operation completed (destination rule creation fails)", - initialResources: []string{ - "testdata/common/domain-ready.yaml", - "testdata/common/cluster-domain-ready.yaml", - "testdata/common/capapplication.yaml", - "testdata/common/capapplicationversion-v1.yaml", - "testdata/captenant/cat-04.initial.yaml", - }, - expectError: true, - mockErrorForResources: []ResourceAction{{Verb: "create", Group: "networking.istio.io", Version: "v1", Resource: "destinationrules", Namespace: "default", Name: "test-cap-01-provider"}}, - backlogItems: []string{"ERP4SMEPREPWORKAPPPLAT-2811"}, - }, - ) - if err.Error() != "mocked api error (destinationrules.networking.istio.io/v1)" { - t.Error("error message is different from expected") - } -} - func TestCAPTenantProvisioningFailed(t *testing.T) { reconcileTestItem( context.TODO(), t, @@ -231,7 +208,6 @@ func TestCAPTenantStartUpgradeWithStrategyNever(t *testing.T) { "testdata/common/capapplicationversion-v2.yaml", "testdata/captenant/cat-08.initial.yaml", "testdata/captenant/provider-tenant-vs-v1.yaml", - "testdata/captenant/provider-tenant-dr-v1.yaml", }, expectedResources: "testdata/captenant/cat-08.expected.yaml", }, @@ -301,7 +277,6 @@ func TestCAPTenantUpgradeOperationCompleted(t *testing.T) { "testdata/common/capapplicationversion-v1.yaml", "testdata/common/capapplicationversion-v2.yaml", "testdata/captenant/provider-tenant-vs-v1.yaml", - "testdata/captenant/provider-tenant-dr-v1.yaml", "testdata/captenant/cat-13.initial.yaml", }, expectedResources: "testdata/captenant/cat-13.expected.yaml", @@ -325,7 +300,6 @@ func TestCAPTenantUpgradeOperationCompletedPreviousVersionsLimited(t *testing.T) "testdata/common/capapplicationversion-v1.yaml", "testdata/common/capapplicationversion-v2.yaml", "testdata/captenant/provider-tenant-vs-v1.yaml", - "testdata/captenant/provider-tenant-dr-v1.yaml", "testdata/captenant/cat-29.initial.yaml", }, expectedResources: "testdata/captenant/cat-29.expected.yaml", @@ -411,7 +385,6 @@ func TestCAPTenantWithUpgradeErrorSameVersion(t *testing.T) { "testdata/common/capapplicationversion-v3.yaml", "testdata/captenant/cat-23.initial.yaml", "testdata/captenant/provider-tenant-vs-v1.yaml", - "testdata/captenant/provider-tenant-dr-v1.yaml", }, expectedResources: "testdata/captenant/cat-23.expected.yaml", }, @@ -644,7 +617,6 @@ func TestCAPTenantUpgradeOperationCompletedWithSessionAffinityEnabled(t *testing "testdata/common/capapplicationversion-v1.yaml", "testdata/common/capapplicationversion-v2.yaml", "testdata/captenant/provider-tenant-vs-v1.yaml", - "testdata/captenant/provider-tenant-dr-v1.yaml", "testdata/captenant/cat-13.initial.yaml", }, expectedResources: "testdata/captenant/cat-with-session-affinity-dr-vs-upgrade.yaml", @@ -708,26 +680,3 @@ func TestCAPTenantUpgradeOperationCompletedWithSessionAffinitySwitchedFromEnable }, ) } - -func TestCAPTenantUpgradeOperationCompletedWithSessionAffinityEnabledAndPreviousCAVRemovedButDRDeletionFailed(t *testing.T) { - err := reconcileTestItem( - context.TODO(), t, - QueueItem{Key: ResourceCAPTenant, ResourceKey: NamespacedResourceKey{Namespace: "default", Name: "test-cap-01-provider"}}, - TestData{ - description: "captenant upgraded - expecting virtual service, destination rule adjustments after removing previous cav v1 but DR deletion fails for some reason", - initialResources: []string{ - "testdata/common/domain-ready.yaml", - "testdata/common/cluster-domain-ready.yaml", - "testdata/common/capapplication-session-affinity.yaml", - "testdata/common/capapplicationversion-v2.yaml", - "testdata/captenant/cat-with-session-affinity-dr-vs-upgrade.yaml", - }, - mockErrorForResources: []ResourceAction{{Verb: "delete", Group: "networking.istio.io", Version: "v1", Resource: "destinationrules", Namespace: "*", Name: "test-cap-01-provider-test-cap-01-cav-v1"}}, - expectError: true, - }, - ) - - if err.Error() != "mocked api error (destinationrules.networking.istio.io/v1)" { - t.Error("error message is different from expected, actual:", err.Error()) - } -} diff --git a/internal/controller/reconcile-networking.go b/internal/controller/reconcile-networking.go index bb23db15..e0a868ea 100644 --- a/internal/controller/reconcile-networking.go +++ b/internal/controller/reconcile-networking.go @@ -11,7 +11,6 @@ import ( "fmt" "slices" "strings" - "time" "github.com/sap/cap-operator/internal/util" "github.com/sap/cap-operator/pkg/apis/sme.sap.com/v1alpha1" @@ -34,223 +33,138 @@ const ( serviceDNSSuffix = ".svc.cluster.local" setCookie = "Set-Cookie" VersionAffinityCookieName = "CAPOP_CAV" + // Use a different name for sticky cookie than the one from approuter (JSESSIONID) used for session handling + RouterHttpCookieName = "CAPOP_ROUTER_STICKY" ) -func (c *Controller) reconcileTenantNetworking(ctx context.Context, cat *v1alpha1.CAPTenant, cavName string, ca *v1alpha1.CAPApplication) (err error) { - var ( - reason, message string - drModified, vsModified, prevCavDrModified bool - eventType string = corev1.EventTypeNormal - ) - - defer func() { - if err != nil { - eventType = corev1.EventTypeWarning - message = err.Error() - } - if reason != "" { // raise event only when there is a modification or problem - c.Event(cat, nil, eventType, reason, EventActionReconcileTenantNetworking, message) - } - }() - - if drModified, err = c.reconcileTenantDestinationRule(ctx, cat, cat.Name, cavName); err != nil { - util.LogError(err, "Destination rule reconciliation failed", string(Processing), cat, nil, "tenantId", cat.Spec.TenantId, "version", cat.Spec.Version) - reason = CAPTenantEventDestinationRuleModificationFailed - return - } - - // Enable session affinity - if prevCavDrModified, err = c.reconcileTenantDestinationRuleForPrevCav(ctx, ca, cat); err != nil { - util.LogError(err, "Destination rule reconciliation failed for previous cav", string(Processing), cat, nil, "tenantId", cat.Spec.TenantId, "version", cat.Spec.Version) - reason = CAPTenantEventDestinationRuleModificationFailed +// This region is for handling DestinationRule creation for stickiness based on the configuration in CAPApplicationVersion. +// #region Destination Rule for stickiness +func (c *Controller) handleDestinationRule(ctx context.Context, drName string, stickiness *v1alpha1.Stickiness, cav *v1alpha1.CAPApplicationVersion) (err error) { + drSpec := getDestinationRuleFromConfig(drName, cav.Namespace, stickiness) + if drSpec == nil { + // if no valid stickiness configuration is found, ignore creation return } - if vsModified, err = c.reconcileTenantVirtualService(ctx, cat, cavName, ca); err != nil { - util.LogError(err, "Virtual service reconciliation failed", string(Processing), cat, nil, "tenantId", cat.Spec.TenantId, "version", cat.Spec.Version) - reason = CAPTenantEventVirtualServiceModificationFailed - return - } - - // update tenant status - if drModified || vsModified || prevCavDrModified { - message = fmt.Sprintf("VirtualService (and DestinationRule) %s.%s was reconciled", cat.Namespace, cat.Name) - reason = CAPTenantEventTenantNetworkingModified - conditionStatus := metav1.ConditionFalse - if isCROConditionReady(cat.Status.GenericStatus) { - conditionStatus = metav1.ConditionTrue - } - cat.SetStatusWithReadyCondition(cat.Status.State, conditionStatus, CAPTenantEventTenantNetworkingModified, message) - } - - return -} - -func (c *Controller) reconcileTenantDestinationRule(ctx context.Context, cat *v1alpha1.CAPTenant, drName string, cavName string) (modified bool, err error) { - var ( - create, update bool - dr *istionwv1.DestinationRule - ) - dr, err = c.istioClient.NetworkingV1().DestinationRules(cat.Namespace).Get(ctx, drName, metav1.GetOptions{}) + _, err = c.istioInformerFactory.Networking().V1().DestinationRules().Lister().DestinationRules(cav.Namespace).Get(drName) if errors.IsNotFound(err) { - dr = &istionwv1.DestinationRule{ + dr := &istionwv1.DestinationRule{ ObjectMeta: metav1.ObjectMeta{ Name: drName, - Namespace: cat.Namespace, + Namespace: cav.Namespace, Labels: map[string]string{}, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(cat, v1alpha1.SchemeGroupVersion.WithKind(v1alpha1.CAPTenantKind))}, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(cav, v1alpha1.SchemeGroupVersion.WithKind(v1alpha1.CAPApplicationVersionKind))}, }, + Spec: *drSpec.DeepCopy(), } - create = true - } else if err != nil { - return - } - - if update, err = c.getUpdatedTenantDestinationRuleObject(cat, dr, cavName); err != nil { - util.LogError(err, "", string(Processing), cat, dr, "tenantId", cat.Spec.TenantId, "version", cat.Spec.Version) - return - } - if create { - util.LogInfo("Creating destination rule", string(Processing), cat, dr, "tenantId", cat.Spec.TenantId, "version", cat.Spec.Version) - _, err = c.istioClient.NetworkingV1().DestinationRules(cat.Namespace).Create(ctx, dr, metav1.CreateOptions{}) - } else if update { - util.LogInfo("Updating destination rule", string(Processing), cat, dr, "tenantId", cat.Spec.TenantId, "version", cat.Spec.Version) - _, err = c.istioClient.NetworkingV1().DestinationRules(cat.Namespace).Update(ctx, dr, metav1.UpdateOptions{}) + util.LogInfo("Creating destination rule", string(Processing), cav, dr, "version", cav.Spec.Version) + _, err = c.istioClient.NetworkingV1().DestinationRules(cav.Namespace).Create(ctx, dr, metav1.CreateOptions{}) } - return create || update, err + return err } -func (c *Controller) reconcileTenantDestinationRuleForPrevCav(ctx context.Context, ca *v1alpha1.CAPApplication, cat *v1alpha1.CAPTenant) (modified bool, err error) { - if len(cat.Status.PreviousCAPApplicationVersions) == 0 { - return false, nil - } - - if ca.Annotations[AnnotationEnableVersionAffinity] == "true" { - return c.handleSessionAffinityEnabled(ctx, cat) +func getDestinationRuleFromConfig(drName, namespace string, stickiness *v1alpha1.Stickiness) *networkingv1.DestinationRule { + if stickiness.Hash == nil { + return nil } - - return c.cleanupAllPreviousCavDRs(ctx, cat) -} - -func (c *Controller) handleSessionAffinityEnabled(ctx context.Context, cat *v1alpha1.CAPTenant) (bool, error) { - var modified bool - var err error - prevCav := cat.Status.PreviousCAPApplicationVersions[len(cat.Status.PreviousCAPApplicationVersions)-1] - - // Check if previous CAV exists - _, cavGetErr := c.crdInformerFactory.Sme().V1alpha1().CAPApplicationVersions().Lister().CAPApplicationVersions(cat.Namespace).Get(prevCav) - switch { - case errors.IsNotFound(cavGetErr): - // CAV doesn't exist, cleanup its DR - modified, err = c.deleteDRIfExists(ctx, cat.Namespace, cat.Name+"-"+prevCav) - if err != nil { - return false, err + hash := stickiness.Hash + var lbHash *networkingv1.LoadBalancerSettings_ConsistentHashLB + if hash.HttpHeaderName != "" { + lbHash = &networkingv1.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &networkingv1.LoadBalancerSettings_ConsistentHashLB_HttpHeaderName{ + HttpHeaderName: hash.HttpHeaderName, + }, } - case cavGetErr != nil: - // Some other error occurred while fetching CAV - return false, cavGetErr - default: - // CAV exists, reconcile its DR - modified, err = c.reconcileTenantDestinationRule(ctx, cat, cat.Name+"-"+prevCav, prevCav) - if err != nil { - return false, err + } else if hash.HttpCookie != nil { + var ttl *durationpb.Duration + if hash.HttpCookie.Ttl != nil { + ttl = durationpb.New(hash.HttpCookie.Ttl.Duration) } - } - - // Clean up second-to-last CAV DR if it exists - if len(cat.Status.PreviousCAPApplicationVersions) > 1 { - secondLastCav := cat.Status.PreviousCAPApplicationVersions[len(cat.Status.PreviousCAPApplicationVersions)-2] - drDeleted, err := c.deleteDRIfExists(ctx, cat.Namespace, cat.Name+"-"+secondLastCav) - if err != nil { - return false, err + var attributes []*networkingv1.LoadBalancerSettings_ConsistentHashLB_HTTPCookie_Attribute + for _, attr := range hash.HttpCookie.Attributes { + attributes = append(attributes, &networkingv1.LoadBalancerSettings_ConsistentHashLB_HTTPCookie_Attribute{ + Name: attr.Name, + Value: attr.Value, + }) + } + lbHash = &networkingv1.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &networkingv1.LoadBalancerSettings_ConsistentHashLB_HttpCookie{ + HttpCookie: &networkingv1.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{ + Name: hash.HttpCookie.Name, + Path: hash.HttpCookie.Path, + Ttl: ttl, + Attributes: attributes, + }, + }, + } + } else if hash.HttpQueryParameterName != "" { + lbHash = &networkingv1.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &networkingv1.LoadBalancerSettings_ConsistentHashLB_HttpQueryParameterName{ + HttpQueryParameterName: hash.HttpQueryParameterName, + }, + } + } else if hash.UseSourceIp { + lbHash = &networkingv1.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &networkingv1.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{ + UseSourceIp: trueVal, + }, } - modified = modified || drDeleted - } - - return modified, nil -} - -func (c *Controller) cleanupAllPreviousCavDRs(ctx context.Context, cat *v1alpha1.CAPTenant) (bool, error) { - drNames := make(map[string]struct{}) - for _, cav := range cat.Status.PreviousCAPApplicationVersions { - drNames[cat.Name+"-"+cav] = struct{}{} - } - - drList, err := c.istioClient.NetworkingV1().DestinationRules(cat.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return false, err } - var modified bool - for _, dr := range drList.Items { - if _, exists := drNames[dr.Name]; exists { - if err := c.istioClient.NetworkingV1().DestinationRules(cat.Namespace).Delete(ctx, dr.Name, metav1.DeleteOptions{}); err != nil { - return false, err - } - modified = true + if lbHash != nil { + return &networkingv1.DestinationRule{ + Host: drName + ServiceSuffix + "." + namespace + serviceDNSSuffix, + TrafficPolicy: &networkingv1.TrafficPolicy{ + LoadBalancer: &networkingv1.LoadBalancerSettings{ + LbPolicy: &networkingv1.LoadBalancerSettings_ConsistentHash{ + ConsistentHash: lbHash, + }, + }, + }, } } - return modified, nil + return nil } -func (c *Controller) deleteDRIfExists(ctx context.Context, namespace, drName string) (bool, error) { - err := c.istioClient.NetworkingV1().DestinationRules(namespace).Delete(ctx, drName, metav1.DeleteOptions{}) - switch { - case errors.IsNotFound(err): - return false, nil // Nothing to delete - case err != nil: - return false, err // Unexpected error - default: - return true, nil // Deleted successfully - } -} +// #endregion -func (c *Controller) getUpdatedTenantDestinationRuleObject(cat *v1alpha1.CAPTenant, dr *istionwv1.DestinationRule, cavName string) (modified bool, err error) { - // verify owner reference - modified, err = c.enforceTenantResourceOwnership(&dr.ObjectMeta, &dr.TypeMeta, cat) - if err != nil { - return modified, err - } +func (c *Controller) reconcileTenantNetworking(ctx context.Context, cat *v1alpha1.CAPTenant, cavName string, ca *v1alpha1.CAPApplication) (err error) { + var ( + reason, message string + vsModified bool + eventType string = corev1.EventTypeNormal + ) - routerPortInfo, err := c.getRouterServicePortInfo(cavName, cat.Namespace) - if err != nil { - return modified, err - } + defer func() { + if err != nil { + eventType = corev1.EventTypeWarning + message = err.Error() + } + if reason != "" { // raise event only when there is a modification or problem + c.Event(cat, nil, eventType, reason, EventActionReconcileTenantNetworking, message) + } + }() - spec := &networkingv1.DestinationRule{ - Host: routerPortInfo.WorkloadName + ServiceSuffix + "." + cat.Namespace + serviceDNSSuffix, - TrafficPolicy: &networkingv1.TrafficPolicy{ - LoadBalancer: &networkingv1.LoadBalancerSettings{ - LbPolicy: &networkingv1.LoadBalancerSettings_ConsistentHash{ - ConsistentHash: &networkingv1.LoadBalancerSettings_ConsistentHashLB{ - HashKey: &networkingv1.LoadBalancerSettings_ConsistentHashLB_HttpCookie{ - HttpCookie: &networkingv1.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{ - Name: RouterHttpCookieName, - Ttl: durationpb.New(0 * time.Second), - Path: "/", - }, - }, - }, - }, - }, - }, + if vsModified, err = c.reconcileTenantVirtualService(ctx, cat, cavName, ca); err != nil { + util.LogError(err, "Virtual service reconciliation failed", string(Processing), cat, nil, "tenantId", cat.Spec.TenantId, "version", cat.Spec.Version) + reason = CAPTenantEventVirtualServiceModificationFailed + return } - // check whether changes have to be applied using hash comparison - serializedSpec, err := json.Marshal(spec) - if err != nil { - return modified, fmt.Errorf("error serializing destination rule spec: %s", err.Error()) - } - hash := sha256Sum(string(serializedSpec)) - if dr.Annotations[AnnotationResourceHash] != hash { - dr.Spec = *spec.DeepCopy() - updateResourceAnnotation(&dr.ObjectMeta, hash) - modified = true + // update tenant status + if vsModified { + message = fmt.Sprintf("VirtualService %s.%s was reconciled", cat.Namespace, cat.Name) + reason = CAPTenantEventTenantNetworkingModified + conditionStatus := metav1.ConditionFalse + if isCROConditionReady(cat.Status.GenericStatus) { + conditionStatus = metav1.ConditionTrue + } + cat.SetStatusWithReadyCondition(cat.Status.State, conditionStatus, CAPTenantEventTenantNetworkingModified, message) } - return modified, err + return } func (c *Controller) reconcileTenantVirtualService(ctx context.Context, cat *v1alpha1.CAPTenant, cavName string, ca *v1alpha1.CAPApplication) (modified bool, err error) { diff --git a/internal/controller/reconcile.go b/internal/controller/reconcile.go index 0297d52d..03bf2310 100644 --- a/internal/controller/reconcile.go +++ b/internal/controller/reconcile.go @@ -81,9 +81,6 @@ var ( const TenantTypeProvider = "provider" -// Use a different name for sticky cookie than the one from approuter (JSESSIONID) used for session handling -const RouterHttpCookieName = "CAPOP_ROUTER_STICKY" - const ( EnvCAPOpAppVersion = "CAPOP_APP_VERSION" EnvCAPOpTenantId = "CAPOP_TENANT_ID" diff --git a/internal/controller/testdata/capapplicationversion/cav-stickiness-http-cookie-custom.yaml b/internal/controller/testdata/capapplicationversion/cav-stickiness-http-cookie-custom.yaml new file mode 100644 index 00000000..a2d80524 --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/cav-stickiness-http-cookie-custom.yaml @@ -0,0 +1,73 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + httpCookie: + name: MY_STICKY_COOKIE + path: /api + ttl: 3600s + attributes: + - name: SameSite + value: Strict + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + state: Processing diff --git a/internal/controller/testdata/capapplicationversion/cav-stickiness-http-header.yaml b/internal/controller/testdata/capapplicationversion/cav-stickiness-http-header.yaml new file mode 100644 index 00000000..0d471b8a --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/cav-stickiness-http-header.yaml @@ -0,0 +1,67 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + httpHeaderName: X-User-ID + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + state: Processing diff --git a/internal/controller/testdata/capapplicationversion/cav-stickiness-query-param.yaml b/internal/controller/testdata/capapplicationversion/cav-stickiness-query-param.yaml new file mode 100644 index 00000000..f582fed4 --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/cav-stickiness-query-param.yaml @@ -0,0 +1,67 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + httpQueryParameterName: session_id + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + state: Processing diff --git a/internal/controller/testdata/capapplicationversion/cav-stickiness-source-ip.yaml b/internal/controller/testdata/capapplicationversion/cav-stickiness-source-ip.yaml new file mode 100644 index 00000000..07db82a2 --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/cav-stickiness-source-ip.yaml @@ -0,0 +1,67 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + useSourceIp: true + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + state: Processing diff --git a/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-http-cookie-custom.yaml b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-http-cookie-custom.yaml new file mode 100644 index 00000000..15be0fb5 --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-http-cookie-custom.yaml @@ -0,0 +1,199 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + finalizers: + - "sme.sap.com/capapplicationversion" + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + httpCookie: + name: MY_STICKY_COOKIE + path: /api + ttl: 3600s + attributes: + - name: SameSite + value: Strict + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + conditions: + - reason: WaitingForWorkloads + observedGeneration: 1 + status: "False" + type: Ready + observedGeneration: 1 + finishedJobs: + - "test-cap-01-cav-v1-content" + state: Processing +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + selector: + matchLabels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + template: + metadata: + creationTimestamp: null + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + spec: + containers: + - env: + - name: CAPOP_APP_VERSION + value: "1.2.3" + - name: destinations + value: '[{"name":"srv-api","url":"http://test-cap-01-cav-v1-cap-backend-srv-svc:4000","forwardAuthToken":true}]' + envFrom: + - secretRef: + name: test-cap-01-cav-v1-app-router-gen + optional: true + image: docker.image.repo/approuter/approuter:latest + name: app-router + imagePullSecrets: + - name: regcred +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpCookie: + name: CAPOP_ROUTER_STICKY + path: / + ttl: 0s +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-cap-backend-srv + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-cap-backend-srv-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpCookie: + name: MY_STICKY_COOKIE + path: /api + ttl: 3600s + attributes: + - name: SameSite + value: Strict diff --git a/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-http-header.yaml b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-http-header.yaml new file mode 100644 index 00000000..be234360 --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-http-header.yaml @@ -0,0 +1,187 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + finalizers: + - "sme.sap.com/capapplicationversion" + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + httpHeaderName: X-User-ID + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + conditions: + - reason: WaitingForWorkloads + observedGeneration: 1 + status: "False" + type: Ready + observedGeneration: 1 + finishedJobs: + - "test-cap-01-cav-v1-content" + state: Processing +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + selector: + matchLabels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + template: + metadata: + creationTimestamp: null + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + spec: + containers: + - env: + - name: CAPOP_APP_VERSION + value: "1.2.3" + - name: destinations + value: '[{"name":"srv-api","url":"http://test-cap-01-cav-v1-cap-backend-srv-svc:4000","forwardAuthToken":true}]' + envFrom: + - secretRef: + name: test-cap-01-cav-v1-app-router-gen + optional: true + image: docker.image.repo/approuter/approuter:latest + name: app-router + imagePullSecrets: + - name: regcred +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpCookie: + name: CAPOP_ROUTER_STICKY + path: / + ttl: 0s +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-cap-backend-srv + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-cap-backend-srv-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpHeaderName: X-User-ID diff --git a/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-query-param.yaml b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-query-param.yaml new file mode 100644 index 00000000..962bdc68 --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-query-param.yaml @@ -0,0 +1,187 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + finalizers: + - "sme.sap.com/capapplicationversion" + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + httpQueryParameterName: session_id + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + conditions: + - reason: WaitingForWorkloads + observedGeneration: 1 + status: "False" + type: Ready + observedGeneration: 1 + finishedJobs: + - "test-cap-01-cav-v1-content" + state: Processing +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + selector: + matchLabels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + template: + metadata: + creationTimestamp: null + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + spec: + containers: + - env: + - name: CAPOP_APP_VERSION + value: "1.2.3" + - name: destinations + value: '[{"name":"srv-api","url":"http://test-cap-01-cav-v1-cap-backend-srv-svc:4000","forwardAuthToken":true}]' + envFrom: + - secretRef: + name: test-cap-01-cav-v1-app-router-gen + optional: true + image: docker.image.repo/approuter/approuter:latest + name: app-router + imagePullSecrets: + - name: regcred +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpCookie: + name: CAPOP_ROUTER_STICKY + path: / + ttl: 0s +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-cap-backend-srv + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-cap-backend-srv-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpQueryParameterName: session_id diff --git a/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-source-ip.yaml b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-source-ip.yaml new file mode 100644 index 00000000..1fffffed --- /dev/null +++ b/internal/controller/testdata/capapplicationversion/expected/cav-ready-stickiness-source-ip.yaml @@ -0,0 +1,187 @@ +apiVersion: sme.sap.com/v1alpha1 +kind: CAPApplicationVersion +metadata: + creationTimestamp: "2022-07-18T06:13:52Z" + generation: 1 + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01 + labels: + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/owner-generation: "2" + sme.sap.com/owner-identifier-hash: 1f74ae2fbff71a708786a4df4bb2ca87ec603581 + name: test-cap-01-cav-v1 + namespace: default + finalizers: + - "sme.sap.com/capapplicationversion" + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplication + name: test-cap-01 + uid: 3c7ba7cb-dc04-4fd1-be86-3eb3a5c64a98 + resourceVersion: "11371108" + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + capApplicationInstance: test-cap-01 + registrySecrets: + - regcred + version: 1.2.3 + workloads: + - name: cap-backend-srv + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/srv/server:latest + type: CAP + ports: + - name: server-app-port + port: 4000 + routerDestinationName: srv-api + stickiness: + hash: + useSourceIp: true + - name: app-router + consumedBTPServices: + - cap-uaa + - cap-saas-registry + deploymentDefinition: + image: docker.image.repo/approuter/approuter:latest + type: Router + - name: content-job + consumedBTPServices: + - cap-uaa + jobDefinition: + image: docker.image.repo/content/cap-content:latest + type: Content + - name: mtx-job + consumedBTPServices: + - cap-uaa + - cap-service-manager + - cap-saas-registry + jobDefinition: + image: docker.image.repo/srv/server:latest + type: TenantOperation +status: + conditions: + - reason: WaitingForWorkloads + observedGeneration: 1 + status: "False" + type: Ready + observedGeneration: 1 + finishedJobs: + - "test-cap-01-cav-v1-content" + state: Processing +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + selector: + matchLabels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + template: + metadata: + creationTimestamp: null + labels: + app: test-cap-01 + sme.sap.com/category: Workload + sme.sap.com/workload-name: test-cap-01-cav-v1-app-router + sme.sap.com/workload-type: Router + sme.sap.com/exposed-workload: "true" + sme.sap.com/btp-app-identifier-hash: f20cc8aeb2003b3abc33f749a16bd53544b6bab2 + sme.sap.com/cav-version: "1.2.3" + sme.sap.com/owner-generation: "1" + sme.sap.com/owner-identifier-hash: e95e0682f33a657e75e1fc435972d19bd407ba3b + annotations: + sme.sap.com/btp-app-identifier: btp-glo-acc-id.test-cap-01 + sme.sap.com/owner-identifier: default.test-cap-01-cav-v1 + spec: + containers: + - env: + - name: CAPOP_APP_VERSION + value: "1.2.3" + - name: destinations + value: '[{"name":"srv-api","url":"http://test-cap-01-cav-v1-cap-backend-srv-svc:4000","forwardAuthToken":true}]' + envFrom: + - secretRef: + name: test-cap-01-cav-v1-app-router-gen + optional: true + image: docker.image.repo/approuter/approuter:latest + name: app-router + imagePullSecrets: + - name: regcred +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpCookie: + name: CAPOP_ROUTER_STICKY + path: / + ttl: 0s +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-cap-backend-srv + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-cap-backend-srv-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + useSourceIp: true diff --git a/internal/controller/testdata/capapplicationversion/expected/cav-ready-valid-env-config.yaml b/internal/controller/testdata/capapplicationversion/expected/cav-ready-valid-env-config.yaml index e89b18a1..e6acf846 100644 --- a/internal/controller/testdata/capapplicationversion/expected/cav-ready-valid-env-config.yaml +++ b/internal/controller/testdata/capapplicationversion/expected/cav-ready-valid-env-config.yaml @@ -175,3 +175,25 @@ spec: name: app-router imagePullSecrets: - name: regcred +--- +apiVersion: networking.istio.io/v1 +kind: DestinationRule +metadata: + name: test-cap-01-cav-v1-app-router + namespace: default + ownerReferences: + - apiVersion: sme.sap.com/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CAPApplicationVersion + name: test-cap-01-cav-v1 + uid: 5e64489b-7346-4984-8617-e8c37338b3d8 +spec: + host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local + trafficPolicy: + loadBalancer: + consistentHash: + httpCookie: + name: CAPOP_ROUTER_STICKY + path: / + ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-04.expected.yaml b/internal/controller/testdata/captenant/cat-04.expected.yaml index 3aec59a0..5929a208 100644 --- a/internal/controller/testdata/captenant/cat-04.expected.yaml +++ b/internal/controller/testdata/captenant/cat-04.expected.yaml @@ -71,31 +71,4 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 76ac6b80ce55711ae052011d8d29727030c897d4869ba6c403ac6842f08b93d6 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "0" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s +--- \ No newline at end of file diff --git a/internal/controller/testdata/captenant/cat-13.expected.yaml b/internal/controller/testdata/captenant/cat-13.expected.yaml index 0e92d90e..277703ba 100644 --- a/internal/controller/testdata/captenant/cat-13.expected.yaml +++ b/internal/controller/testdata/captenant/cat-13.expected.yaml @@ -75,33 +75,4 @@ spec: host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local port: number: 5000 - weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - generation: 1 - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s + weight: 100 \ No newline at end of file diff --git a/internal/controller/testdata/captenant/cat-15.expected.yaml b/internal/controller/testdata/captenant/cat-15.expected.yaml index 314998ea..ee2cc660 100644 --- a/internal/controller/testdata/captenant/cat-15.expected.yaml +++ b/internal/controller/testdata/captenant/cat-15.expected.yaml @@ -30,7 +30,7 @@ spec: versionUpgradeStrategy: always status: conditions: - - message: "VirtualService (and DestinationRule) default.test-cap-01-provider was reconciled" + - message: "VirtualService default.test-cap-01-provider was reconciled" reason: TenantNetworkingModified observedGeneration: 3 status: "True" @@ -74,31 +74,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "3" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-30.expected.yaml b/internal/controller/testdata/captenant/cat-30.expected.yaml index d48a8759..d950543c 100644 --- a/internal/controller/testdata/captenant/cat-30.expected.yaml +++ b/internal/controller/testdata/captenant/cat-30.expected.yaml @@ -30,7 +30,7 @@ spec: versionUpgradeStrategy: always status: conditions: - - message: "VirtualService (and DestinationRule) default.test-cap-01-provider was reconciled" + - message: "VirtualService default.test-cap-01-provider was reconciled" reason: TenantNetworkingModified observedGeneration: 3 status: "True" @@ -82,31 +82,3 @@ spec: set: foo: bar weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "3" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-disabled-dr-vs.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-disabled-dr-vs.yaml index 6733bd01..52c52071 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-disabled-dr-vs.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-disabled-dr-vs.yaml @@ -30,7 +30,7 @@ spec: versionUpgradeStrategy: always status: conditions: - - message: "VirtualService (and DestinationRule) default.test-cap-01-provider was reconciled" + - message: "VirtualService default.test-cap-01-provider was reconciled" reason: TenantNetworkingModified observedGeneration: 2 status: "True" @@ -76,32 +76,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - generation: 1 - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-headers.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-headers.yaml index b0876d28..3562e1ba 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-headers.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-headers.yaml @@ -109,31 +109,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 76ac6b80ce55711ae052011d8d29727030c897d4869ba6c403ac6842f08b93d6 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "0" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-logout-endpoint.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-logout-endpoint.yaml index 7f50f8d9..8729f0ee 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-logout-endpoint.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-logout-endpoint.yaml @@ -98,31 +98,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 76ac6b80ce55711ae052011d8d29727030c897d4869ba6c403ac6842f08b93d6 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "0" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-prev-cav-removed.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-prev-cav-removed.yaml index 9754eea8..c39352c9 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-prev-cav-removed.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-prev-cav-removed.yaml @@ -31,7 +31,7 @@ spec: status: observedGeneration: 2 conditions: - - message: "VirtualService (and DestinationRule) default.test-cap-01-provider was reconciled" + - message: "VirtualService default.test-cap-01-provider was reconciled" observedGeneration: 2 reason: TenantNetworkingModified status: "True" @@ -103,32 +103,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - generation: 1 - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.expected.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.expected.yaml index c4c06371..31e2106c 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.expected.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.expected.yaml @@ -31,7 +31,7 @@ spec: status: observedGeneration: 2 conditions: - - message: "VirtualService (and DestinationRule) default.test-cap-01-provider was reconciled" + - message: "VirtualService default.test-cap-01-provider was reconciled" observedGeneration: 2 reason: TenantNetworkingModified status: "True" @@ -130,60 +130,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 921a053e719ece9bc922497f98452ffd3ad4d057039fd831e3e46da92c4d2df4 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - generation: 1 - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v3-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider-test-cap-01-cav-v2 - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.yaml index 03e68f20..3fbd946e 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade-to-cav-v3.yaml @@ -31,7 +31,7 @@ spec: status: observedGeneration: 2 conditions: - - message: "VirtualService (and DestinationRule) default.test-cap-01-provider was reconciled" + - message: "VirtualService default.test-cap-01-provider was reconciled" observedGeneration: 2 reason: TenantNetworkingModified status: "True" @@ -166,58 +166,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - generation: 1 - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 76ac6b80ce55711ae052011d8d29727030c897d4869ba6c403ac6842f08b93d6 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - name: test-cap-01-provider-test-cap-01-cav-v1 - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade.yaml index c6254541..813394d5 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs-upgrade.yaml @@ -129,60 +129,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 97ecccd1443b2219a85159a5fdb2f8444543746a5ae890114462e6666e556696 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - generation: 1 - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v2-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 76ac6b80ce55711ae052011d8d29727030c897d4869ba6c403ac6842f08b93d6 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "2" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider-test-cap-01-cav-v1 - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs.yaml b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs.yaml index 85d55b35..9657fca8 100644 --- a/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs.yaml +++ b/internal/controller/testdata/captenant/cat-with-session-affinity-dr-vs.yaml @@ -98,31 +98,3 @@ spec: port: number: 5000 weight: 100 ---- -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 76ac6b80ce55711ae052011d8d29727030c897d4869ba6c403ac6842f08b93d6 - sme.sap.com/owner-identifier: default.test-cap-01-provider - labels: - sme.sap.com/owner-generation: "0" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/internal/controller/testdata/captenant/provider-tenant-dr-v1.yaml b/internal/controller/testdata/captenant/provider-tenant-dr-v1.yaml deleted file mode 100644 index b1f93e60..00000000 --- a/internal/controller/testdata/captenant/provider-tenant-dr-v1.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: networking.istio.io/v1 -kind: DestinationRule -metadata: - annotations: - sme.sap.com/resource-hash: 76ac6b80ce55711ae052011d8d29727030c897d4869ba6c403ac6842f08b93d6 - sme.sap.com/owner-identifier: default.test-cap-01-provider - generation: 1 - labels: - sme.sap.com/owner-generation: "0" - sme.sap.com/owner-identifier-hash: db1f1fd7eaeb0e6407c741b7e4b2540044bcc4ec - sme.sap.com/tenant-type: provider - name: test-cap-01-provider - namespace: default - ownerReferences: - - apiVersion: sme.sap.com/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: CAPTenant - name: test-cap-01-provider -spec: - host: test-cap-01-cav-v1-app-router-svc.default.svc.cluster.local - trafficPolicy: - loadBalancer: - consistentHash: - httpCookie: - name: CAPOP_ROUTER_STICKY - path: / - ttl: 0s diff --git a/pkg/apis/sme.sap.com/v1alpha1/types.go b/pkg/apis/sme.sap.com/v1alpha1/types.go index 1a6136ef..0e174bcc 100644 --- a/pkg/apis/sme.sap.com/v1alpha1/types.go +++ b/pkg/apis/sme.sap.com/v1alpha1/types.go @@ -290,6 +290,40 @@ type DeploymentDetails struct { PodDisruptionBudget *policyv1.PodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"` // Horizontal Pod Autoscaler may be used to specify the scaling behavior for this workload HorizontalPodAutoscaler *HorizontalPodAutoscalerSpec `json:"horizontalPodAutoscaler,omitempty"` + // Stickiness configuration based on istio consistent hashing for the workload. When present, this is used to create a DestinationRule for the workload. + Stickiness *Stickiness `json:"stickiness,omitempty"` +} + +type Stickiness struct { + // Hash based stickiness configuration. + Hash *StickinessHash `json:"hash,omitempty"` +} + +// Loosely based on the options available for consistent hash load balancer in istio (https://istio.io/latest/docs/reference/config/networking/destination-rule/#ConsistentHashLB), but only include the options that make sense for our use case. +// +kubebuilder:validation:AtMostOneOf=httpHeaderName;httpCookie;useSourceIp;httpQueryParameterName +type StickinessHash struct { + // Hash based on a specific HTTP header. + HttpHeaderName string `json:"httpHeaderName,omitempty"` + // Hash based on HTTP cookie. + HttpCookie *HTTPCookie `json:"httpCookie,omitempty"` + // Hash based on the source IP address. + // This is applicable for both TCP and HTTP connections. + UseSourceIp bool `json:"useSourceIp,omitempty"` + // Hash based on a specific HTTP query parameter. + HttpQueryParameterName string `json:"httpQueryParameterName,omitempty"` +} + +type HTTPCookie struct { + // Name of the cookie. + Name string `json:"name,omitempty"` + // Path to set for the cookie. + Path string `json:"path,omitempty"` + // Lifetime of the cookie. If specified, a cookie with the TTL will be + // generated if the cookie is not present. If the TTL is present and zero, + // the generated cookie will be a session cookie. + Ttl *metav1.Duration `json:"ttl,omitempty"` + // Additional attributes for the cookie. They will be used when generating a new cookie. + Attributes []NameValue `json:"attributes,omitempty"` } // HorizontalPodAutoscalerSpec wraps autoscalingv2.HorizontalPodAutoscalerSpec but gets rid of scaleTargetRef, diff --git a/pkg/apis/sme.sap.com/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/sme.sap.com/v1alpha1/zz_generated.deepcopy.go index f012abcc..f9154644 100644 --- a/pkg/apis/sme.sap.com/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/sme.sap.com/v1alpha1/zz_generated.deepcopy.go @@ -880,6 +880,11 @@ func (in *DeploymentDetails) DeepCopyInto(out *DeploymentDetails) { *out = new(HorizontalPodAutoscalerSpec) (*in).DeepCopyInto(*out) } + if in.Stickiness != nil { + in, out := &in.Stickiness, &out.Stickiness + *out = new(Stickiness) + (*in).DeepCopyInto(*out) + } return } @@ -1043,6 +1048,32 @@ func (in *GenericStatus) DeepCopy() *GenericStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPCookie) DeepCopyInto(out *HTTPCookie) { + *out = *in + if in.Ttl != nil { + in, out := &in.Ttl, &out.Ttl + *out = new(metav1.Duration) + **out = **in + } + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make([]NameValue, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPCookie. +func (in *HTTPCookie) DeepCopy() *HTTPCookie { + if in == nil { + return nil + } + out := new(HTTPCookie) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HorizontalPodAutoscalerSpec) DeepCopyInto(out *HorizontalPodAutoscalerSpec) { *out = *in @@ -1230,6 +1261,48 @@ func (in *ServiceInfo) DeepCopy() *ServiceInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Stickiness) DeepCopyInto(out *Stickiness) { + *out = *in + if in.Hash != nil { + in, out := &in.Hash, &out.Hash + *out = new(StickinessHash) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Stickiness. +func (in *Stickiness) DeepCopy() *Stickiness { + if in == nil { + return nil + } + out := new(Stickiness) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StickinessHash) DeepCopyInto(out *StickinessHash) { + *out = *in + if in.HttpCookie != nil { + in, out := &in.HttpCookie, &out.HttpCookie + *out = new(HTTPCookie) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StickinessHash. +func (in *StickinessHash) DeepCopy() *StickinessHash { + if in == nil { + return nil + } + out := new(StickinessHash) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantOperationWorkloadReference) DeepCopyInto(out *TenantOperationWorkloadReference) { *out = *in diff --git a/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/deploymentdetails.go b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/deploymentdetails.go index 2de53025..4a184c29 100644 --- a/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/deploymentdetails.go +++ b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/deploymentdetails.go @@ -35,6 +35,8 @@ type DeploymentDetailsApplyConfiguration struct { PodDisruptionBudget *policyv1.PodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"` // Horizontal Pod Autoscaler may be used to specify the scaling behavior for this workload HorizontalPodAutoscaler *HorizontalPodAutoscalerSpecApplyConfiguration `json:"horizontalPodAutoscaler,omitempty"` + // Stickiness configuration based on istio consistent hashing for the workload. When present, this is used to create a DestinationRule for the workload. + Stickiness *StickinessApplyConfiguration `json:"stickiness,omitempty"` } // DeploymentDetailsApplyConfiguration constructs a declarative configuration of the DeploymentDetails type for use with @@ -285,3 +287,11 @@ func (b *DeploymentDetailsApplyConfiguration) WithHorizontalPodAutoscaler(value b.HorizontalPodAutoscaler = value return b } + +// WithStickiness sets the Stickiness field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Stickiness field is set to the value of the last call. +func (b *DeploymentDetailsApplyConfiguration) WithStickiness(value *StickinessApplyConfiguration) *DeploymentDetailsApplyConfiguration { + b.Stickiness = value + return b +} diff --git a/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/httpcookie.go b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/httpcookie.go new file mode 100644 index 00000000..e7b25f38 --- /dev/null +++ b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/httpcookie.go @@ -0,0 +1,70 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and cap-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// HTTPCookieApplyConfiguration represents a declarative configuration of the HTTPCookie type for use +// with apply. +type HTTPCookieApplyConfiguration struct { + // Name of the cookie. + Name *string `json:"name,omitempty"` + // Path to set for the cookie. + Path *string `json:"path,omitempty"` + // Lifetime of the cookie. If specified, a cookie with the TTL will be + // generated if the cookie is not present. If the TTL is present and zero, + // the generated cookie will be a session cookie. + Ttl *v1.Duration `json:"ttl,omitempty"` + // Additional attributes for the cookie. They will be used when generating a new cookie. + Attributes []NameValueApplyConfiguration `json:"attributes,omitempty"` +} + +// HTTPCookieApplyConfiguration constructs a declarative configuration of the HTTPCookie type for use with +// apply. +func HTTPCookie() *HTTPCookieApplyConfiguration { + return &HTTPCookieApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *HTTPCookieApplyConfiguration) WithName(value string) *HTTPCookieApplyConfiguration { + b.Name = &value + return b +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *HTTPCookieApplyConfiguration) WithPath(value string) *HTTPCookieApplyConfiguration { + b.Path = &value + return b +} + +// WithTtl sets the Ttl field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ttl field is set to the value of the last call. +func (b *HTTPCookieApplyConfiguration) WithTtl(value v1.Duration) *HTTPCookieApplyConfiguration { + b.Ttl = &value + return b +} + +// WithAttributes adds the given value to the Attributes field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Attributes field. +func (b *HTTPCookieApplyConfiguration) WithAttributes(values ...*NameValueApplyConfiguration) *HTTPCookieApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithAttributes") + } + b.Attributes = append(b.Attributes, *values[i]) + } + return b +} diff --git a/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/stickiness.go b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/stickiness.go new file mode 100644 index 00000000..52a38c82 --- /dev/null +++ b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/stickiness.go @@ -0,0 +1,29 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and cap-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// StickinessApplyConfiguration represents a declarative configuration of the Stickiness type for use +// with apply. +type StickinessApplyConfiguration struct { + // Hash based stickiness configuration. + Hash *StickinessHashApplyConfiguration `json:"hash,omitempty"` +} + +// StickinessApplyConfiguration constructs a declarative configuration of the Stickiness type for use with +// apply. +func Stickiness() *StickinessApplyConfiguration { + return &StickinessApplyConfiguration{} +} + +// WithHash sets the Hash field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Hash field is set to the value of the last call. +func (b *StickinessApplyConfiguration) WithHash(value *StickinessHashApplyConfiguration) *StickinessApplyConfiguration { + b.Hash = value + return b +} diff --git a/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/stickinesshash.go b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/stickinesshash.go new file mode 100644 index 00000000..cdb396af --- /dev/null +++ b/pkg/client/applyconfiguration/sme.sap.com/v1alpha1/stickinesshash.go @@ -0,0 +1,62 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and cap-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// StickinessHashApplyConfiguration represents a declarative configuration of the StickinessHash type for use +// with apply. +// +// Loosely based on the options available for consistent hash load balancer in istio (https://istio.io/latest/docs/reference/config/networking/destination-rule/#ConsistentHashLB), but only include the options that make sense for our use case. +type StickinessHashApplyConfiguration struct { + // Hash based on a specific HTTP header. + HttpHeaderName *string `json:"httpHeaderName,omitempty"` + // Hash based on HTTP cookie. + HttpCookie *HTTPCookieApplyConfiguration `json:"httpCookie,omitempty"` + // Hash based on the source IP address. + // This is applicable for both TCP and HTTP connections. + UseSourceIp *bool `json:"useSourceIp,omitempty"` + // Hash based on a specific HTTP query parameter. + HttpQueryParameterName *string `json:"httpQueryParameterName,omitempty"` +} + +// StickinessHashApplyConfiguration constructs a declarative configuration of the StickinessHash type for use with +// apply. +func StickinessHash() *StickinessHashApplyConfiguration { + return &StickinessHashApplyConfiguration{} +} + +// WithHttpHeaderName sets the HttpHeaderName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HttpHeaderName field is set to the value of the last call. +func (b *StickinessHashApplyConfiguration) WithHttpHeaderName(value string) *StickinessHashApplyConfiguration { + b.HttpHeaderName = &value + return b +} + +// WithHttpCookie sets the HttpCookie field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HttpCookie field is set to the value of the last call. +func (b *StickinessHashApplyConfiguration) WithHttpCookie(value *HTTPCookieApplyConfiguration) *StickinessHashApplyConfiguration { + b.HttpCookie = value + return b +} + +// WithUseSourceIp sets the UseSourceIp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UseSourceIp field is set to the value of the last call. +func (b *StickinessHashApplyConfiguration) WithUseSourceIp(value bool) *StickinessHashApplyConfiguration { + b.UseSourceIp = &value + return b +} + +// WithHttpQueryParameterName sets the HttpQueryParameterName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HttpQueryParameterName field is set to the value of the last call. +func (b *StickinessHashApplyConfiguration) WithHttpQueryParameterName(value string) *StickinessHashApplyConfiguration { + b.HttpQueryParameterName = &value + return b +} diff --git a/pkg/client/applyconfiguration/utils.go b/pkg/client/applyconfiguration/utils.go index 2a089b5b..dbddba21 100644 --- a/pkg/client/applyconfiguration/utils.go +++ b/pkg/client/applyconfiguration/utils.go @@ -81,6 +81,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &smesapcomv1alpha1.GenericStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("HorizontalPodAutoscalerSpec"): return &smesapcomv1alpha1.HorizontalPodAutoscalerSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HTTPCookie"): + return &smesapcomv1alpha1.HTTPCookieApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("JobDetails"): return &smesapcomv1alpha1.JobDetailsApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("MetricRule"): @@ -97,6 +99,10 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &smesapcomv1alpha1.ServiceExposureApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServiceInfo"): return &smesapcomv1alpha1.ServiceInfoApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("Stickiness"): + return &smesapcomv1alpha1.StickinessApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("StickinessHash"): + return &smesapcomv1alpha1.StickinessHashApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("TenantOperations"): return &smesapcomv1alpha1.TenantOperationsApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("TenantOperationWorkloadReference"):