Skip to content

Commit a2e31f2

Browse files
committed
Switch to eviction condition, remove eviction status, improve tests
* Instead of duplicated and mostly inconsistent eviction status as a status field, use conditions to display and check for the eviction status. * More conditions are anticipated to provide insights about current state of the eviction. * Improve tests by explicitly checking every iteration of a reconciliation. This can be used as a template for other tests to increase coverage.
1 parent 6df4eba commit a2e31f2

7 files changed

Lines changed: 152 additions & 128 deletions

File tree

api/v1/eviction_types.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import (
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2222
)
2323

24-
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
25-
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
26-
2724
// EvictionSpec defines the desired state of Eviction
2825
type EvictionSpec struct {
2926
// +kubebuilder:validation:Required
@@ -40,11 +37,22 @@ type EvictionSpec struct {
4037
Reason string `json:"reason"`
4138
}
4239

40+
const (
41+
// ConditionTypeEviction is the type of condition for eviction status
42+
ConditionTypeEviction = "Eviction"
43+
44+
// ConditionReasonRunning means the eviction is currently running
45+
ConditionReasonRunning string = "Running"
46+
47+
// ConditionReasonFailed means the eviction has failed
48+
ConditionReasonFailed string = "Failed"
49+
50+
// ConditionReasonSuceeded means the eviction has succeeded
51+
ConditionReasonSuceeded string = "Succeeded"
52+
)
53+
4354
// EvictionStatus defines the observed state of Eviction
4455
type EvictionStatus struct {
45-
// +kubebuilder:validation:Enum=Pending;Running;Failed;Succeeded
46-
// +kubebuilder:default=Pending
47-
EvictionState string `json:"evictionState"`
4856
// +kubebuilder:validation:Optional
4957
HypervisorServiceId string `json:"hypervisorServiceId"`
5058
// +kubebuilder:validation:Optional
@@ -62,7 +70,7 @@ type EvictionStatus struct {
6270
// +kubebuilder:resource:scope=Cluster,shortName=evi
6371
// +kubebuilder:printcolumn:JSONPath=".spec.hypervisor",name="Hypervisor",type="string"
6472
// +kubebuilder:printcolumn:JSONPath=".spec.reason",name="Reason",type="string"
65-
// +kubebuilder:printcolumn:JSONPath=".status.evictionState",name="State",type="string"
73+
// +kubebuilder:printcolumn:JSONPath=".status.conditions[?(@.type==\"Eviction\")].reason",name="State",type="string"
6674
// +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name="Created",type="date"
6775

6876
// Eviction is the Schema for the evictions API

charts/openstack-hypervisor-operator/crds/eviction-crd.yaml

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ spec:
2222
- jsonPath: .spec.reason
2323
name: Reason
2424
type: string
25-
- jsonPath: .status.evictionState
25+
- jsonPath: .status.conditions[?(@.type=="Eviction")].reason
2626
name: State
2727
type: string
2828
- jsonPath: .metadata.creationTimestamp
@@ -128,14 +128,6 @@ spec:
128128
- type
129129
type: object
130130
type: array
131-
evictionState:
132-
default: Pending
133-
enum:
134-
- Pending
135-
- Running
136-
- Failed
137-
- Succeeded
138-
type: string
139131
hypervisorServiceId:
140132
type: string
141133
outstandingInstances:
@@ -146,8 +138,6 @@ spec:
146138
default: 0
147139
format: int64
148140
type: integer
149-
required:
150-
- evictionState
151141
type: object
152142
type: object
153143
served: true

config/crd/bases/kvm.cloud.sap_evictions.yaml

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ spec:
2323
- jsonPath: .spec.reason
2424
name: Reason
2525
type: string
26-
- jsonPath: .status.evictionState
26+
- jsonPath: .status.conditions[?(@.type=="Eviction")].reason
2727
name: State
2828
type: string
2929
- jsonPath: .metadata.creationTimestamp
@@ -129,14 +129,6 @@ spec:
129129
- type
130130
type: object
131131
type: array
132-
evictionState:
133-
default: Pending
134-
enum:
135-
- Pending
136-
- Running
137-
- Failed
138-
- Succeeded
139-
type: string
140132
hypervisorServiceId:
141133
type: string
142134
outstandingInstances:
@@ -147,8 +139,6 @@ spec:
147139
default: 0
148140
format: int64
149141
type: integer
150-
required:
151-
- evictionState
152142
type: object
153143
type: object
154144
served: true

internal/controller/decomission_controller_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package controller
2020
import (
2121
"context"
2222

23+
"github.com/gophercloud/gophercloud/v2/testhelper"
2324
. "github.com/onsi/ginkgo/v2"
2425
. "github.com/onsi/gomega"
2526
corev1 "k8s.io/api/core/v1"
@@ -77,7 +78,10 @@ var _ = Describe("Decommission Controller", func() {
7778
})
7879

7980
It("should successfully reconcile the resource", func() {
80-
By("Reconciling the created resource")
81+
By("ConditionType the created resource")
82+
testhelper.SetupHTTP()
83+
defer testhelper.TeardownHTTP()
84+
8185
_, err := reconcileNodeLoop(1)
8286
Expect(err).NotTo(HaveOccurred())
8387
})

internal/controller/eviction_controller.go

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ type EvictionReconciler struct {
5454

5555
const (
5656
evictionFinalizerName = "eviction-controller.cloud.sap/finalizer"
57-
Succeeded = "Succeeded"
58-
Running = "Running"
59-
Reconciling = "Reconciling"
60-
Reconciled = "Reconciled"
61-
Failed = "Failed"
6257
)
6358

6459
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=evictions,verbs=get;list;watch;create;update;patch;delete
@@ -75,31 +70,37 @@ func (r *EvictionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
7570
return ctrl.Result{}, client.IgnoreNotFound(err)
7671
}
7772

78-
log := logger.FromContext(ctx).WithName("Eviction").WithValues("hypervisor", eviction.Spec.Hypervisor, "status", eviction.Status.EvictionState)
73+
log := logger.FromContext(ctx).
74+
WithName("Eviction").
75+
WithValues("hypervisor", eviction.Spec.Hypervisor)
7976
ctx = logger.IntoContext(ctx, log)
77+
8078
// Being deleted
8179
if !eviction.DeletionTimestamp.IsZero() {
8280
err := r.handleFinalizer(ctx, eviction)
8381
log.Info("deleted")
8482
return ctrl.Result{}, err
8583
}
8684

87-
switch eviction.Status.EvictionState {
88-
case Succeeded, Failed:
89-
// Nothing left to be done
90-
log.Info("finished")
91-
return ctrl.Result{}, nil
92-
case "Pending", "":
93-
// Let's see if we can take it up
94-
eviction.Status.EvictionState = "Pending" // "" -> "Pending: Fixes the test
95-
log.Info("setup")
85+
statusCondition := meta.FindStatusCondition(eviction.Status.Conditions, kvmv1.ConditionTypeEviction)
86+
if statusCondition == nil {
87+
// No condition, so we are in the initial state
9688
return r.handlePending(ctx, eviction)
97-
case "Running":
89+
} else if statusCondition.Status == metav1.ConditionTrue {
90+
// We are running, so we need to evict the next instance
9891
return r.handleRunning(ctx, eviction)
99-
default:
100-
log.Info("Unknown eviction-state", "EvictionState", eviction.Status.EvictionState)
92+
} else if statusCondition.Status == metav1.ConditionFalse {
93+
// We are done, so we can just return
94+
log.Info("finished")
10195
return ctrl.Result{}, nil
96+
} else {
97+
log.
98+
WithValues("reason", statusCondition.Reason).
99+
WithValues("msg", statusCondition.Message).
100+
Info("unknown status condition")
102101
}
102+
103+
return ctrl.Result{}, nil
103104
}
104105

105106
func (r *EvictionReconciler) handleRunning(ctx context.Context, eviction *kvmv1.Eviction) (reconcile.Result, error) {
@@ -109,14 +110,13 @@ func (r *EvictionReconciler) handleRunning(ctx context.Context, eviction *kvmv1.
109110
}
110111

111112
meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{
112-
Type: Reconciling,
113-
Status: metav1.ConditionTrue,
114-
Message: Reconciled,
115-
Reason: Reconciled,
113+
Type: kvmv1.ConditionTypeEviction,
114+
Status: metav1.ConditionFalse,
115+
Message: "eviction completed successfully",
116+
Reason: kvmv1.ConditionReasonSuceeded,
116117
})
117118

118119
eviction.Status.OutstandingRamMb = 0
119-
eviction.Status.EvictionState = Succeeded
120120
logger.FromContext(ctx).Info("succeeded")
121121
return ctrl.Result{}, r.Status().Update(ctx, eviction)
122122
}
@@ -160,9 +160,15 @@ func (r *EvictionReconciler) handlePending(ctx context.Context, eviction *kvmv1.
160160
} else {
161161
// That is (likely) an eviction for a node that never registered
162162
// so we are good to go
163+
msg := "eviction completed successfully due to expected case of no hypervisor"
164+
meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{
165+
Type: kvmv1.ConditionTypeEviction,
166+
Status: metav1.ConditionFalse,
167+
Message: msg,
168+
Reason: kvmv1.ConditionReasonSuceeded,
169+
})
163170
eviction.Status.OutstandingRamMb = 0
164-
eviction.Status.EvictionState = Succeeded
165-
logger.FromContext(ctx).Info("succeeded due to expected case of no hypervisor")
171+
logger.FromContext(ctx).Info(msg)
166172
return ctrl.Result{}, r.Status().Update(ctx, eviction)
167173
}
168174
}
@@ -172,17 +178,15 @@ func (r *EvictionReconciler) handlePending(ctx context.Context, eviction *kvmv1.
172178
if currentHypervisor != hypervisorName {
173179
err = fmt.Errorf("hypervisor name %q does not match spec %q", currentHypervisor, hypervisorName)
174180
log.Error(err, "Hypervisor name mismatch")
175-
if eviction.Status.EvictionState != Failed ||
176-
meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{
177-
Type: "Eviction",
178-
Status: metav1.ConditionFalse,
179-
Message: err.Error(),
180-
Reason: Failed}) {
181-
eviction.Status.EvictionState = Failed
182-
log.Info("Update", "status", eviction.Status)
183-
if err := r.Status().Update(ctx, eviction); err != nil {
184-
return ctrl.Result{}, err
185-
}
181+
meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{
182+
Type: kvmv1.ConditionTypeEviction,
183+
Status: metav1.ConditionFalse,
184+
Message: err.Error(),
185+
Reason: kvmv1.ConditionReasonFailed,
186+
})
187+
log.Info("Update", "status", eviction.Status)
188+
if err = r.Status().Update(ctx, eviction); err != nil {
189+
return ctrl.Result{}, err
186190
}
187191

188192
return ctrl.Result{}, nil
@@ -212,13 +216,12 @@ func (r *EvictionReconciler) handlePending(ctx context.Context, eviction *kvmv1.
212216

213217
// Update status
214218
eviction.Status.HypervisorServiceId = hypervisor.ID
215-
eviction.Status.EvictionState = Running
216219
eviction.Status.OutstandingRamMb = hypervisor.MemoryMbUsed
217220
meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{
218-
Type: Reconciling,
221+
Type: kvmv1.ConditionTypeEviction,
219222
Status: metav1.ConditionTrue,
220-
Message: Reconciling,
221-
Reason: Reconciling,
223+
Message: "eviction started",
224+
Reason: kvmv1.ConditionReasonRunning,
222225
})
223226

224227
return ctrl.Result{}, r.Status().Update(ctx, eviction)
@@ -375,7 +378,7 @@ func (r *EvictionReconciler) disableHypervisor(ctx context.Context, hypervisor *
375378

376379
if disabledReason != nil && disabledReason != "" && disabledReason != evictionReason {
377380
// Disabled for another reason already
378-
return r.addProgressCondition(ctx, eviction, "Found host already disabled", "Update"), nil
381+
return r.addProgressCondition(ctx, eviction, "Found host already disabled", kvmv1.ConditionReasonRunning), nil
379382
}
380383

381384
if !controllerutil.ContainsFinalizer(eviction, evictionFinalizerName) {
@@ -396,7 +399,7 @@ func (r *EvictionReconciler) disableHypervisor(ctx context.Context, hypervisor *
396399
return r.addErrorCondition(ctx, eviction, err), err
397400
}
398401

399-
if r.addProgressCondition(ctx, eviction, "Host disabled", "Update") {
402+
if r.addProgressCondition(ctx, eviction, "Host disabled", kvmv1.ConditionReasonRunning) {
400403
return true, nil
401404
}
402405

@@ -438,7 +441,7 @@ func (r *EvictionReconciler) coldMigrate(ctx context.Context, uuid string, evict
438441
// addCondition adds a condition to the Eviction status and updates the status
439442
func (r *EvictionReconciler) addCondition(ctx context.Context, eviction *kvmv1.Eviction, status metav1.ConditionStatus, message string, reason string) bool {
440443
if !meta.SetStatusCondition(&eviction.Status.Conditions, metav1.Condition{
441-
Type: "Eviction",
444+
Type: kvmv1.ConditionTypeEviction,
442445
Status: status,
443446
Message: message,
444447
Reason: reason,
@@ -460,15 +463,15 @@ func (r *EvictionReconciler) addProgressCondition(ctx context.Context, eviction
460463
}
461464

462465
func (r *EvictionReconciler) addErrorCondition(ctx context.Context, eviction *kvmv1.Eviction, err error) bool {
463-
return r.addCondition(ctx, eviction, metav1.ConditionFalse, err.Error(), Failed)
466+
return r.addCondition(ctx, eviction, metav1.ConditionFalse, err.Error(), kvmv1.ConditionReasonFailed)
464467
}
465468

466469
// SetupWithManager sets up the controller with the Manager.
467470
func (r *EvictionReconciler) SetupWithManager(mgr ctrl.Manager) error {
468471
ctx := context.Background()
469472

470473
var err error
471-
if r.computeClient, err = openstack.GetServiceClient(ctx, "compute"); err != nil {
474+
if r.computeClient, err = openstack.GetServiceClient(ctx, "compute", nil); err != nil {
472475
return err
473476
}
474477
r.computeClient.Microversion = "2.90" // Xena (or later)

0 commit comments

Comments
 (0)