Operator-sdk: How do I add an OpenShift route to scheme in a Reconcile unit test?

Created on 13 May 2019  路  8Comments  路  Source: operator-framework/operator-sdk

Type of question

Are you asking about community best practices, how to implement a specific feature, or about general context and help around the operator-sdk?

How to implement a specific feature.

Question

What did you do?

I have created a test function to test my Reconcile function by following the Testing Reconcile example.

I have added the OpenShift route to the scheme:

s := scheme.Scheme

// add route to scheme
routev1.AddToScheme(s)

s.AddKnownTypes(mobilesecurityservicev1alpha1.SchemeGroupVersion, &mobilesecurityservicev1alpha1.MobileSecurityService{})
s.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})
s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ConfigMap{}, &corev1.Service{})

From my test I call the Reconcile function which then creates the route:

res, err = r.Reconcile(req)
if err != nil {
    t.Fatalf("reconcile: (%v)", err)
}

This succeeds.

Next, I try to get the route:

route := &routev1.Route{}
err = cl.Get(context.TODO(), types.NamespacedName{Namespace: instance.Namespace, Name: utils.GetRouteName(&instance)}, route)
if err != nil {
    t.Fatalf("get route: (%v)", err)
}

What did you expect to see?

Given that the route was added to the scheme, I expected the test in the last snippet above to succeed, and for the route to be found.

What did you see instead? Under which circumstances?

The test fails at above snippet. Here is the error message:

controller_test.go:363: get route: (no kind "Route" is registered for version "v1" in scheme "k8s.io/client-go/kubernetes/scheme/register.go:61")

How do I register the Route to this version "v1"?

Environment

  • operator-sdk version v0.7.0+git
  • Kubernetes version information:
Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.1", GitCommit:"b7394102d6ef778017f2ca4046abbaa23b88c290", GitTreeState:"clean", BuildDate:"2019-04-08T17:11:31Z", GoVersion:"go1.12.1", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"11+", GitVersion:"v1.11.0+d4cacc0", GitCommit:"d4cacc0", GitTreeState:"clean", BuildDate:"2019-05-02T11:52:09Z", GoVersion:"go1.10.8", Compiler:"gc", Platform:"linux/amd64"}
  • Kubernetes cluster kind:

Additional context

MiniShift version: minishift v1.28.0+48e89ed

Link to actual test code.

All 8 comments

@LiliC could you give a hand to us?

Hey,

I think you need to use this function: https://github.com/craicoverflow/mobile-security-service-operator/blob/AEROGEAR-8951/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go#L57 and pass the scheme where this registered to it.

I am going to close but please re-open if that does not work.

Hi @shawn-hurley,

Thanks for the reply. I've tried the function you suggested instead and unfortunately I still get the same error:

go test `pwd`/pkg/controller/mobilesecurityservice
--- FAIL: TestReconcileMobileSecurityService_Reconcile (0.01s)
    controller_test.go:352: get route: (no kind "Route" is registered for version "v1" in scheme "k8s.io/client-go/kubernetes/scheme/register.go:61")

Could you reopen this issue? I am unable to.

@craicoverflow I have another idea

routev1.AddToScheme(s)

and then:

s.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})

I think you should remove the second one and then see if that helps?

Hi @craicoverflow,

The following isolated test worked.

package mobilesecurityservice

import (
    "context"

    "github.com/aerogear/mobile-security-service-operator/pkg/apis/mobilesecurityservice/v1alpha1"
    mobilesecurityservicev1alpha1 "github.com/aerogear/mobile-security-service-operator/pkg/apis/mobilesecurityservice/v1alpha1"
    "github.com/aerogear/mobile-security-service-operator/pkg/utils"
    routev1 "github.com/openshift/api/route/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/types"
    "k8s.io/client-go/kubernetes/scheme"
    _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    "sigs.k8s.io/controller-runtime/pkg/client/fake"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
    "testing"
)

var (
    name            = "mobile-security-service"
    namespace       = "mobile-security-service-operator"
    replicas  int32 = 1

    instance = &mobilesecurityservicev1alpha1.MobileSecurityService{
        ObjectMeta: metav1.ObjectMeta{
            Name:      name,
            Namespace: namespace,
        },
        Spec: v1alpha1.MobileSecurityServiceSpec{
            Size:                    1,
            MemoryLimit:             "512Mi",
            MemoryRequest:           "512Mi",
            ClusterProtocol:         "http",
            ConfigMapName:           "mss-config",
            RouteName:               "mss-route",
            SkipNamespaceValidation: true,
        },
    }
)

func TestReconcileMobileSecurityService_ReconcileController(t *testing.T) {

    // Register operator types with the runtime scheme.
    s := scheme.Scheme

    //Add route Openshift scheme
    if err := routev1.AddToScheme(s); err != nil {
        t.Fatalf("Unable to add route scheme: (%v)", err)
    }

    s.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})
    s.AddKnownTypes(mobilesecurityservicev1alpha1.SchemeGroupVersion, instance)
    objs := []runtime.Object{instance}

    // Create a fake client to mock API calls.
    cl := fake.NewFakeClient(objs...)
    // Create a ReconcileMemcached object with the scheme and fake client.
    r := &ReconcileMobileSecurityService{client: cl, scheme: s}

    // Mock request to simulate Reconcile() being called on an event for a
    // watched resource .
    req := reconcile.Request{
        NamespacedName: types.NamespacedName{
            Name:      name,
            Namespace: namespace,
        },
    }
    res, err := r.Reconcile(req)
    if err != nil {
        t.Fatalf("reconcile: (%v)", err)
    }
    // Check the result of reconciliation to make sure it has the desired state.
    if !res.Requeue {
        t.Error("reconcile did not requeue request as expected")
    }

    route := &routev1.Route{}
    err = r.client.Get(context.TODO(), types.NamespacedName{Name: utils.GetRouteName(instance), Namespace: instance.Namespace}, route)
    if err != nil {
        t.Fatalf("get route: (%v)", err)
    }

}

Following the result.
Screenshot 2019-05-14 at 15 59 22

@camilamacedo86 can you see if removing this line:

s.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})

works as well?

Hi @shawn-hurley,

After removing the line s.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{}) it still working which confirms the following is adding the schema.

        //Add route Openshift scheme
    if err := routev1.AddToScheme(s); err != nil {
        t.Fatalf("Unable to add route scheme: (%v)", err)
    }

So, following the full test in order to prove that it is importing the schema.

package mobilesecurityservice

import (
    "context"

    "github.com/aerogear/mobile-security-service-operator/pkg/apis/mobilesecurityservice/v1alpha1"
    mobilesecurityservicev1alpha1 "github.com/aerogear/mobile-security-service-operator/pkg/apis/mobilesecurityservice/v1alpha1"
    "github.com/aerogear/mobile-security-service-operator/pkg/utils"
    routev1 "github.com/openshift/api/route/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/types"
    "k8s.io/client-go/kubernetes/scheme"
    _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    "sigs.k8s.io/controller-runtime/pkg/client/fake"
    "sigs.k8s.io/controller-runtime/pkg/reconcile"
    "testing"
)

var (
    name            = "mobile-security-service"
    namespace       = "mobile-security-service-operator"
    replicas  int32 = 1

    instance = &mobilesecurityservicev1alpha1.MobileSecurityService{
        ObjectMeta: metav1.ObjectMeta{
            Name:      name,
            Namespace: namespace,
        },
        Spec: v1alpha1.MobileSecurityServiceSpec{
            Size:                    1,
            MemoryLimit:             "512Mi",
            MemoryRequest:           "512Mi",
            ClusterProtocol:         "http",
            ConfigMapName:           "mss-config",
            RouteName:               "mss-route",
            SkipNamespaceValidation: true,
        },
    }
)

func TestReconcileMobileSecurityService_ReconcileController(t *testing.T) {

    // Register operator types with the runtime scheme.
    s := scheme.Scheme

    ////Add route Openshift scheme
    if err := routev1.AddToScheme(s); err != nil {
        t.Fatalf("Unable to add route scheme: (%v)", err)
    }

    s.AddKnownTypes(mobilesecurityservicev1alpha1.SchemeGroupVersion, instance)
    objs := []runtime.Object{instance}

    // Create a fake client to mock API calls.
    cl := fake.NewFakeClient(objs...)
    // Create a ReconcileMemcached object with the scheme and fake client.
    r := &ReconcileMobileSecurityService{client: cl, scheme: s}

    // Mock request to simulate Reconcile() being called on an event for a
    // watched resource .
    req := reconcile.Request{
        NamespacedName: types.NamespacedName{
            Name:      name,
            Namespace: namespace,
        },
    }
    res, err := r.Reconcile(req)
    if err != nil {
        t.Fatalf("reconcile: (%v)", err)
    }
    // Check the result of reconciliation to make sure it has the desired state.
    if !res.Requeue {
        t.Error("reconcile did not requeue request as expected")
    }

    route := &routev1.Route{}
    err = r.client.Get(context.TODO(), types.NamespacedName{Name: utils.GetRouteName(instance), Namespace: instance.Namespace}, route)
    if err != nil {
        t.Fatalf("get route: (%v)", err)
    }

} 

Following the result without adding the Route scheme in the following lines.

Screenshot 2019-05-14 at 16 14 56

Following the result when the Route scheme is added as described above which means that the Scheme was added with success.

Screenshot 2019-05-14 at 16 16 10

Hi @shawn-hurley and @craicoverflow,

I could reproduce the scenario faced by @craicoverflow and it shows a bug since:

NOTE After adding the Schema the msg error is quite similar the msg faced when the schema is missing but is NOT the same.

Regards the question raised here

How do I add an OpenShift route to scheme in a Reconcile unit test?

shows that we can confirm that the following answer is correct beside the issue/bug https://github.com/operator-framework/operator-sdk/issues/1427 found

// TestMemcachedController runs ReconcileMemcached.Reconcile() against a
// fake client that tracks a Memcached object.
func TestMemcachedController(t *testing.T) {
    // Set the logger to development mode for verbose logs.
    logf.SetLogger(logf.ZapLogger(true))

    var (
        name            = "memcached-operator"
        namespace       = "memcached"
        replicas  int32 = 3
    )

    // A Memcached resource with metadata and spec.
    memcached := &appv1alpha1.Memcached{
        ObjectMeta: metav1.ObjectMeta{
            Name:      name,
            Namespace: namespace,
        },
        Spec: appv1alpha1.MemcachedSpec{
            Size: replicas, // Set desired number of Memcached replicas.
        },
    }

    // Register operator types with the runtime scheme.
    s := scheme.Scheme

    //TODO (user) : Add route Openshift scheme
    if err := routev1.AddToScheme(s); err != nil {
        t.Fatalf("Unable to add route scheme: (%v)", err)
    }

    //TODO (user) :Create a mock Route Object
    route := &routev1.Route{
        TypeMeta: v1.TypeMeta{
            APIVersion: "v1",
            Kind:       "Route",
        },
        ObjectMeta: v1.ObjectMeta{
            Name:      name,
            Namespace: namespace,
            Labels:    getAppLabels(name),
        },
    }


    s.AddKnownTypes(appv1alpha1.SchemeGroupVersion, memcached)
    // Create a fake client to mock API calls.
    //TODO (user) :Add the route object
    objs := []runtime.Object{memcached, route}
    // Create a ReconcileMemcached object with the scheme and fake client.
    r := &ReconcileMemcached{client: cl, scheme: s}

    // Mock request to simulate Reconcile() being called on an event for a
    // watched resource .
    req := reconcile.Request{
        NamespacedName: types.NamespacedName{
            Name:      name,
            Namespace: namespace,
        },
    }
    res, err := r.Reconcile(req)
    if err != nil {
        t.Fatalf("reconcile: (%v)", err)
    }
    // Check the result of reconciliation to make sure it has the desired state.
    if !res.Requeue {
        t.Error("reconcile did not requeue request as expected")
    }

    ...

    //TODO (user): Use the thrid-party object in the fakeClient as follows

    //To update the mock object inject into the controller/reconcile
    err = r.client.Update(context.TODO(), route)
    if err != nil {
        t.Fatalf("create route: (%v)", err)
    }

    //NOTE: The GET and LIST are returning the error "get route: (no kind "Route" is registered for version "v1" in scheme "k8s.io/client-go/kubernetes/scheme/register.go:61")"
        // The issue opened for we check it is: https://github.com/operator-framework/operator-sdk/issues/1427  
    //TODO (user):  Add the following code to GET the mock object inject into the controller/reconcile
    route = &routev1.Route{}
    err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, route)
    if err != nil {
        t.Fatalf("get route: (%v)", err)
    }

}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hasbro17 picture hasbro17  路  3Comments

linuxbsdfreak picture linuxbsdfreak  路  4Comments

danielsig727 picture danielsig727  路  4Comments

ffoysal picture ffoysal  路  3Comments

surajssd picture surajssd  路  3Comments