Skip to content

Commit 12fa1a5

Browse files
committed
example,pkg/test/e2eutil,test/e2e: update go e2e test to support and check for status conditions
1 parent 2609258 commit 12fa1a5

File tree

3 files changed

+155
-14
lines changed

3 files changed

+155
-14
lines changed

example/memcached-operator/memcached_controller.go.tmpl

+91-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package memcached
216

317
import (
@@ -6,6 +20,7 @@ import (
620

721
cachev1alpha1 "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1"
822

23+
"github.com/operator-framework/operator-sdk/pkg/status"
924
appsv1 "k8s.io/api/apps/v1"
1025
corev1 "k8s.io/api/core/v1"
1126
"k8s.io/apimachinery/pkg/api/errors"
@@ -108,31 +123,48 @@ func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Res
108123

109124
// Check if the deployment already exists, if not create a new one
110125
found := &appsv1.Deployment{}
111-
err = r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)
112-
if err != nil && errors.IsNotFound(err) {
126+
getErr := r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)
127+
if getErr != nil && errors.IsNotFound(getErr) {
113128
// Define a new deployment
114129
dep := r.deploymentForMemcached(memcached)
115130
reqLogger.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
116-
err = r.client.Create(context.TODO(), dep)
117-
if err != nil {
118-
reqLogger.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
119-
return reconcile.Result{}, err
131+
if createErr := r.client.Create(context.TODO(), dep); createErr != nil {
132+
reqLogger.Error(createErr, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
133+
134+
// Update status not ready
135+
if setStatusErr := r.setStatusNotReady(memcached, createErr); setStatusErr != nil {
136+
return reconcile.Result{}, setStatusErr
137+
}
138+
return reconcile.Result{}, createErr
139+
}
140+
// Update status not ready
141+
if setStatusErr := r.setStatusNotReady(memcached, getErr); setStatusErr != nil {
142+
return reconcile.Result{}, setStatusErr
120143
}
121144
// Deployment created successfully - return and requeue
122145
return reconcile.Result{Requeue: true}, nil
123-
} else if err != nil {
124-
reqLogger.Error(err, "Failed to get Deployment")
146+
} else if getErr != nil {
147+
reqLogger.Error(getErr, "Failed to get Deployment")
148+
149+
// Update status not ready
150+
if setStatusErr := r.setStatusNotReady(memcached, getErr); setStatusErr != nil {
151+
return reconcile.Result{}, setStatusErr
152+
}
125153
return reconcile.Result{}, err
126154
}
127155

128156
// Ensure the deployment size is the same as the spec
129157
size := memcached.Spec.Size
130158
if *found.Spec.Replicas != size {
131159
found.Spec.Replicas = &size
132-
err = r.client.Update(context.TODO(), found)
133-
if err != nil {
134-
reqLogger.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
135-
return reconcile.Result{}, err
160+
if updateErr := r.client.Update(context.TODO(), found); updateErr != nil {
161+
reqLogger.Error(updateErr, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
162+
163+
// Update status not ready
164+
if setStatusErr := r.setStatusNotReady(memcached, updateErr); setStatusErr != nil {
165+
return reconcile.Result{}, setStatusErr
166+
}
167+
return reconcile.Result{}, updateErr
136168
}
137169
// Spec updated - return and requeue
138170
return reconcile.Result{Requeue: true}, nil
@@ -153,13 +185,18 @@ func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Res
153185
// Update status.Nodes if needed
154186
if !reflect.DeepEqual(podNames, memcached.Status.Nodes) {
155187
memcached.Status.Nodes = podNames
156-
err := r.client.Status().Update(context.TODO(), memcached)
188+
err := r.updateStatus(memcached)
157189
if err != nil {
158190
reqLogger.Error(err, "Failed to update Memcached status")
159191
return reconcile.Result{}, err
160192
}
161193
}
162194

195+
// Set the Memcached status Ready condition to True, if necessary
196+
if setStatusErr := r.setStatusReady(memcached); setStatusErr != nil {
197+
return reconcile.Result{}, setStatusErr
198+
}
199+
163200
return reconcile.Result{}, nil
164201
}
165202

@@ -219,3 +256,44 @@ func getPodNames(pods []corev1.Pod) []string {
219256
}
220257
return podNames
221258
}
259+
260+
func (r *ReconcileMemcached) updateStatus(m *cachev1alpha1.Memcached) error {
261+
if m.Status.Nodes == nil {
262+
m.Status.Nodes = []string{}
263+
}
264+
if m.Status.Conditions == nil {
265+
m.Status.Conditions = status.Conditions{}
266+
}
267+
return r.client.Status().Update(context.TODO(), m)
268+
}
269+
270+
func (r *ReconcileMemcached) setStatusReady(m *cachev1alpha1.Memcached) error {
271+
if !m.Status.Conditions.IsTrue(status.ConditionType("Ready")) {
272+
if setErr := m.Status.Conditions.SetCondition(&status.BasicCondition{
273+
Type: status.ConditionType("Ready"),
274+
Status: corev1.ConditionTrue,
275+
}); setErr != nil {
276+
return setErr
277+
}
278+
if updateErr := r.updateStatus(m); updateErr != nil {
279+
return updateErr
280+
}
281+
}
282+
return nil
283+
}
284+
285+
func (r *ReconcileMemcached) setStatusNotReady(m *cachev1alpha1.Memcached, origErr error) error {
286+
if !m.Status.Conditions.IsFalse(status.ConditionType("Ready")) {
287+
if setErr := m.Status.Conditions.SetCondition(&status.BasicCondition{
288+
Type: status.ConditionType("Ready"),
289+
Status: corev1.ConditionFalse,
290+
Message: origErr.Error(),
291+
}); setErr != nil {
292+
return setErr
293+
}
294+
if updateErr := r.updateStatus(m); updateErr != nil {
295+
return updateErr
296+
}
297+
}
298+
return nil
299+
}

pkg/test/e2eutil/wait_util.go

+60
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ package e2eutil
1616

1717
import (
1818
"context"
19+
"encoding/json"
1920
"testing"
2021
"time"
2122

23+
"github.com/operator-framework/operator-sdk/pkg/status"
2224
"github.com/operator-framework/operator-sdk/pkg/test"
2325

26+
v1 "k8s.io/api/core/v1"
2427
apierrors "k8s.io/apimachinery/pkg/api/errors"
2528
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2629
"k8s.io/apimachinery/pkg/runtime"
@@ -71,6 +74,8 @@ func waitForDeployment(t *testing.T, kubeclient kubernetes.Interface, namespace,
7174
return nil
7275
}
7376

77+
// WaitForDeletion checks to see if a given object is deleted, trying every retryInterval until the timeout has elapsed.
78+
// If the object has not been deleted before the timeout, the function returns an error.
7479
func WaitForDeletion(t *testing.T, dynclient client.Client, obj runtime.Object, retryInterval, timeout time.Duration) error {
7580
key, err := client.ObjectKeyFromObject(obj)
7681
if err != nil {
@@ -97,3 +102,58 @@ func WaitForDeletion(t *testing.T, dynclient client.Client, obj runtime.Object,
97102
t.Logf("%s %s was deleted\n", kind, key)
98103
return nil
99104
}
105+
106+
// WaitForCondition checks to see if a given object has a given condition type/status pair, trying every retryInterval until
107+
// the timeout has elapsed. If the condition is not found before the timeout, the function returns an error.
108+
func WaitForCondition(t *testing.T, dynclient client.Client, obj runtime.Object, cType status.ConditionType, cStatus v1.ConditionStatus, retryInterval, timeout time.Duration) error {
109+
key, err := client.ObjectKeyFromObject(obj)
110+
if err != nil {
111+
return err
112+
}
113+
114+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
115+
defer cancel()
116+
err = wait.Poll(retryInterval, timeout, func() (done bool, err error) {
117+
err = dynclient.Get(ctx, key, obj)
118+
if err != nil {
119+
return false, err
120+
}
121+
122+
data, err := json.Marshal(obj)
123+
if err != nil {
124+
return false, err
125+
}
126+
127+
var cObj struct {
128+
Status struct {
129+
Conditions status.Conditions `json:"conditions"`
130+
} `json:"status"`
131+
}
132+
err = json.Unmarshal(data, &cObj)
133+
if err != nil {
134+
return false, err
135+
}
136+
137+
var c status.BasicCondition
138+
err = cObj.Status.Conditions.GetCondition(cType, &c)
139+
if err != nil {
140+
if err != status.ErrConditionNotFound {
141+
return false, err
142+
}
143+
t.Logf("waiting for status %s %s, condition not found", cType, cStatus)
144+
return false, nil
145+
}
146+
147+
if cStatus != c.GetStatus() {
148+
t.Logf("waiting for status %s %s, got %s", cType, cStatus, c.GetStatus())
149+
return false, nil
150+
}
151+
152+
return true, nil
153+
})
154+
if err != nil {
155+
return err
156+
}
157+
t.Logf("Found condition %s %s\n", cType, cStatus)
158+
return nil
159+
}

test/e2e/memcached_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,10 @@ func memcachedScaleTest(t *testing.T, f *framework.Framework, ctx *framework.Tes
484484
}
485485

486486
// wait for example-memcached to reach 4 replicas
487-
return e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "example-memcached", 4, retryInterval, timeout)
487+
if err := e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "example-memcached", 4, retryInterval, timeout); err != nil {
488+
return err
489+
}
490+
return e2eutil.WaitForCondition(t, f.Client.Client, &obj, "Ready", v1.ConditionTrue, retryInterval, timeout)
488491
}
489492

490493
func MemcachedLocal(t *testing.T) {

0 commit comments

Comments
 (0)