che-operator/controllers/che/checluster_controller_test.go

940 lines
33 KiB
Go

//
// Copyright (c) 2019-2021 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
// Contributors:
// Red Hat, Inc. - initial API and implementation
//
package che
import (
"context"
"fmt"
"os"
"strconv"
mocks "github.com/eclipse-che/che-operator/mocks"
"reflect"
"time"
chev1alpha1 "github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1"
"github.com/golang/mock/gomock"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
devworkspace "github.com/eclipse-che/che-operator/pkg/deploy/dev-workspace"
identity_provider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider"
"github.com/google/go-cmp/cmp"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/eclipse-che/che-operator/pkg/util"
console "github.com/openshift/api/console/v1"
orgv1 "github.com/eclipse-che/che-operator/api/v1"
oauthv1 "github.com/openshift/api/oauth/v1"
routev1 "github.com/openshift/api/route/v1"
userv1 "github.com/openshift/api/user/v1"
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
packagesv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/utils/pointer"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"
fakeDiscovery "k8s.io/client-go/discovery/fake"
fakeclientset "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
configv1 "github.com/openshift/api/config/v1"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"testing"
)
var (
namespace = "eclipse-che"
)
func TestNativeUserModeEnabled(t *testing.T) {
type testCase struct {
name string
initObjects []runtime.Object
isOpenshift bool
devworkspaceEnabled bool
initialNativeUserValue *bool
expectedNativeUserValue *bool
}
testCases := []testCase{
{
name: "che-operator should use nativeUserMode when devworkspaces on openshift and no initial value in CR for nativeUserMode",
isOpenshift: true,
devworkspaceEnabled: true,
initialNativeUserValue: nil,
expectedNativeUserValue: pointer.BoolPtr(true),
},
{
name: "che-operator should use nativeUserMode value from initial CR",
isOpenshift: true,
devworkspaceEnabled: true,
initialNativeUserValue: pointer.BoolPtr(false),
expectedNativeUserValue: pointer.BoolPtr(false),
},
{
name: "che-operator should use nativeUserMode value from initial CR",
isOpenshift: true,
devworkspaceEnabled: true,
initialNativeUserValue: pointer.BoolPtr(true),
expectedNativeUserValue: pointer.BoolPtr(true),
},
{
name: "che-operator should use nativeUserMode when devworkspaces on kubernetes and no initial value in CR for nativeUserMode",
isOpenshift: false,
devworkspaceEnabled: true,
initialNativeUserValue: nil,
expectedNativeUserValue: pointer.BoolPtr(true),
},
{
name: "che-operator not modify nativeUserMode when devworkspace not enabled",
isOpenshift: true,
devworkspaceEnabled: false,
initialNativeUserValue: nil,
expectedNativeUserValue: nil,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true)))
scheme := scheme.Scheme
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(routev1.GroupVersion, &routev1.Route{})
scheme.AddKnownTypes(oauthv1.SchemeGroupVersion, &oauthv1.OAuthClient{})
scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.Proxy{})
scheme.AddKnownTypes(crdv1.SchemeGroupVersion, &crdv1.CustomResourceDefinition{})
initCR := InitCheWithSimpleCR().DeepCopy()
initCR.Spec.DevWorkspace.Enable = testCase.devworkspaceEnabled
initCR.Spec.Auth.NativeUserMode = testCase.initialNativeUserValue
testCase.initObjects = append(testCase.initObjects, initCR)
util.IsOpenShift = testCase.isOpenshift
// reread templates (workaround after setting IsOpenShift value)
devworkspace.DevWorkspaceTemplates = devworkspace.DevWorkspaceTemplatesPath()
devworkspace.DevWorkspaceIssuerFile = devworkspace.DevWorkspaceTemplates + "/devworkspace-controller-selfsigned-issuer.Issuer.yaml"
devworkspace.DevWorkspaceCertificateFile = devworkspace.DevWorkspaceTemplates + "/devworkspace-controller-serving-cert.Certificate.yaml"
cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{}
if !ok {
t.Fatal("Error creating fake discovery client")
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
}
_, err := r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
cr := &orgv1.CheCluster{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cr); err != nil {
t.Errorf("CR not found")
}
if !reflect.DeepEqual(testCase.expectedNativeUserValue, cr.Spec.Auth.NativeUserMode) {
expectedValue, actualValue := "nil", "nil"
if testCase.expectedNativeUserValue != nil {
expectedValue = strconv.FormatBool(*testCase.expectedNativeUserValue)
}
if cr.Spec.Auth.NativeUserMode != nil {
actualValue = strconv.FormatBool(*cr.Spec.Auth.NativeUserMode)
}
t.Errorf("Expected nativeUserMode '%+v', but found '%+v' for input '%+v'",
expectedValue, actualValue, testCase)
}
})
}
}
func TestEnsureServerExposureStrategy(t *testing.T) {
type testCase struct {
name string
expectedCr *orgv1.CheCluster
devWorkspaceEnabled bool
initObjects []runtime.Object
}
testCases := []testCase{
{
name: "Single Host should be enabled if devWorkspace is enabled",
expectedCr: &orgv1.CheCluster{
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "single-host",
},
},
},
devWorkspaceEnabled: true,
},
{
name: "Multi Host should be enabled if devWorkspace is not enabled",
expectedCr: &orgv1.CheCluster{
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
},
devWorkspaceEnabled: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true)))
scheme := scheme.Scheme
orgv1.SchemeBuilder.AddToScheme(scheme)
initCR := InitCheWithSimpleCR().DeepCopy()
testCase.initObjects = append(testCase.initObjects, initCR)
if testCase.devWorkspaceEnabled {
initCR.Spec.DevWorkspace.Enable = true
}
cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{}
if !ok {
t.Fatal("Error creating fake discovery client")
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
}
util.IsOpenShift = true
util.IsOpenShift4 = false
_, err := r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
cr := &orgv1.CheCluster{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cr); err != nil {
t.Errorf("CR not found")
}
if !reflect.DeepEqual(testCase.expectedCr.Spec.Server.ServerExposureStrategy, cr.Spec.Server.ServerExposureStrategy) {
t.Errorf("Expected CR and CR returned from API server are different (-want +got): %v", cmp.Diff(testCase.expectedCr.Spec.Server.ServerExposureStrategy, cr.Spec.Server.ServerExposureStrategy))
}
})
}
}
func TestShouldSetUpCorrectlyDevfileRegistryURL(t *testing.T) {
type testCase struct {
name string
isOpenShift bool
isOpenShift4 bool
initObjects []runtime.Object
cheCluster *orgv1.CheCluster
expectedDevfileRegistryURL string
}
testCases := []testCase{
{
name: "Test Status.DevfileRegistryURL #1",
cheCluster: &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: os.Getenv("CHE_FLAVOR"),
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
ExternalDevfileRegistry: false,
},
},
},
expectedDevfileRegistryURL: "http://devfile-registry-eclipse-che./",
},
{
name: "Test Status.DevfileRegistryURL #2",
cheCluster: &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: os.Getenv("CHE_FLAVOR"),
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
ExternalDevfileRegistry: false,
DevfileRegistryUrl: "https://devfile-registry.external.1",
ExternalDevfileRegistries: []orgv1.ExternalDevfileRegistries{
{Url: "https://devfile-registry.external.2"},
},
},
},
},
expectedDevfileRegistryURL: "http://devfile-registry-eclipse-che./",
},
{
name: "Test Status.DevfileRegistryURL #2",
cheCluster: &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: os.Getenv("CHE_FLAVOR"),
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
ExternalDevfileRegistry: true,
DevfileRegistryUrl: "https://devfile-registry.external.1",
ExternalDevfileRegistries: []orgv1.ExternalDevfileRegistries{
{Url: "https://devfile-registry.external.2"},
},
},
},
},
expectedDevfileRegistryURL: "",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true)))
scheme := scheme.Scheme
orgv1.SchemeBuilder.AddToScheme(scheme)
testCase.initObjects = append(testCase.initObjects, testCase.cheCluster)
cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
if !ok {
t.Fatal("Error creating fake discovery client")
}
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
}
util.IsOpenShift = testCase.isOpenShift
util.IsOpenShift4 = testCase.isOpenShift4
_, err := r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
cr := &orgv1.CheCluster{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cr); err != nil {
t.Errorf("CR not found")
}
if cr.Status.DevfileRegistryURL != testCase.expectedDevfileRegistryURL {
t.Fatalf("Exected: %s, but found: %s", testCase.expectedDevfileRegistryURL, cr.Status.DevfileRegistryURL)
}
})
}
}
func TestCheController(t *testing.T) {
var err error
util.IsOpenShift = true
util.IsOpenShift4 = false
cl, dc, scheme := Init()
// Create a ReconcileChe object with the scheme and fake client
r := NewReconciler(cl, cl, dc, &scheme, "")
r.tests = true
// get CR
cheCR := &orgv1.CheCluster{
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
CheHost: "eclipse.org",
},
},
}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil {
t.Errorf("CR not found")
}
// Mock request to simulate Reconcile() being called on an event for a
// watched resource .
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
}
reconcileLoops := 4
for i := 0; i < reconcileLoops; i++ {
_, err = r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
}
// get devfile-registry configmap
devfilecm := &corev1.ConfigMap{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DevfileRegistryName, Namespace: cheCR.Namespace}, devfilecm); err != nil {
t.Errorf("ConfigMap %s not found: %s", devfilecm.Name, err)
}
// get plugin-registry configmap
pluginRegistrycm := &corev1.ConfigMap{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DevfileRegistryName, Namespace: cheCR.Namespace}, pluginRegistrycm); err != nil {
t.Errorf("ConfigMap %s not found: %s", pluginRegistrycm.Name, err)
}
// get CR
if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil {
t.Errorf("CR not found")
}
// update CR and make sure Che configmap has been updated
cheCR.Spec.Server.TlsSupport = true
if err := cl.Update(context.TODO(), cheCR); err != nil {
t.Error("Failed to update CheCluster custom resource")
}
// reconcile again
for i := 0; i < reconcileLoops; i++ {
_, err = r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
}
// get configmap
cm := &corev1.ConfigMap{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: cheCR.Namespace}, cm); err != nil {
t.Errorf("ConfigMap %s not found: %s", cm.Name, err)
}
customCm := &corev1.ConfigMap{}
// Custom ConfigMap should be gone
err = cl.Get(context.TODO(), types.NamespacedName{Name: "custom", Namespace: cheCR.Namespace}, customCm)
if !errors.IsNotFound(err) {
t.Errorf("Custom config map should be deleted and merged with Che ConfigMap")
}
// Get the custom role binding that should have been created for the role we passed in
rb := &rbac.RoleBinding{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che-workspace-custom", Namespace: cheCR.Namespace}, rb); err != nil {
t.Errorf("Custom role binding %s not found: %s", rb.Name, err)
}
// run a few checks to make sure the operator reconciled tls routes and updated configmap
if cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] != "true" {
// If the test fails here without obvious reason, it could mean that there was not enought reconcile loops before.
// To fix the above problem, just increase reconcileLoops variable above.
t.Errorf("ConfigMap wasn't updated. Expecting true, but got: %s", cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"])
}
route := &routev1.Route{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultCheFlavor(cheCR), Namespace: cheCR.Namespace}, route); err != nil {
t.Errorf("Route %s not found: %s", cm.Name, err)
}
if route.Spec.TLS.Termination != "edge" {
t.Errorf("Test failed as %s %s is not a TLS route", route.Kind, route.Name)
}
// get CR
if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil {
t.Errorf("CR not found")
}
// update CR and make sure Che configmap has been updated
cheCR.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(true)
if err := cl.Update(context.TODO(), cheCR); err != nil {
t.Error("Failed to update CheCluster custom resource")
}
_, err = r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
// get configmap and check if identity provider name and workspace project name are correctly set
cm = &corev1.ConfigMap{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: cheCR.Namespace}, cm); err != nil {
t.Errorf("ConfigMap %s not found: %s", cm.Name, err)
}
_, isOpenshiftv4, err := util.DetectOpenShift()
if err != nil {
logrus.Errorf("Error detecting openshift version: %v", err)
}
expectedIdentityProviderName := "openshift-v3"
if isOpenshiftv4 {
expectedIdentityProviderName = "openshift-v4"
}
if cm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"] != expectedIdentityProviderName {
t.Errorf("ConfigMap wasn't updated properly. Expecting '%s', got: '%s'", expectedIdentityProviderName, cm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"])
}
clusterAPI := deploy.ClusterAPI{
Client: r.client,
NonCachingClient: r.client,
Scheme: r.Scheme,
}
deployContext := &deploy.DeployContext{
CheCluster: cheCR,
ClusterAPI: clusterAPI,
}
if err = r.client.Get(context.TODO(), types.NamespacedName{Name: cheCR.Name, Namespace: cheCR.Namespace}, cheCR); err != nil {
t.Errorf("Failed to get the Che custom resource %s: %s", cheCR.Name, err)
}
if _, err = identity_provider.SyncOpenShiftIdentityProviderItems(deployContext); err != nil {
t.Errorf("Failed to create the items for the identity provider: %s", err)
}
oAuthClientName := cheCR.Spec.Auth.OAuthClientName
oauthSecret := cheCR.Spec.Auth.OAuthSecret
oAuthClient := &oauthv1.OAuthClient{}
if err = r.client.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName, Namespace: ""}, oAuthClient); err != nil {
t.Errorf("Failed to Get oAuthClient %s: %s", oAuthClient.Name, err)
}
if oAuthClient.Secret != oauthSecret {
t.Errorf("Secrets do not match. Expecting %s, got %s", oauthSecret, oAuthClient.Secret)
}
// check if a new Postgres deployment is not created when spec.Database.ExternalDB is true
cheCR.Spec.Database.ExternalDb = true
if err := cl.Update(context.TODO(), cheCR); err != nil {
t.Error("Failed to update CheCluster custom resource")
}
postgresDeployment := &appsv1.Deployment{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: cheCR.Namespace}, postgresDeployment)
err = r.client.Delete(context.TODO(), postgresDeployment)
_, err = r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.PostgresName, Namespace: cheCR.Namespace}, postgresDeployment)
if err == nil {
t.Fatalf("Deployment postgres shoud not exist")
}
// check of storageClassName ends up in pvc spec
fakeStorageClassName := "fake-storage-class-name"
cheCR.Spec.Storage.PostgresPVCStorageClassName = fakeStorageClassName
cheCR.Spec.Database.ExternalDb = false
if err := r.client.Update(context.TODO(), cheCR); err != nil {
t.Fatalf("Failed to update %s CR: %s", cheCR.Name, err)
}
pvc := &corev1.PersistentVolumeClaim{}
if err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultPostgresVolumeClaimName, Namespace: cheCR.Namespace}, pvc); err != nil {
t.Fatalf("Failed to get PVC: %s", err)
}
if err = r.client.Delete(context.TODO(), pvc); err != nil {
t.Fatalf("Failed to delete PVC %s: %s", pvc.Name, err)
}
_, err = r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
pvc = &corev1.PersistentVolumeClaim{}
if err = r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultPostgresVolumeClaimName, Namespace: cheCR.Namespace}, pvc); err != nil {
t.Fatalf("Failed to get PVC: %s", err)
}
actualStorageClassName := pvc.Spec.StorageClassName
if len(*actualStorageClassName) != len(fakeStorageClassName) {
t.Fatalf("Expecting %s storageClassName, got %s", fakeStorageClassName, *actualStorageClassName)
}
// Get CheCR one more time to get it with newer Che url in the status.
r.client.Get(context.TODO(), types.NamespacedName{Name: cheCR.GetName(), Namespace: cheCR.GetNamespace()}, cheCR)
if err != nil {
t.Fatalf("Failed to get custom resource Eclipse Che: %s", err.Error())
}
if cheCR.Status.CheURL != "https://eclipse.org" {
t.Fatalf("Expected che host url in the custom resource status: %s, but got %s", "https://eclipse.org", cheCR.Status.CheURL)
}
// check if oAuthClient is deleted after CR is deleted (finalizer logic)
// since fake api does not set deletion timestamp, CR is updated in tests rather than deleted
logrus.Info("Updating CR with deletion timestamp")
deletionTimestamp := &metav1.Time{Time: time.Now()}
cheCR.DeletionTimestamp = deletionTimestamp
if err := r.client.Update(context.TODO(), cheCR); err != nil {
t.Fatalf("Failed to update CR: %s", err)
}
if err := deploy.ReconcileOAuthClientFinalizer(deployContext); err != nil {
t.Fatal("Failed to reconcile oAuthClient")
}
oauthClientName := cheCR.Spec.Auth.OAuthClientName
oauthClient := &oauthv1.OAuthClient{}
err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName}, oauthClient)
if err == nil {
t.Fatalf("OauthClient %s has not been deleted", oauthClientName)
}
logrus.Infof("Disregard the error above. OauthClient %s has been deleted", oauthClientName)
}
func TestConfiguringLabelsForRoutes(t *testing.T) {
util.IsOpenShift = true
// Set the logger to development mode for verbose logs.
logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true)))
cl, dc, scheme := Init()
// Create a ReconcileChe object with the scheme and fake client
r := NewReconciler(cl, cl, dc, &scheme, "")
r.tests = true
// get CR
cheCR := &orgv1.CheCluster{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil {
t.Errorf("CR not found")
}
// Mock request to simulate Reconcile() being called on an event for a
// watched resource .
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
}
// reconcile
_, err := r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: os.Getenv("CHE_FLAVOR"), Namespace: namespace}, cheCR); err != nil {
t.Errorf("CR not found")
}
cheCR.Spec.Server.CheServerRoute.Labels = "route=one"
if err := cl.Update(context.TODO(), cheCR); err != nil {
t.Error("Failed to update CheCluster custom resource")
}
// reconcile again
_, err = r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
// get route
route := &routev1.Route{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: deploy.DefaultCheFlavor(cheCR), Namespace: cheCR.Namespace}, route); err != nil {
t.Errorf("Route %s not found: %s", route.Name, err)
}
if route.ObjectMeta.Labels["route"] != "one" {
t.Fatalf("Route '%s' does not have label '%s'", route.Name, route)
}
}
func TestShouldDelegatePermissionsForCheWorkspaces(t *testing.T) {
util.IsOpenShift = true
type testCase struct {
name string
initObjects []runtime.Object
clusterRole bool
checluster *orgv1.CheCluster
}
// the same namespace with Che
crWsInTheSameNs1 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs1.Spec.Server.WorkspaceNamespaceDefault = crWsInTheSameNs1.Namespace
crWsInTheSameNs2 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs2.Spec.Server.WorkspaceNamespaceDefault = ""
crWsInTheSameNs3 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs3.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInTheSameNs3.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = ""
crWsInTheSameNs4 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs4.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInTheSameNs4.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = crWsInTheSameNs1.Namespace
// differ namespace with Che
crWsInAnotherNs1 := InitCheWithSimpleCR().DeepCopy()
crWsInAnotherNs1.Spec.Server.WorkspaceNamespaceDefault = "some-test-namespace"
crWsInAnotherNs2 := InitCheWithSimpleCR().DeepCopy()
crWsInAnotherNs2.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInAnotherNs2.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = "some-test-namespace"
crWsInAnotherNs3 := InitCheWithSimpleCR().DeepCopy()
crWsInAnotherNs3.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInAnotherNs3.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = crWsInTheSameNs1.Namespace
crWsInAnotherNs3.Spec.Server.WorkspaceNamespaceDefault = "some-test-namespace"
testCases := []testCase{
{
name: "che-operator should delegate permission for workspaces in differ namespace than Che. WorkspaceNamespaceDefault = 'some-test-namespace'",
initObjects: []runtime.Object{},
clusterRole: true,
checluster: crWsInAnotherNs1,
},
{
name: "che-operator should delegate permission for workspaces in differ namespace than Che. Property CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT = 'some-test-namespace'",
initObjects: []runtime.Object{},
clusterRole: true,
checluster: crWsInAnotherNs2,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true)))
scheme := scheme.Scheme
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(oauthv1.SchemeGroupVersion, &oauthv1.OAuthClient{})
scheme.AddKnownTypes(userv1.SchemeGroupVersion, &userv1.UserList{}, &userv1.User{})
scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.OAuth{}, &configv1.Proxy{})
scheme.AddKnownTypes(routev1.GroupVersion, &routev1.Route{})
initCR := testCase.checluster
initCR.Spec.Auth.OpenShiftoAuth = pointer.BoolPtr(false)
testCase.initObjects = append(testCase.initObjects, initCR)
cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
clientSet := fakeclientset.NewSimpleClientset()
// todo do we need fake discovery
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{}
if !ok {
t.Fatal("Error creating fake discovery client")
}
var m *mocks.MockPermissionChecker
if testCase.clusterRole {
ctrl := gomock.NewController(t)
m = mocks.NewMockPermissionChecker(ctrl)
m.EXPECT().GetNotPermittedPolicyRules(gomock.Any(), "").Return([]rbac.PolicyRule{}, nil).MaxTimes(2)
defer ctrl.Finish()
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
}
_, err := r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
_, err = r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
if !testCase.clusterRole {
viewRole := &rbac.Role{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.ViewRoleName, Namespace: namespace}, viewRole); err != nil {
t.Errorf("role '%s' not found", deploy.ViewRoleName)
}
viewRoleBinding := &rbac.RoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ViewRoleBindingName, Namespace: namespace}, viewRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", ViewRoleBindingName)
}
execRole := &rbac.Role{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.ExecRoleName, Namespace: namespace}, execRole); err != nil {
t.Errorf("role '%s' not found", deploy.ExecRoleName)
}
execRoleBinding := &rbac.RoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ExecRoleBindingName, Namespace: namespace}, execRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", ExecRoleBindingName)
}
editRoleBinding := &rbac.RoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: EditRoleBindingName, Namespace: namespace}, editRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", EditRoleBindingName)
}
} else {
manageNamespacesClusterRoleName := fmt.Sprintf(CheNamespaceEditorClusterRoleNameTemplate, namespace)
cheManageNamespaceClusterRole := &rbac.ClusterRole{}
if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: manageNamespacesClusterRoleName}, cheManageNamespaceClusterRole); err != nil {
t.Errorf("role '%s' not found", manageNamespacesClusterRoleName)
}
cheManageNamespaceClusterRoleBinding := &rbac.ClusterRoleBinding{}
if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: manageNamespacesClusterRoleName}, cheManageNamespaceClusterRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", manageNamespacesClusterRoleName)
}
cheWorkspacesClusterRoleName := fmt.Sprintf(CheWorkspacesClusterRoleNameTemplate, namespace)
cheWorkspacesClusterRole := &rbac.ClusterRole{}
if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: cheWorkspacesClusterRoleName}, cheWorkspacesClusterRole); err != nil {
t.Errorf("role '%s' not found", cheWorkspacesClusterRole)
}
cheWorkspacesClusterRoleBinding := &rbac.ClusterRoleBinding{}
if err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Name: cheWorkspacesClusterRoleName}, cheWorkspacesClusterRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", cheWorkspacesClusterRole)
}
}
})
}
}
func Init() (client.Client, discovery.DiscoveryInterface, runtime.Scheme) {
objs, ds, scheme := createAPIObjects()
oAuthClient := &oauthv1.OAuthClient{}
users := &userv1.UserList{}
user := &userv1.User{}
// Register operator types with the runtime scheme
scheme.AddKnownTypes(oauthv1.SchemeGroupVersion, oAuthClient)
scheme.AddKnownTypes(userv1.SchemeGroupVersion, users, user)
scheme.AddKnownTypes(configv1.SchemeGroupVersion, &configv1.Proxy{})
// Create a fake client to mock API calls
return fake.NewFakeClient(objs...), ds, scheme
}
func createAPIObjects() ([]runtime.Object, discovery.DiscoveryInterface, runtime.Scheme) {
pgPod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-pg-pod",
Namespace: "eclipse-che",
Labels: map[string]string{
"component": deploy.PostgresName,
},
},
}
// A CheCluster custom resource with metadata and spec
cheCR := InitCheWithSimpleCR()
route := &routev1.Route{
ObjectMeta: metav1.ObjectMeta{
Name: deploy.DefaultCheFlavor(cheCR),
Namespace: namespace,
},
}
packageManifest := &packagesv1.PackageManifest{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes-imagepuller-operator",
Namespace: namespace,
},
}
// Objects to track in the fake client.
objs := []runtime.Object{
cheCR, pgPod, route, packageManifest,
}
// Register operator types with the runtime scheme
scheme := scheme.Scheme
scheme.AddKnownTypes(orgv1.GroupVersion, cheCR)
scheme.AddKnownTypes(routev1.SchemeGroupVersion, route)
scheme.AddKnownTypes(console.GroupVersion, &console.ConsoleLink{})
chev1alpha1.AddToScheme(scheme)
packagesv1.AddToScheme(scheme)
operatorsv1.AddToScheme(scheme)
operatorsv1alpha1.AddToScheme(scheme)
cli := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := cli.Discovery().(*fakeDiscovery.FakeDiscovery)
if !ok {
logrus.Error("Error creating fake discovery client")
os.Exit(1)
}
// Create a fake client to mock API calls
return objs, fakeDiscovery, *scheme
}
func InitCheWithSimpleCR() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
CheWorkspaceClusterRole: "cluster-admin",
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: pointer.BoolPtr(false),
},
},
}
}