Skip to content

Commit 146e71d

Browse files
authored
Merge pull request #1175 from borisurbanik/THREESCALE-14651
THREESCALE-14651 Fix HPA reconciler to skip API delete calls when HPA already absent
2 parents de2fb00 + 61ef18d commit 146e71d

2 files changed

Lines changed: 216 additions & 6 deletions

File tree

pkg/3scale/amp/operator/base_apimanager_logic_reconciler.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
v1 "k8s.io/api/core/v1"
1919
policyv1 "k8s.io/api/policy/v1"
2020
rbacv1 "k8s.io/api/rbac/v1"
21-
"k8s.io/apimachinery/pkg/api/errors"
2221
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2322
"k8s.io/apimachinery/pkg/types"
2423
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -209,11 +208,8 @@ func (r *BaseAPIManagerLogicReconciler) ReconcileHpa(desired *hpa.HorizontalPodA
209208
}
210209
}
211210
// Delete HPA in all other cases
212-
err := r.DeleteResource(desired)
213-
if err != nil && !errors.IsNotFound(err) {
214-
return err
215-
}
216-
return nil
211+
helper.TagObjectToDelete(desired)
212+
return r.ReconcileResource(&hpa.HorizontalPodAutoscaler{}, desired, mutateFn)
217213
}
218214

219215
func (r *BaseAPIManagerLogicReconciler) ReconcilePodMonitor(desired *monitoringv1.PodMonitor, mutateFn reconcilers.MutateFn) error {

pkg/3scale/amp/operator/base_apimanager_logic_reconciler_test.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ import (
55
"testing"
66

77
appsv1alpha1 "github.com/3scale/3scale-operator/apis/apps/v1alpha1"
8+
"github.com/3scale/3scale-operator/pkg/3scale/amp/component"
89
"github.com/3scale/3scale-operator/pkg/reconcilers"
910

1011
grafanav1alpha1 "github.com/grafana-operator/grafana-operator/v4/api/integreatly/v1alpha1"
1112
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
13+
hpav2 "k8s.io/api/autoscaling/v2"
1214
v1 "k8s.io/api/core/v1"
15+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1316
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1417
"k8s.io/apimachinery/pkg/runtime"
18+
"k8s.io/apimachinery/pkg/types"
1519
fakeclientset "k8s.io/client-go/kubernetes/fake"
1620
"k8s.io/client-go/kubernetes/scheme"
1721
"k8s.io/client-go/tools/record"
@@ -400,6 +404,216 @@ func TestBaseAPIManagerLogicReconcilerHasPodMonitors(t *testing.T) {
400404
}
401405
}
402406

407+
func newTestReconciler(t *testing.T, apimanager *appsv1alpha1.APIManager, existingObjs ...runtime.Object) (*BaseAPIManagerLogicReconciler, client.Client) {
408+
t.Helper()
409+
s := scheme.Scheme
410+
if err := appsv1alpha1.AddToScheme(s); err != nil {
411+
t.Fatal(err)
412+
}
413+
414+
objs := append([]runtime.Object{apimanager}, existingObjs...)
415+
cl := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
416+
clientAPIReader := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
417+
clientset := fakeclientset.NewSimpleClientset()
418+
recorder := record.NewFakeRecorder(10000)
419+
log := logf.Log.WithName("operator_test")
420+
421+
baseReconciler := reconcilers.NewBaseReconciler(context.Background(), cl, s, clientAPIReader, log, clientset.Discovery(), recorder)
422+
return NewBaseAPIManagerLogicReconciler(baseReconciler, apimanager), cl
423+
}
424+
425+
func TestReconcileHpa(t *testing.T) {
426+
baseAM := func(annotations map[string]string) *appsv1alpha1.APIManager {
427+
am := &appsv1alpha1.APIManager{
428+
ObjectMeta: metav1.ObjectMeta{
429+
Name: apimanagerName,
430+
Namespace: namespace,
431+
Annotations: annotations,
432+
},
433+
Spec: appsv1alpha1.APIManagerSpec{
434+
APIManagerCommonSpec: appsv1alpha1.APIManagerCommonSpec{
435+
WildcardDomain: wildcardDomain,
436+
},
437+
},
438+
}
439+
if _, err := am.SetDefaults(); err != nil {
440+
panic(err)
441+
}
442+
return am
443+
}
444+
445+
hpaExists := func(t *testing.T, cl client.Client, name string) bool {
446+
t.Helper()
447+
obj := &hpav2.HorizontalPodAutoscaler{}
448+
err := cl.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, obj)
449+
if k8serrors.IsNotFound(err) {
450+
return false
451+
}
452+
if err != nil {
453+
t.Fatalf("unexpected error checking HPA %s: %v", name, err)
454+
}
455+
return true
456+
}
457+
458+
tests := []struct {
459+
name string
460+
apimanager *appsv1alpha1.APIManager
461+
existingHPA *hpav2.HorizontalPodAutoscaler
462+
desired *hpav2.HorizontalPodAutoscaler
463+
assert func(t *testing.T, cl client.Client)
464+
}{
465+
// Backend listener
466+
{
467+
name: "backend listener - HPA does not exist in cluster, disabled in config - no-op",
468+
apimanager: baseAM(nil),
469+
desired: component.DefaultHpa(component.BackendListenerName, namespace),
470+
assert: func(t *testing.T, cl client.Client) {
471+
if hpaExists(t, cl, component.BackendListenerName) {
472+
t.Error("HPA should not exist when disabled and was not pre-existing")
473+
}
474+
},
475+
},
476+
{
477+
name: "backend listener - HPA exists in cluster, disabled in config - deletes HPA",
478+
apimanager: baseAM(nil),
479+
existingHPA: component.DefaultHpa(component.BackendListenerName, namespace),
480+
desired: component.DefaultHpa(component.BackendListenerName, namespace),
481+
assert: func(t *testing.T, cl client.Client) {
482+
if hpaExists(t, cl, component.BackendListenerName) {
483+
t.Error("HPA should have been deleted when disabled")
484+
}
485+
},
486+
},
487+
{
488+
name: "backend listener - HPA does not exist in cluster, enabled in config - creates HPA",
489+
apimanager: func() *appsv1alpha1.APIManager {
490+
am := baseAM(nil)
491+
am.Spec.Backend.ListenerSpec.Hpa = true
492+
return am
493+
}(),
494+
desired: component.DefaultHpa(component.BackendListenerName, namespace),
495+
assert: func(t *testing.T, cl client.Client) {
496+
if !hpaExists(t, cl, component.BackendListenerName) {
497+
t.Error("HPA should have been created when enabled")
498+
}
499+
},
500+
},
501+
{
502+
name: "backend listener - HPA exists in cluster, enabled but with async-disable annotation - deletes HPA",
503+
apimanager: func() *appsv1alpha1.APIManager {
504+
am := baseAM(map[string]string{appsv1alpha1.DisableAsyncAnnotation: "true"})
505+
am.Spec.Backend.ListenerSpec.Hpa = true
506+
return am
507+
}(),
508+
existingHPA: component.DefaultHpa(component.BackendListenerName, namespace),
509+
desired: component.DefaultHpa(component.BackendListenerName, namespace),
510+
assert: func(t *testing.T, cl client.Client) {
511+
if hpaExists(t, cl, component.BackendListenerName) {
512+
t.Error("HPA should have been deleted when async is disabled regardless of HPA flag")
513+
}
514+
},
515+
},
516+
// Backend worker
517+
{
518+
name: "backend worker - HPA exists in cluster, disabled in config - deletes HPA",
519+
apimanager: baseAM(nil),
520+
existingHPA: component.DefaultHpa(component.BackendWorkerName, namespace),
521+
desired: component.DefaultHpa(component.BackendWorkerName, namespace),
522+
assert: func(t *testing.T, cl client.Client) {
523+
if hpaExists(t, cl, component.BackendWorkerName) {
524+
t.Error("backend worker HPA should have been deleted when disabled")
525+
}
526+
},
527+
},
528+
{
529+
name: "backend worker - HPA does not exist in cluster, enabled in config - creates HPA",
530+
apimanager: func() *appsv1alpha1.APIManager {
531+
am := baseAM(nil)
532+
am.Spec.Backend.WorkerSpec.Hpa = true
533+
return am
534+
}(),
535+
desired: component.DefaultHpa(component.BackendWorkerName, namespace),
536+
assert: func(t *testing.T, cl client.Client) {
537+
if !hpaExists(t, cl, component.BackendWorkerName) {
538+
t.Error("HPA should have been created when enabled")
539+
}
540+
},
541+
},
542+
{
543+
name: "backend worker - HPA exists in cluster, enabled in config but with async-disable annotation - deletes HPA",
544+
apimanager: func() *appsv1alpha1.APIManager {
545+
am := baseAM(map[string]string{appsv1alpha1.DisableAsyncAnnotation: "true"})
546+
am.Spec.Backend.WorkerSpec.Hpa = true
547+
return am
548+
}(),
549+
existingHPA: component.DefaultHpa(component.BackendWorkerName, namespace),
550+
desired: component.DefaultHpa(component.BackendWorkerName, namespace),
551+
assert: func(t *testing.T, cl client.Client) {
552+
if hpaExists(t, cl, component.BackendWorkerName) {
553+
t.Error("backend worker HPA should have been deleted when async is disabled")
554+
}
555+
},
556+
},
557+
// Apicast
558+
{
559+
name: "apicast - HPA exists in cluster, disabled in config - deletes HPA",
560+
apimanager: baseAM(nil),
561+
existingHPA: component.DefaultHpa(component.ApicastProductionName, namespace),
562+
desired: component.DefaultHpa(component.ApicastProductionName, namespace),
563+
assert: func(t *testing.T, cl client.Client) {
564+
if hpaExists(t, cl, component.ApicastProductionName) {
565+
t.Error("Apicast HPA should have been deleted when disabled")
566+
}
567+
},
568+
},
569+
{
570+
name: "apicast - HPA does not exist in cluster, enabled in config - creates HPA",
571+
apimanager: func() *appsv1alpha1.APIManager {
572+
am := baseAM(nil)
573+
am.Spec.Apicast.ProductionSpec.Hpa = true
574+
return am
575+
}(),
576+
desired: component.DefaultHpa(component.ApicastProductionName, namespace),
577+
assert: func(t *testing.T, cl client.Client) {
578+
if !hpaExists(t, cl, component.ApicastProductionName) {
579+
t.Error("Apicast HPA should have been created when enabled")
580+
}
581+
},
582+
},
583+
{
584+
name: "apicast - HPA does not exist in cluster, enabled in config with async-disable annotation - creates HPA (annotation has no effect)",
585+
apimanager: func() *appsv1alpha1.APIManager {
586+
am := baseAM(map[string]string{appsv1alpha1.DisableAsyncAnnotation: "true"})
587+
am.Spec.Apicast.ProductionSpec.Hpa = true
588+
return am
589+
}(),
590+
desired: component.DefaultHpa(component.ApicastProductionName, namespace),
591+
assert: func(t *testing.T, cl client.Client) {
592+
if !hpaExists(t, cl, component.ApicastProductionName) {
593+
t.Error("Apicast HPA should be created regardless of async-disable annotation")
594+
}
595+
},
596+
},
597+
}
598+
599+
for _, tt := range tests {
600+
t.Run(tt.name, func(t *testing.T) {
601+
var existingObjs []runtime.Object
602+
if tt.existingHPA != nil {
603+
existingObjs = append(existingObjs, tt.existingHPA)
604+
}
605+
606+
r, cl := newTestReconciler(t, tt.apimanager, existingObjs...)
607+
608+
if err := r.ReconcileHpa(tt.desired, reconcilers.CreateOnlyMutator); err != nil {
609+
t.Fatalf("unexpected error: %v", err)
610+
}
611+
612+
tt.assert(t, cl)
613+
})
614+
}
615+
}
616+
403617
func TestBaseAPIManagerLogicReconcilerHasServiceMonitors(t *testing.T) {
404618
var (
405619
apimanagerName = "example-apimanager"

0 commit comments

Comments
 (0)