-
Notifications
You must be signed in to change notification settings - Fork 243
Expand file tree
/
Copy pathactuator.go
More file actions
221 lines (198 loc) · 8.12 KB
/
actuator.go
File metadata and controls
221 lines (198 loc) · 8.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package vsphere
// This is a thin layer to implement the machine actuator interface with cloud provider details.
// The lifetime of scope and reconciler is a machine actuator operation.
import (
"context"
"fmt"
"time"
"k8s.io/component-base/featuregate"
machinev1 "github.com/openshift/api/machine/v1beta1"
machinecontroller "github.com/openshift/machine-api-operator/pkg/controller/machine"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
)
const (
scopeFailFmt = "%s: failed to create scope for machine: %v"
reconcilerFailFmt = "%s: reconciler failed to %s machine: %w"
createEventAction = "Create"
updateEventAction = "Update"
deleteEventAction = "Delete"
noEventAction = ""
requeueAfterSeconds = 20
)
// Actuator is responsible for performing machine reconciliation.
type Actuator struct {
client runtimeclient.Client
apiReader runtimeclient.Reader
eventRecorder record.EventRecorder
TaskIDCache map[string]string
FailedProvStatusUpdate map[string]*machinev1.VSphereMachineProviderStatus
FeatureGates featuregate.MutableFeatureGate
openshiftConfigNamespace string
}
// ActuatorParams holds parameter information for Actuator.
type ActuatorParams struct {
Client runtimeclient.Client
APIReader runtimeclient.Reader
EventRecorder record.EventRecorder
TaskIDCache map[string]string
FailedProvStatusUpdate map[string]*machinev1.VSphereMachineProviderStatus
FeatureGates featuregate.MutableFeatureGate
OpenshiftConfigNamespace string
}
// NewActuator returns an actuator.
func NewActuator(params ActuatorParams) *Actuator {
return &Actuator{
client: params.Client,
apiReader: params.APIReader,
eventRecorder: params.EventRecorder,
TaskIDCache: params.TaskIDCache,
FailedProvStatusUpdate: params.FailedProvStatusUpdate,
FeatureGates: params.FeatureGates,
openshiftConfigNamespace: params.OpenshiftConfigNamespace,
}
}
// Set corresponding event based on error. It also returns the original error
// for convenience, so callers can do "return handleMachineError(...)".
func (a *Actuator) handleMachineError(machine *machinev1.Machine, err error, eventAction string) error {
klog.Errorf("%q error: %v", machine.GetName(), err)
if eventAction != noEventAction {
a.eventRecorder.Eventf(machine, corev1.EventTypeWarning, "Failed"+eventAction, "%v", err)
}
return err
}
// Create creates a machine and is invoked by the machine controller.
func (a *Actuator) Create(ctx context.Context, machine *machinev1.Machine) error {
klog.Infof("%s: actuator creating machine", machine.GetName())
scope, err := newMachineScope(machineScopeParams{
Context: ctx,
client: a.client,
machine: machine,
apiReader: a.apiReader,
featureGates: a.FeatureGates,
openshiftConfigNameSpace: a.openshiftConfigNamespace,
})
if err != nil {
fmtErr := fmt.Errorf(scopeFailFmt, machine.GetName(), err)
return a.handleMachineError(machine, fmtErr, createEventAction)
}
// Ensure we're not reconciling a stale machine by checking our task-id.
// This is a workaround for a cache race condition.
if val, ok := a.TaskIDCache[machine.Name]; ok {
if val != scope.providerStatus.TaskRef {
if a.FailedProvStatusUpdate[machine.Name] != nil {
// Attempt to update previous status
klog.Infof("Attempting to re-patch machine %s", machine.Name)
scope.providerStatus = a.FailedProvStatusUpdate[machine.Name]
if err := scope.PatchMachine(); err != nil {
// Still not having any luck. Return the error and retry later.
return err
}
// Update worked. Clear out the failed patch info.
delete(a.FailedProvStatusUpdate, machine.Name)
} else {
klog.Errorf("%s: machine object missing expected provider task ID, requeue", machine.GetName())
return &machinecontroller.RequeueAfterError{RequeueAfter: requeueAfterSeconds * time.Second}
}
}
}
var retErr error
err = newReconciler(scope).create()
// save the taskRef in our cache in case of any error with patch.
if scope.providerStatus.TaskRef != "" {
a.TaskIDCache[machine.Name] = scope.providerStatus.TaskRef
}
if err != nil {
fmtErr := fmt.Errorf(reconcilerFailFmt, machine.GetName(), createEventAction, err)
retErr = a.handleMachineError(machine, fmtErr, createEventAction)
} else {
a.eventRecorder.Eventf(machine, corev1.EventTypeNormal, createEventAction, "Created Machine %v", machine.GetName())
}
if err := scope.PatchMachine(); err != nil {
// An error occurred while saving status fields. Save off and try again later.
a.FailedProvStatusUpdate[scope.machine.Name] = scope.providerStatus
return err
}
return retErr
}
func (a *Actuator) Exists(ctx context.Context, machine *machinev1.Machine) (bool, error) {
klog.Infof("%s: actuator checking if machine exists", machine.GetName())
scope, err := newMachineScope(machineScopeParams{
Context: ctx,
client: a.client,
machine: machine,
apiReader: a.apiReader,
featureGates: a.FeatureGates,
openshiftConfigNameSpace: a.openshiftConfigNamespace,
})
if err != nil {
return false, fmt.Errorf(scopeFailFmt, machine.GetName(), err)
}
return newReconciler(scope).exists()
}
func (a *Actuator) Update(ctx context.Context, machine *machinev1.Machine) error {
klog.Infof("%s: actuator updating machine", machine.GetName())
// Cleanup TaskIDCache so we don't continually grow
delete(a.TaskIDCache, machine.Name)
delete(a.FailedProvStatusUpdate, machine.Name)
scope, err := newMachineScope(machineScopeParams{
Context: ctx,
client: a.client,
machine: machine,
apiReader: a.apiReader,
featureGates: a.FeatureGates,
openshiftConfigNameSpace: a.openshiftConfigNamespace,
})
if err != nil {
fmtErr := fmt.Errorf(scopeFailFmt, machine.GetName(), err)
return a.handleMachineError(machine, fmtErr, updateEventAction)
}
if err := newReconciler(scope).update(); err != nil {
// Update machine and machine status in case it was modified
if err := scope.PatchMachine(); err != nil {
return err
}
fmtErr := fmt.Errorf(reconcilerFailFmt, machine.GetName(), updateEventAction, err)
return a.handleMachineError(machine, fmtErr, updateEventAction)
}
previousResourceVersion := scope.machine.ResourceVersion
if err := scope.PatchMachine(); err != nil {
return err
}
currentResourceVersion := scope.machine.ResourceVersion
// Create event only if machine object was modified
if previousResourceVersion != currentResourceVersion {
a.eventRecorder.Eventf(machine, corev1.EventTypeNormal, updateEventAction, "Updated Machine %v", machine.GetName())
}
return nil
}
func (a *Actuator) Delete(ctx context.Context, machine *machinev1.Machine) error {
klog.Infof("%s: actuator deleting machine", machine.GetName())
// Cleanup TaskIDCache so we don't continually grow
// Cleanup here as well in case Update() was never successfully called.
delete(a.TaskIDCache, machine.Name)
delete(a.FailedProvStatusUpdate, machine.Name)
scope, err := newMachineScope(machineScopeParams{
Context: ctx,
client: a.client,
machine: machine,
apiReader: a.apiReader,
featureGates: a.FeatureGates,
openshiftConfigNameSpace: a.openshiftConfigNamespace,
})
if err != nil {
fmtErr := fmt.Errorf(scopeFailFmt, machine.GetName(), err)
return a.handleMachineError(machine, fmtErr, deleteEventAction)
}
if err := newReconciler(scope).delete(); err != nil {
if err := scope.PatchMachine(); err != nil {
return err
}
fmtErr := fmt.Errorf(reconcilerFailFmt, machine.GetName(), deleteEventAction, err)
return a.handleMachineError(machine, fmtErr, deleteEventAction)
}
a.eventRecorder.Eventf(machine, corev1.EventTypeNormal, deleteEventAction, "Deleted machine %v", machine.GetName())
return scope.PatchMachine()
}