drupalsite_controller.go 7.61 KB
Newer Older
1
/*
2
Copyright 2021.
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
Vineet Reddy Rajula's avatar
Vineet Reddy Rajula committed
24
25
	buildv1 "github.com/openshift/api/build/v1"
	imagev1 "github.com/openshift/api/image/v1"
26
27
	routev1 "github.com/openshift/api/route/v1"
	"github.com/operator-framework/operator-lib/status"
28
	webservicesv1a1 "gitlab.cern.ch/drupal/paas/drupalsite-operator/api/v1alpha1"
29
	appsv1 "k8s.io/api/apps/v1"
30
	batchv1 "k8s.io/api/batch/v1"
31
32
33
34
35
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
36
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
37
38
39
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

40
41
// DrupalSiteReconciler reconciles a DrupalSite object
type DrupalSiteReconciler struct {
42
43
44
45
46
	client.Client
	Log    logr.Logger
	Scheme *runtime.Scheme
}

47
48
49
// +kubebuilder:rbac:groups=drupal.webservices.cern.ch,resources=drupalsites,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=drupal.webservices.cern.ch,resources=drupalsites/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=drupal.webservices.cern.ch,resources=drupalsites/finalizers,verbs=update
50
// +kubebuilder:rbac:groups=app,resources=deployments,verbs=*
51
52
53
54
55
// +kubebuilder:rbac:groups=build.openshift.io,resources=buildconfig,verbs=*
// +kubebuilder:rbac:groups=image.openshift.io,resources=imagestream,verbs=*
// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=*
// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims;services,verbs=*
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=*
56
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;
57
58
59
60
61

// SetupWithManager adds a manager which watches the resources
func (r *DrupalSiteReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&webservicesv1a1.DrupalSite{}).
62
		Owns(&appsv1.Deployment{}).
63
64
65
66
67
68
69
70
		Owns(&buildv1.BuildConfig{}).
		Owns(&imagev1.ImageStream{}).
		Owns(&routev1.Route{}).
		Owns(&corev1.PersistentVolumeClaim{}).
		Owns(&corev1.Service{}).
		Owns(&batchv1.Job{}).
		Complete(r)
}
71

72
func (r *DrupalSiteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
73
74
75
76
77
	// _ = context.Background()
	log := r.Log.WithValues("Request.Namespace", req.NamespacedName, "Request.Name", req.Name)

	log.Info("Reconciling request")

78
	// Fetch the DrupalSite instance
79
80
	drupalSite := &webservicesv1a1.DrupalSite{}
	err := r.Get(ctx, req.NamespacedName, drupalSite)
81
82
83
84
85
	if err != nil {
		if errors.IsNotFound(err) {
			// Request object not found, could have been deleted after reconcile request.
			// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
			// Return and don't requeue
86
			log.Info("DrupalSite resource not found. Ignoring since object must be deleted")
87
88
89
			return ctrl.Result{}, nil
		}
		// Error reading the object - requeue the request.
90
		log.Error(err, "Failed to get DrupalSite")
91
92
93
94
		return ctrl.Result{}, err
	}

	//Handle deletion
95
	if drupalSite.GetDeletionTimestamp() != nil {
96
97
98
99
		if controllerutil.ContainsFinalizer(drupalSite, finalizerStr) {
			return r.cleanupDrupalSite(ctx, log, drupalSite)
		}
		return ctrl.Result{}, nil
100
101
102
	}

	handleTransientErr := func(transientErr reconcileError, logstrFmt string) (reconcile.Result, error) {
103
104
		setNotReady(drupalSite, transientErr)
		r.updateCRStatusorFailReconcile(ctx, log, drupalSite)
105
106
107
108
109
110
111
112
113
		if transientErr.Temporary() {
			log.Error(transientErr, fmt.Sprintf(logstrFmt, transientErr.Unwrap()))
			return reconcile.Result{}, transientErr
		}
		log.Error(transientErr, "Permanent error marked as transient! Permanent errors should not bubble up to the reconcile loop.")
		return reconcile.Result{}, nil
	}

	// Init. Check if finalizer is set. If not, set it, validate and update CR status
114
	if update := ensureSpecFinalizer(drupalSite, log); update {
115
		log.Info("Initializing DrupalSite Spec")
116
		return r.updateCRorFailReconcile(ctx, log, drupalSite)
117
	}
118
	if err := validateSpec(drupalSite.Spec); err != nil {
119
		log.Error(err, fmt.Sprintf("%v failed to validate DrupalSite spec", err.Unwrap()))
120
121
		setErrorCondition(drupalSite, err)
		return r.updateCRStatusorFailReconcile(ctx, log, drupalSite)
122
123
	}

124
125
126
127
	// Ensure installed - Installed status
	// Create route

	// Ensure all primary resources
Vineet Reddy Rajula's avatar
Vineet Reddy Rajula committed
128
	if transientErr := r.ensureResources(drupalSite, log); transientErr != nil {
129
130
131
132
133
134
135
136
		setNotReady(drupalSite, transientErr)
		return handleTransientErr(transientErr, "%v while creating the resources")
	}

	// Check if the drupal site is ready to serve requests
	if siteReady := r.isDrupalSiteReady(ctx, drupalSite); siteReady {
		if update := setReady(drupalSite); update {
			return r.updateCRStatusorFailReconcile(ctx, log, drupalSite)
137
		}
138
139
	}

140
141
142
143
144
145
146
147
148
	// Check if the site is installed and mark the condition
	if installed := r.isInstallJobCompleted(ctx, drupalSite); installed {
		if update := setInstalled(drupalSite); update {
			return r.updateCRStatusorFailReconcile(ctx, log, drupalSite)
		}
	} else {
		if update := setNotInstalled(drupalSite); update {
			return r.updateCRStatusorFailReconcile(ctx, log, drupalSite)
		}
149
150
	}

151
152
	// If the installed status and ready status is true, create the route
	if drupalSite.ConditionTrue("Installed") && drupalSite.ConditionTrue("Ready") {
Vineet Reddy Rajula's avatar
Vineet Reddy Rajula committed
153
		if transientErr := r.ensureIngressResources(drupalSite, log); transientErr != nil {
154
155
156
			return handleTransientErr(transientErr, "%v while creating route")
		}
		return r.updateCRorFailReconcile(ctx, log, drupalSite)
157
158
159
160
161
	}

	return ctrl.Result{}, nil
}

162
// cleanupDrupalSite checks and removes if a finalizer exists on the resource
163
func (r *DrupalSiteReconciler) cleanupDrupalSite(ctx context.Context, log logr.Logger, drp *webservicesv1a1.DrupalSite) (ctrl.Result, error) {
164
165
166
167
168
169
	// finalizer: dependentResources
	// 1. check if such resources exist
	//   - delete them
	//   - reconcile
	// 1. if not, delete the finalizer key manually and let Kubernetes delete the resource cleanly
	// TODO
170
	log.Info("Deleting DrupalSite")
171
172
	controllerutil.RemoveFinalizer(drp, finalizerStr)
	return r.updateCRorFailReconcile(ctx, log, drp)
173
174
}

175
func setReady(drp *webservicesv1a1.DrupalSite) (update bool) {
176
177
178
179
180
	return drp.Status.Conditions.SetCondition(status.Condition{
		Type:   "Ready",
		Status: "True",
	})
}
181
func setNotReady(drp *webservicesv1a1.DrupalSite, transientErr reconcileError) (update bool) {
182
183
184
185
186
187
188
	return drp.Status.Conditions.SetCondition(status.Condition{
		Type:    "Ready",
		Status:  "False",
		Reason:  status.ConditionReason(transientErr.Unwrap().Error()),
		Message: transientErr.Error(),
	})
}
189
190
191
192
193
194
195
196
197
198
199
200
func setInstalled(drp *webservicesv1a1.DrupalSite) (update bool) {
	return drp.Status.Conditions.SetCondition(status.Condition{
		Type:   "Installed",
		Status: "True",
	})
}
func setNotInstalled(drp *webservicesv1a1.DrupalSite) (update bool) {
	return drp.Status.Conditions.SetCondition(status.Condition{
		Type:   "Installed",
		Status: "False",
	})
}
201
func setErrorCondition(drp *webservicesv1a1.DrupalSite, err reconcileError) (update bool) {
202
203
204
205
206
207
208
	return drp.Status.Conditions.SetCondition(status.Condition{
		Type:    "Error",
		Status:  "True",
		Reason:  status.ConditionReason(err.Unwrap().Error()),
		Message: err.Error(),
	})
}