Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
122 changes: 122 additions & 0 deletions cmd/controller/tmp.go
Original file line number Diff line number Diff line change
@@ -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)
}
39 changes: 39 additions & 0 deletions crds/sme.sap.com_capapplicationversions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/informers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 31 additions & 6 deletions internal/controller/reconcile-capapplicationversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions internal/controller/reconcile-capapplicationversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}},
},
)
}
29 changes: 14 additions & 15 deletions internal/controller/reconcile-captenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Loading
Loading