generated from kedacore/github-template
-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ensuring operator updates ScaledObject on all HTTPScaledObject changes
Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>
- Loading branch information
Showing
15 changed files
with
409 additions
and
188 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
operator/controllers/httpscaledobject_controller_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package controllers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/go-logr/logr" | ||
"github.com/kedacore/http-add-on/operator/api/v1alpha1" | ||
"github.com/kedacore/http-add-on/pkg/k8s" | ||
"github.com/kedacore/http-add-on/pkg/routing" | ||
"github.com/stretchr/testify/require" | ||
"k8s.io/client-go/kubernetes/scheme" | ||
"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
) | ||
|
||
// When an HTTPScaledObject replicas field is updated, | ||
// the corresponding ScaledObject should be updated by | ||
// the reconciler. | ||
func TestUpdatesReplicas(t *testing.T) { | ||
ctx := context.Background() | ||
const ( | ||
ns = "testns" | ||
name = "test" | ||
) | ||
namespacedName := k8s.NamespacedName(ns, name) | ||
r := require.New(t) | ||
// Add HTTPScaledObject to scheme | ||
r.NoError(v1alpha1.AddToScheme(scheme.Scheme)) | ||
routingTable := routing.NewTable() | ||
infra := newCommonTestInfra(ns, name) | ||
|
||
// ensure the routing table config map exists | ||
routingCM := newRoutingConfigMap(ns) | ||
r.NoError(routing.SaveTableToConfigMap( | ||
routingTable, | ||
routingCM, | ||
)) | ||
r.NoError(infra.cl.Create(ctx, routingCM)) | ||
// make sure the HTTPScaledObject is already created so it | ||
// can be reconciled | ||
r.NoError(infra.cl.Create(ctx, &infra.httpso)) | ||
// clear out all create call records, so we can later test | ||
// actual create calls from the Reconcile method | ||
infra.cl.Creates = nil | ||
ctrl := HTTPScaledObjectReconciler{ | ||
Client: infra.cl, | ||
Log: logr.Discard(), | ||
RoutingTable: routingTable, | ||
} | ||
|
||
_, err := ctrl.Reconcile(ctx, reconcile.Request{ | ||
NamespacedName: namespacedName, | ||
}) | ||
r.NoError(err) | ||
// expect one get call for the HTTPScaledObject, | ||
// then one for the routing table config map | ||
r.Equal(2, len(infra.cl.Gets)) | ||
getCall := infra.cl.Gets[0] | ||
getGVK := getCall.Obj.GetObjectKind().GroupVersionKind() | ||
r.Equal("HTTPScaledObject", getGVK.Kind) | ||
r.Equal(infra.httpso.Name, getCall.Key.Name) | ||
r.Equal(infra.httpso.Namespace, getCall.Key.Namespace) | ||
getCall = infra.cl.Gets[1] | ||
getGVK = getCall.Obj.GetObjectKind().GroupVersionKind() | ||
r.Equal("ConfigMap", getGVK.Kind) | ||
r.Equal(infra.httpso.GetNamespace(), getCall.Key.Namespace) | ||
r.Equal(routing.ConfigMapRoutingTableName, getCall.Key.Name) | ||
|
||
// expect a create call for the ScaledObject | ||
r.Equal(1, len(infra.cl.Creates)) | ||
createCall := infra.cl.Creates[0] | ||
createGVK := createCall.GetObjectKind().GroupVersionKind() | ||
r.Equal("keda.sh", createGVK.Group) | ||
r.Equal("v1alpha1", createGVK.Version) | ||
r.Equal("ScaledObject", createGVK.Kind) | ||
r.Equal(fmt.Sprintf("%s-app", infra.httpso.Name), createCall.GetName()) | ||
r.Equal(infra.httpso.Namespace, createCall.GetNamespace()) | ||
|
||
// expect a call to patch the ConfigMap | ||
r.Equal(1, len(infra.cl.Patches)) | ||
patchCall := infra.cl.Patches[0] | ||
patchGVK := patchCall.GetObjectKind().GroupVersionKind() | ||
r.Equal("ConfigMap", patchGVK.Kind) | ||
r.Equal(infra.httpso.GetNamespace(), patchCall.GetNamespace()) | ||
r.Equal(routing.ConfigMapRoutingTableName, patchCall.GetName()) | ||
|
||
// now change the min and max replicas of the HTTPScaledObject | ||
// and ensure the ScaledObject is updated (there should now be an | ||
// Update call) | ||
updatedHTTPSO := v1alpha1.HTTPScaledObject{} | ||
r.NoError(infra.cl.Get(ctx, namespacedName, &updatedHTTPSO)) | ||
updatedHTTPSO.Spec.Replicas.Min++ | ||
updatedHTTPSO.Spec.Replicas.Max++ | ||
r.NoError(infra.cl.Update(ctx, &updatedHTTPSO)) | ||
priorGets := infra.cl.Gets | ||
_, err = ctrl.Reconcile(ctx, reconcile.Request{ | ||
NamespacedName: namespacedName, | ||
}) | ||
r.NoError(err) | ||
// there should be twice as many get calls, since | ||
// we get the HTTPScaledObject and the routing table | ||
// ConfigMap again | ||
r.Equal(len(priorGets)*2, len(infra.cl.Gets)) | ||
latestCreate := infra.cl.Creates[len(infra.cl.Creates)-1] | ||
latestCreateGVK := latestCreate.GetObjectKind().GroupVersionKind() | ||
r.Equal("ScaledObject", latestCreateGVK.Kind) | ||
r.Equal(fmt.Sprintf("%s-app", infra.httpso.Name), latestCreate.GetName()) | ||
r.Equal(infra.httpso.Namespace, latestCreate.GetNamespace()) | ||
unstructuredSO, err := k8s.GetScaledObject(ctx, infra.cl, ns, name+"-app") | ||
r.NoError(err) | ||
minIface := getNestedField(unstructuredSO.Object, "spec.replicas.min") | ||
maxIface := getNestedField(unstructuredSO.Object, "spec.replicas.max") | ||
r.Equal(updatedHTTPSO.Spec.Replicas.Min, minIface) | ||
r.Equal(updatedHTTPSO.Spec.Replicas.Max, maxIface) | ||
} | ||
|
||
// getNestedField returns the value of the field with the given path. | ||
// for example, if you specify "a.b.c", it will return the field at | ||
// m[a][b][c]. This assumes that m[a] is a map[string]interface{}, | ||
// m[b] is a map[string]interface{}, and so on. | ||
func getNestedField(m map[string]interface{}, path string) interface{} { | ||
parts := strings.Split(path, ".") | ||
var ret interface{} = m | ||
for _, part := range parts { | ||
m, ok := ret.(map[string]interface{}) | ||
if !ok { | ||
return nil | ||
} | ||
ret = m[part] | ||
} | ||
return ret | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.