Add finalizers. Make storageclass configurable. More tests (#12)

* Add finalizers. Make storageclass configurable. More tests

* Fix logs
pull/13/head
Eugene Ivantsov 2019-04-09 16:03:51 +03:00 committed by GitHub
parent d88e2bc677
commit 9dc8a3c50b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 170 additions and 35 deletions

View File

@ -65,7 +65,12 @@ spec:
# instruct Che server to launch a special pod to precreate a subpath in a PV
preCreateSubPaths: true
# image:tag for preCreateSubPaths jobs
pvcJobsImage:
pvcJobsImage: ''
# keep blank unless you need to use a non default storage class for Postgres PVC
postgresPVCStorageClassName: ''
# keep blank unless you need to use a non default storage class for workspace PVC(s)
workspacePVCStorageClassName: ''
auth:
# instructs operator on whether or not to deploy Keycloak/RH SSO instance. When set to true provision connection details
externalKeycloak:

View File

@ -120,6 +120,10 @@ type CheClusterSpecStorage struct {
PreCreateSubPaths bool `json:"preCreateSubPaths"`
// PvcJobsImage is image:tag for preCreateSubPaths jobs
PvcJobsImage string `json:"pvcJobsImage"`
// PostgresPVCStorageClassName is storage class for a postgres pvc. Empty string by default, which means default storage class is used
PostgresPVCStorageClassName string `json:"postgresPVCStorageClassName"`
// WorkspacePVCStorageClassName is storage class for a workspaces pvc. Empty string by default, which means default storage class is used
WorkspacePVCStorageClassName string `json:"workspacePVCStorageClassName"`
}
type CheClusterSpecK8SOnly struct {

View File

@ -1,14 +1,3 @@
//
// Copyright (c) 2012-2019 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
//
// +build !ignore_autogenerated
/*

View File

@ -173,6 +173,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
}
var _ reconcile.Reconciler = &ReconcileChe{}
var oAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org"
// ReconcileChe reconciles a CheCluster object
type ReconcileChe struct {
@ -207,6 +208,15 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
if err != nil {
logrus.Errorf("An error occurred when detecting current infra: %s", err)
}
// delete oAuthClient before CR is deleted
doInstallOpenShiftoAuthProvider := instance.Spec.Auth.OpenShiftOauth
if doInstallOpenShiftoAuthProvider {
if err := r.ReconcileFinalizer(instance); err != nil {
return reconcile.Result{}, err
}
}
// create a secret with router tls cert
if isOpenShift {
secret := &corev1.Secret{}

View File

@ -16,6 +16,7 @@ import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
oauth "github.com/openshift/api/oauth/v1"
routev1 "github.com/openshift/api/route/v1"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -25,6 +26,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"time"
"testing"
)
@ -180,4 +182,49 @@ func TestCheController(t *testing.T) {
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: "postgres-data", 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)
}
res, err = r.Reconcile(req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
pvc = &corev1.PersistentVolumeClaim{}
if err = r.client.Get(context.TODO(), types.NamespacedName{Name: "postgres-data", 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 )
}
// 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 := r.ReconcileFinalizer(cheCR); err != nil {
t.Fatal("Failed to reconcile oAuthClient")
}
oauthClientName := cheCR.Spec.Auth.OauthClientName
_, err = r.GetOAuthClient(oauthClientName)
if err == nil {
t.Fatalf("OauthClient %s has not been deleted", oauthClientName)
}
logrus.Infof("Disregard the error above. OauthClient %s has been deleted", oauthClientName)
}

View File

@ -0,0 +1,42 @@
package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
"github.com/sirupsen/logrus"
)
func (r *ReconcileChe) ReconcileFinalizer(instance *orgv1.CheCluster) (err error) {
if instance.ObjectMeta.DeletionTimestamp.IsZero() {
if !util.ContainsString(instance.ObjectMeta.Finalizers, oAuthFinalizerName) {
instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, oAuthFinalizerName)
if err := r.client.Update(context.Background(), instance); err != nil {
return err
}
}
} else {
if util.ContainsString(instance.ObjectMeta.Finalizers, oAuthFinalizerName) {
oAuthClientName := instance.Spec.Auth.OauthClientName
logrus.Infof("Custom resource %s is being deleted. Deleting oAuthClient %s first", instance.Name, oAuthClientName)
oAuthClient, err := r.GetOAuthClient(oAuthClientName)
if err != nil {
logrus.Errorf("Failed to get %s oAuthClient: %s", oAuthClientName, err)
return err
}
if err := r.client.Delete(context.TODO(), oAuthClient); err != nil {
logrus.Errorf("Failed to delete %s oAuthClient: %s", oAuthClientName, err)
return err
}
instance.ObjectMeta.Finalizers = util.DoRemoveString(instance.ObjectMeta.Finalizers, oAuthFinalizerName)
logrus.Infof("Updating %s CR", instance.Name)
if err := r.client.Update(context.Background(), instance); err != nil {
logrus.Errorf("Failed to update %s CR: %s", instance.Name, err)
return err
}
}
return nil
}
return nil
}

View File

@ -14,16 +14,17 @@ package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
oauth "github.com/openshift/api/oauth/v1"
routev1 "github.com/openshift/api/route/v1"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
routev1 "github.com/openshift/api/route/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/types"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func(r *ReconcileChe) GetEffectiveDeployment(instance *orgv1.CheCluster, name string) (deployment *appsv1.Deployment, err error) {
func (r *ReconcileChe) GetEffectiveDeployment(instance *orgv1.CheCluster, name string) (deployment *appsv1.Deployment, err error) {
deployment = &appsv1.Deployment{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, deployment)
if err != nil {
@ -33,8 +34,7 @@ func(r *ReconcileChe) GetEffectiveDeployment(instance *orgv1.CheCluster, name st
return deployment, nil
}
func(r *ReconcileChe) GetEffectiveIngress(instance *orgv1.CheCluster, name string) (ingress *v1beta1.Ingress) {
func (r *ReconcileChe) GetEffectiveIngress(instance *orgv1.CheCluster, name string) (ingress *v1beta1.Ingress) {
ingress = &v1beta1.Ingress{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, ingress)
if err != nil {
@ -44,9 +44,7 @@ func(r *ReconcileChe) GetEffectiveIngress(instance *orgv1.CheCluster, name strin
return ingress
}
func(r *ReconcileChe) GetEffectiveRoute(instance *orgv1.CheCluster, name string) (route *routev1.Route) {
func (r *ReconcileChe) GetEffectiveRoute(instance *orgv1.CheCluster, name string) (route *routev1.Route) {
route = &routev1.Route{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, route)
if err != nil {
@ -75,4 +73,13 @@ func (r *ReconcileChe) GetCR(request reconcile.Request) (instance *orgv1.CheClus
return nil, err
}
return instance, nil
}
}
func (r *ReconcileChe) GetOAuthClient(oAuthClientName string) (oAuthClient *oauth.OAuthClient, err error) {
oAuthClient = &oauth.OAuthClient{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName, Namespace: ""}, oAuthClient); err != nil {
logrus.Errorf("Failed to Get oAuthClient %s: %s", oAuthClientName, err)
return nil, err
}
return oAuthClient, nil
}

View File

@ -40,6 +40,7 @@ type CheConfigMap struct {
PvcStrategy string `json:"CHE_INFRA_KUBERNETES_PVC_STRATEGY"`
PvcClaimSize string `json:"CHE_INFRA_KUBERNETES_PVC_QUANTITY"`
PvcJobsImage string `json:"CHE_INFRA_KUBERNETES_PVC_JOBS_IMAGE"`
WorkspacePvcStorageClassName string `json:"CHE_INFRA_KUBERNETES_PVC_STORAGE__CLASS__NAME"`
PreCreateSubPaths string `json:"CHE_INFRA_KUBERNETES_PVC_PRECREATE__SUBPATHS"`
TlsSupport string `json:"CHE_INFRA_OPENSHIFT_TLS__ENABLED"`
K8STrustCerts string `json:"CHE_INFRA_KUBERNETES_TRUST__CERTS"`
@ -62,7 +63,7 @@ type CheConfigMap struct {
WebSocketEndpointMinor string `json:"CHE_WEBSOCKET_ENDPOINT__MINOR"`
}
func GetCustomConfigMapData()(cheEnv map[string]string) {
func GetCustomConfigMapData() (cheEnv map[string]string) {
cheEnv = map[string]string{
"CHE_PREDEFINED_STACKS_RELOAD__ON__START": "true",
@ -122,6 +123,7 @@ func GetConfigMapData(cr *orgv1.CheCluster) (cheEnv map[string]string) {
tlsSecretName := cr.Spec.K8SOnly.TlsSecretName
pvcStrategy := util.GetValue(cr.Spec.Storage.PvcStrategy, DefaultPvcStrategy)
pvcClaimSize := util.GetValue(cr.Spec.Storage.PvcClaimSize, DefaultPvcClaimSize)
workspacePvcStorageClassName := cr.Spec.Storage.WorkspacePVCStorageClassName
pvcJobsImage := util.GetValue(cr.Spec.Storage.PvcJobsImage, DefaultPvcJobsImage)
preCreateSubPaths := "true"
if !cr.Spec.Storage.PreCreateSubPaths {
@ -152,6 +154,7 @@ func GetConfigMapData(cr *orgv1.CheCluster) (cheEnv map[string]string) {
WorkspacesNamespace: workspacesNamespace,
PvcStrategy: pvcStrategy,
PvcClaimSize: pvcClaimSize,
WorkspacePvcStorageClassName: workspacePvcStorageClassName,
PvcJobsImage: pvcJobsImage,
PreCreateSubPaths: preCreateSubPaths,
TlsSupport: tls,

View File

@ -19,7 +19,26 @@ import (
)
func NewPvc(cr *orgv1.CheCluster, name string, pvcClaimSize string, labels map[string]string) *corev1.PersistentVolumeClaim {
//value := true
accessModes := []corev1.PersistentVolumeAccessMode{
// todo Make configurable
corev1.ReadWriteOnce,
}
resources := corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName(corev1.ResourceStorage): resource.MustParse(pvcClaimSize),
}}
pvcSpec := corev1.PersistentVolumeClaimSpec{
AccessModes: accessModes,
Resources: resources,
}
if len(cr.Spec.Storage.PostgresPVCStorageClassName) > 1 {
pvcSpec = corev1.PersistentVolumeClaimSpec{
AccessModes: accessModes,
StorageClassName: &cr.Spec.Storage.PostgresPVCStorageClassName,
Resources: resources,
}
}
return &corev1.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
@ -30,18 +49,7 @@ func NewPvc(cr *orgv1.CheCluster, name string, pvcClaimSize string, labels map[s
Namespace: cr.Namespace,
Labels: labels,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
// todo Make configurable
corev1.ReadWriteOnce,
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName(corev1.ResourceStorage): resource.MustParse(pvcClaimSize),
},
},
},
Spec: pvcSpec,
}
}

View File

@ -25,6 +25,26 @@ import (
"time"
)
func ContainsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}
func DoRemoveString(slice []string, s string) (result []string) {
for _, item := range slice {
if item == s {
continue
}
result = append(result, item)
}
return
}
func GeneratePasswd(stringLength int) (passwd string) {
rand.Seed(time.Now().UnixNano())
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +