Skip to content

Commit

Permalink
Adding a fake controller-runtime Client implementation and improving …
Browse files Browse the repository at this point in the history
…operator tests (#247)

* multi-tenant interceptor and scaler

Signed-off-by: Aaron Schlesinger <aaron@ecomaz.net>

* specifying host in XKCD ingress

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* routing the xkcd chart to the interceptor properly

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* check host header first

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* sending true active response in stream

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* passing target pending requests through to the underlying ScaledObject (so the scaler can read it later)

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* removing broken target pending requests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* using getHost in proxy handler

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding integration test

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding more tests to the integration test

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* splitting up integration tests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* more checks

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* mark new test TODO

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* expanding interceptor integration tests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* error messages

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* refactor test

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* more test improvements

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* rolling back target pending requests in ScaledObject

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* removing target metric error. it's not used anymore

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* improving waitFunc test

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* Refactoring the deployment cache to add better error handing and resilience.

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding doc comment

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* refactoring deploy cache and adding tests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* Using interfaces for deployment watch & list

this makes tests easier

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding more deploy cache tests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* Fixing up TestK8sDeploymentCacheRewatch

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* shutting down everything else when one thing errors, and adding a deployments cache endpoint

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* removing commented code

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* clarifying deployment cache JSON output, and simplifying deployment watch function

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding TODO tests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* error logs and restoring the count middleware

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* using consistent net/http package name throughout main.go

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* Refactoring deployment cache deployment storage

Also, running go mod tidy and adding new TODO (i.e. failing) tests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* using deployment.Status.ReadyReplicas, instead of just replicas

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* integration_tets ==> proxy_handlers_integration_test

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding some resilience to tests

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding deployment cache endpoint documentation

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* running the global test target with sh.RunV

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* adding timeout to magefile test target

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* finishing one TODO test and adding issue for the rest:

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* implementing a fake controller-runtime client

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>

* testing properly

Signed-off-by: Aaron Schlesinger <70865+arschles@users.noreply.github.com>
  • Loading branch information
arschles authored Oct 25, 2021
1 parent 5687d40 commit 119e0ea
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 17 deletions.
12 changes: 5 additions & 7 deletions operator/controllers/routing_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,19 @@ func updateRoutingMap(
) error {
lggr = lggr.WithName("updateRoutingMap")
routingConfigMap, err := k8s.GetConfigMap(ctx, cl, namespace, routing.ConfigMapRoutingTableName)
// if there is an error other than not found on the ConfigMap, we should
// fail
if err != nil && !errors.IsNotFound(err) {
// if there is an error other than not found on the ConfigMap, we should
// fail
lggr.Error(
err,
"other issue fetching the routing table ConfigMap",
"configMapName",
routing.ConfigMapRoutingTableName,
)
return pkgerrs.Wrap(err, "routing table ConfigMap fetch error")
}

// if either the routing table ConfigMap doesn't exist or for some reason it's
// nil in memory, we need to create it
if errors.IsNotFound(err) || routingConfigMap == nil {
} else if errors.IsNotFound(err) || routingConfigMap == nil {
// if either the routing table ConfigMap doesn't exist or for some reason it's
// nil in memory, we need to create it
lggr.Info(
"routing table ConfigMap didn't exist, creating it",
"configMapName",
Expand Down
35 changes: 25 additions & 10 deletions operator/controllers/routing_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"testing"

"github.com/go-logr/logr"
"github.com/kedacore/http-add-on/pkg/k8s"
"github.com/kedacore/http-add-on/pkg/routing"
"github.com/stretchr/testify/require"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestRoutingTable(t *testing.T) {
Expand All @@ -20,7 +22,16 @@ func TestRoutingTable(t *testing.T) {
)
r := require.New(t)
ctx := context.Background()
cl := fake.NewClientBuilder().Build()
cl := k8s.NewFakeRuntimeClient()
cm := &corev1.ConfigMap{
Data: map[string]string{},
}
// save the empty routing table to the config map,
// so that it has valid structure
r.NoError(routing.SaveTableToConfigMap(table, cm))
cl.GetFunc = func() client.Object {
return cm
}
target := routing.Target{
Service: svcName,
Port: 8080,
Expand All @@ -35,10 +46,12 @@ func TestRoutingTable(t *testing.T) {
target,
ns,
))
// TODO: ensure that the ConfigMap was updated.
// requires
// https://github.com/kubernetes-sigs/controller-runtime/issues/1633
// to be implemented.
// ensure that the ConfigMap was read and created. no updates
// should occur
r.Equal(1, len(cl.FakeRuntimeClientReader.GetCalls))
r.Equal(1, len(cl.FakeRuntimeClientWriter.Patches))
r.Equal(0, len(cl.FakeRuntimeClientWriter.Updates))
r.Equal(0, len(cl.FakeRuntimeClientWriter.Creates))

retTarget, err := table.Lookup(host)
r.NoError(err)
Expand All @@ -53,10 +66,12 @@ func TestRoutingTable(t *testing.T) {
ns,
))

// TODO: ensure that the ConfigMap was updated.
// requires
// https://github.com/kubernetes-sigs/controller-runtime/issues/1633
// to be implemnented
// ensure that the ConfigMap was read and updated. no additional
// creates should occur.
r.Equal(2, len(cl.FakeRuntimeClientReader.GetCalls))
r.Equal(2, len(cl.FakeRuntimeClientWriter.Patches))
r.Equal(0, len(cl.FakeRuntimeClientWriter.Updates))
r.Equal(0, len(cl.FakeRuntimeClientWriter.Creates))

_, err = table.Lookup(host)
r.Error(err)
Expand Down
171 changes: 171 additions & 0 deletions pkg/k8s/client_fake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package k8s

import (
"context"
"encoding/json"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ client.Client = &FakeRuntimeClient{}
var _ client.Reader = &FakeRuntimeClientReader{}
var _ client.Writer = &FakeRuntimeClientWriter{}
var _ client.StatusClient = &FakeRuntimeStatusClient{}

// FakeRuntimeClient is a fake implementation of
// (k8s.io/controller-runtime/pkg/client).Client
type FakeRuntimeClient struct {
*FakeRuntimeClientReader
*FakeRuntimeClientWriter
*FakeRuntimeStatusClient
}

func NewFakeRuntimeClient() *FakeRuntimeClient {
return &FakeRuntimeClient{
FakeRuntimeClientReader: &FakeRuntimeClientReader{},
FakeRuntimeClientWriter: &FakeRuntimeClientWriter{},
FakeRuntimeStatusClient: &FakeRuntimeStatusClient{},
}
}

// Scheme implements the controller-runtime Client interface.
//
// NOTE: this method is not implemented and always returns nil.
func (f *FakeRuntimeStatusClient) Scheme() *runtime.Scheme {
return nil
}

// RESTMapper implements the controller-runtime Client interface.
//
// NOTE: this method is not implemented and always returns nil.
func (f *FakeRuntimeClientReader) RESTMapper() meta.RESTMapper {
return nil
}

type GetCall struct {
Key client.ObjectKey
Obj client.Object
}

// FakeRuntimeClientReader is a fake implementation of
// (k8s.io/controller-runtime/pkg/client).ClientReader
type FakeRuntimeClientReader struct {
GetCalls []GetCall
GetFunc func() client.Object
ListCalls []client.ObjectList
ListFunc func() client.ObjectList
}

func (f *FakeRuntimeClientReader) Get(
ctx context.Context,
key client.ObjectKey,
obj client.Object,
) error {
f.GetCalls = append(f.GetCalls, GetCall{
Key: key,
Obj: obj,
})
// marshal the GetFunc return value, then unmarshal
// it back into the obj parameter.
b, err := json.Marshal(f.GetFunc())
if err != nil {
return err
}
if err := json.Unmarshal(b, obj); err != nil {
return err
}

return nil
}

func (f *FakeRuntimeClientReader) List(
ctx context.Context,
list client.ObjectList,
opts ...client.ListOption,
) error {
f.ListCalls = append(f.ListCalls, list)
b, err := json.Marshal(f.ListFunc())
if err != nil {
return err
}
if err := json.Unmarshal(b, list); err != nil {
return err
}
return nil
}

// FakeRuntimeClientWriter is a fake implementation of
// (k8s.io/controller-runtime/pkg/client).ClientWriter
//
// It stores all method calls in the respective struct
// fields. Instances of FakeRuntimeClientWriter are not
// concurrency-safe
type FakeRuntimeClientWriter struct {
Creates []client.Object
Deletes []client.Object
Updates []client.Object
Patches []client.Object
DeleteAllOfs []client.Object
}

func (f *FakeRuntimeClientWriter) Create(
ctx context.Context,
obj client.Object,
opts ...client.CreateOption,
) error {
f.Creates = append(f.Creates, obj)
return nil
}

func (f *FakeRuntimeClientWriter) Delete(
ctx context.Context,
obj client.Object,
opts ...client.DeleteOption,
) error {
f.Deletes = append(f.Deletes, obj)
return nil
}

func (f *FakeRuntimeClientWriter) Update(
ctx context.Context,
obj client.Object,
opts ...client.UpdateOption,
) error {
f.Updates = append(f.Updates, obj)
return nil
}

func (f *FakeRuntimeClientWriter) Patch(
ctx context.Context,
obj client.Object,
patch client.Patch,
opts ...client.PatchOption,
) error {
f.Patches = append(f.Patches, obj)
return nil
}

func (f *FakeRuntimeClientWriter) DeleteAllOf(
ctx context.Context,
obj client.Object,
opts ...client.DeleteAllOfOption,
) error {
f.DeleteAllOfs = append(f.DeleteAllOfs, obj)
return nil
}

// FakeRuntimeStatusClient is a fake implementation of
// (k8s.io/controller-runtime/pkg/client).StatusClient
type FakeRuntimeStatusClient struct {
}

// Status implements the controller-runtime StatusClient
// interface.
//
// NOTE: this function isn't implemented and always returns
// nil.
func (f *FakeRuntimeStatusClient) Status() client.StatusWriter {
return nil
}

0 comments on commit 119e0ea

Please sign in to comment.