Skip to content

Commit

Permalink
fix: Allow to remove the finalizer even if the ScaledObject isn't val…
Browse files Browse the repository at this point in the history
…id (#4397)

Co-authored-by: Tom Kerkhove <kerkhove.tom@gmail.com>
  • Loading branch information
JorTurFer and tomkerkhove authored Mar 22, 2023
1 parent 5c65298 commit 43789f2
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 39 deletions.
3 changes: 2 additions & 1 deletion BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ Follow these instructions if you want to debug the KEDA webhook using VS Code.
clientConfig:
url: "https://${YOUR_URL}/validate-keda-sh-v1alpha1-scaledobject"
```
**Note:** You could need to define also the key `caBundle` with the CA bundle encoded in base64 if the cluster can get it during the manifest apply (this happens with localtunnel for instance)
**Note:** You need to define also the key `caBundle` with the CA bundle encoded in base64. This `caBundle` is the pem file from the CA used to sign the certificate. Remember to disable the `caBundle` inyection to avoid unintended rewrites of your `caBundle` (by KEDA operator or by any other 3rd party)


4. Deploy CRDs and KEDA into `keda` namespace
```bash
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio

### Fixes

- **Admission Webhooks**: Allow to remove the finalizer even if the ScaledObject isn't valid ([#4396](https://github.com/kedacore/keda/issue/4396))
- **AWS SQS Scaler**: Respect `scaleOnInFlight` value ([#4276](https://github.com/kedacore/keda/issue/4276))

### Deprecations
Expand Down
17 changes: 17 additions & 0 deletions apis/keda/v1alpha1/scaledobject_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,30 @@ func (so *ScaledObject) ValidateCreate() error {
func (so *ScaledObject) ValidateUpdate(old runtime.Object) error {
val, _ := json.MarshalIndent(so, "", " ")
scaledobjectlog.V(1).Info(fmt.Sprintf("validating scaledobject update for %s", string(val)))

if isRemovingFinalizer(so, old) {
scaledobjectlog.V(1).Info("finalizer removal, skipping validation")
return nil
}

return validateWorkload(so, "update")
}

func (so *ScaledObject) ValidateDelete() error {
return nil
}

func isRemovingFinalizer(so *ScaledObject, old runtime.Object) bool {
oldSo := old.(*ScaledObject)

soSpec, _ := json.MarshalIndent(so.Spec, "", " ")
oldSoSpec, _ := json.MarshalIndent(oldSo.Spec, "", " ")
soSpecString := string(soSpec)
oldSoSpecString := string(oldSoSpec)

return len(so.ObjectMeta.Finalizers) == 0 && len(oldSo.ObjectMeta.Finalizers) == 1 && soSpecString == oldSoSpecString
}

func validateWorkload(so *ScaledObject, action string) error {
prommetrics.RecordScaledObjectValidatingTotal(so.Namespace, action)
err := verifyCPUMemoryScalers(so, action)
Expand Down
122 changes: 84 additions & 38 deletions apis/keda/v1alpha1/scaledobject_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ var _ = It("should validate the so creation when there isn't any hpa", func() {
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("should validate the so creation when there are other SO for other workloads", func() {
Expand All @@ -171,8 +172,9 @@ var _ = It("should validate the so creation when there are other SO for other wo
err = k8sClient.Create(context.Background(), so1)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so2)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so2)
}).ShouldNot(HaveOccurred())
})

var _ = It("should validate the so creation when there are other HPA for other workloads", func() {
Expand All @@ -188,8 +190,9 @@ var _ = It("should validate the so creation when there are other HPA for other w
err = k8sClient.Create(context.Background(), hpa)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("should validate the so creation when it's own hpa is already generated", func() {
Expand All @@ -206,8 +209,9 @@ var _ = It("should validate the so creation when it's own hpa is already generat
err = k8sClient.Create(context.Background(), hpa)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("should validate the so update when it's own hpa is already generated", func() {
Expand All @@ -228,8 +232,9 @@ var _ = It("should validate the so update when it's own hpa is already generated
Expect(err).ToNot(HaveOccurred())

so.Spec.MaxReplicaCount = pointer.Int32(7)
err = k8sClient.Update(context.Background(), so)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Update(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("shouldn't validate the so creation when there is another unmanaged hpa", func() {
Expand All @@ -246,8 +251,9 @@ var _ = It("shouldn't validate the so creation when there is another unmanaged h
err = k8sClient.Create(context.Background(), hpa)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("shouldn't validate the so creation when there is another so", func() {
Expand All @@ -264,8 +270,9 @@ var _ = It("shouldn't validate the so creation when there is another so", func()
err = k8sClient.Create(context.Background(), so2)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("shouldn't validate the so creation when there is another hpa with custom apis", func() {
Expand All @@ -282,8 +289,9 @@ var _ = It("shouldn't validate the so creation when there is another hpa with cu
err = k8sClient.Create(context.Background(), hpa)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("should validate the so creation with cpu and memory when deployment has requests", func() {
Expand All @@ -299,8 +307,9 @@ var _ = It("should validate the so creation with cpu and memory when deployment
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("shouldn't validate the so creation with cpu and memory when deployment hasn't got memory request", func() {
Expand All @@ -316,8 +325,9 @@ var _ = It("shouldn't validate the so creation with cpu and memory when deployme
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("shouldn't validate the so creation with cpu and memory when deployment hasn't got cpu request", func() {
Expand All @@ -333,8 +343,9 @@ var _ = It("shouldn't validate the so creation with cpu and memory when deployme
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("should validate the so creation with cpu and memory when statefulset has requests", func() {
Expand All @@ -350,8 +361,9 @@ var _ = It("should validate the so creation with cpu and memory when statefulset
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("shouldn't validate the so creation with cpu and memory when statefulset hasn't got memory request", func() {
Expand All @@ -367,8 +379,9 @@ var _ = It("shouldn't validate the so creation with cpu and memory when stateful
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("shouldn't validate the so creation with cpu and memory when statefulset hasn't got cpu request", func() {
Expand All @@ -384,8 +397,9 @@ var _ = It("shouldn't validate the so creation with cpu and memory when stateful
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("should validate the so creation without cpu and memory when custom resources", func() {
Expand All @@ -397,56 +411,88 @@ var _ = It("should validate the so creation without cpu and memory when custom r
err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), so)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("should validate so creation when all requirements are met for scaling to zero with cpu scaler", func() {
namespaceName := "scale-to-zero-good"
namespace := createNamespace(namespaceName)
workload := createDeployment(namespaceName, true, false)

scaledobject := createScaledObjectSTZ(soName, namespaceName, workloadName, 0, 5, true)
so := createScaledObjectSTZ(soName, namespaceName, workloadName, 0, 5, true)

err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), scaledobject)
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = It("shouldn't validate so creation with cpu scaler requirements not being met for scaling to 0", func() {
namespaceName := "scale-to-zero-min-replicas-bad"
namespace := createNamespace(namespaceName)
workload := createDeployment(namespaceName, true, false)

scaledobject := createScaledObjectSTZ(soName, namespaceName, workloadName, 0, 5, false)
so := createScaledObjectSTZ(soName, namespaceName, workloadName, 0, 5, false)

err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())
err = k8sClient.Create(context.Background(), scaledobject)
Expect(err).To(HaveOccurred())
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).Should(HaveOccurred())
})

var _ = It("should validate so creation when min replicas is > 0 with only cpu scaler given", func() {
namespaceName := "scale-to-zero-no-external-trigger-good"
namespace := createNamespace(namespaceName)
workload := createDeployment(namespaceName, true, false)

scaledobject := createScaledObjectSTZ(soName, namespaceName, workloadName, 1, 5, false)
so := createScaledObjectSTZ(soName, namespaceName, workloadName, 1, 5, false)

err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())
err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())
err = k8sClient.Create(context.Background(), scaledobject)
Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())

})

var _ = It("should validate the so update if it's removing the finalizer even if it's invalid", func() {

namespaceName := "removing-finalizers"
namespace := createNamespace(namespaceName)
workload := createDeployment(namespaceName, true, true)
so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", true)
so.ObjectMeta.Finalizers = append(so.ObjectMeta.Finalizers, "finalizer")

err := k8sClient.Create(context.Background(), namespace)
Expect(err).ToNot(HaveOccurred())

err = k8sClient.Create(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

Eventually(func() error {
return k8sClient.Create(context.Background(), so)
}).ShouldNot(HaveOccurred())

workload.Spec.Template.Spec.Containers[0].Resources.Requests = nil
err = k8sClient.Update(context.Background(), workload)
Expect(err).ToNot(HaveOccurred())

so.ObjectMeta.Finalizers = []string{}
Eventually(func() error {
return k8sClient.Update(context.Background(), so)
}).ShouldNot(HaveOccurred())
})

var _ = AfterSuite(func() {
Expand Down

0 comments on commit 43789f2

Please sign in to comment.