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.
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
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"}
Additional context
MiniShift version: minishift v1.28.0+48e89ed
@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.

@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.

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

Hi @shawn-hurley and @craicoverflow,
I could reproduce the scenario faced by @craicoverflow and it shows a bug since:
fakeClient.Update and fakeClient.Create are working well with the v1.Route object and the issue can just be checked when the Get and List are used. (The schema.GroupVersionKind is nil inside of them)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)
}
}