• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / hyperconverged-cluster-operator / 23590193690

26 Mar 2026 10:45AM UTC coverage: 80.034% (+0.03%) from 80.0%
23590193690

Pull #4129

github

web-flow
Merge bc9898669 into f28e0ae02
Pull Request #4129: Fix a flaky tests

6 of 13 new or added lines in 1 file covered. (46.15%)

80 existing lines in 6 files now uncovered.

10390 of 12982 relevant lines covered (80.03%)

2.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.23
/controllers/operandhandler/operandHandler.go
1
package operandhandler
2

3
import (
4
        "context"
5
        "fmt"
6
        "io/fs"
7
        "time"
8

9
        "golang.org/x/sync/errgroup"
10
        corev1 "k8s.io/api/core/v1"
11
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12
        "k8s.io/apimachinery/pkg/runtime"
13
        "sigs.k8s.io/controller-runtime/pkg/client"
14
        logf "sigs.k8s.io/controller-runtime/pkg/log"
15

16
        hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
17
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/common"
18
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/handlers"
19
        aiewebhook "github.com/kubevirt/hyperconverged-cluster-operator/controllers/handlers/aie-webhook"
20
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/handlers/passt"
21
        waspagent "github.com/kubevirt/hyperconverged-cluster-operator/controllers/handlers/wasp-agent"
22
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/operands"
23
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/monitoring/hyperconverged/metrics"
24
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
25
)
26

27
const (
28
        reconcileFailed       = "ReconcileFailed"
29
        ErrCDIUninstall       = "ErrCDIUninstall"
30
        uninstallCDIErrorMsg  = "The uninstall request failed on CDI component: "
31
        ErrVirtUninstall      = "ErrVirtUninstall"
32
        uninstallVirtErrorMsg = "The uninstall request failed on virt component: "
33
        ErrHCOUninstall       = "ErrHCOUninstall"
34
        uninstallHCOErrorMsg  = "The uninstall request failed on dependent components, please check their logs."
35
        deleteTimeOut         = 30 * time.Second
36
)
37

38
var (
39
        logger = logf.Log.WithName("operandHandlerInit")
40
)
41

42
type OperandHandler struct {
43
        client   client.Client
44
        operands []operands.Operand
45
        // save for deletions
46
        objects      []client.Object
47
        eventEmitter hcoutil.EventEmitter
48
}
49

50
func NewOperandHandler(client client.Client, scheme *runtime.Scheme, ci hcoutil.ClusterInfo, eventEmitter hcoutil.EventEmitter) *OperandHandler {
2✔
51
        operandList := []operands.Operand{
2✔
52
                handlers.NewKvPriorityClassHandler(client, scheme),
2✔
53
                handlers.NewKubevirtHandler(client, scheme),
2✔
54
                handlers.NewCdiHandler(client, scheme),
2✔
55
                handlers.NewCnaHandler(client, scheme),
2✔
56
                handlers.NewAAQHandler(client, scheme),
2✔
57
                handlers.NewMigControllerHandler(client, scheme),
2✔
58
                passt.NewPasstDaemonSetHandler(client, scheme),
2✔
59
                passt.NewPasstNetworkAttachmentDefinitionHandler(client, scheme),
2✔
60
        }
2✔
61

2✔
62
        if ci.IsOpenshift() {
4✔
63
                operandList = append(operandList, []operands.Operand{
2✔
64
                        handlers.NewSspHandler(client, scheme),
2✔
65
                        handlers.NewCliDownloadHandler(client, scheme),
2✔
66
                        handlers.NewCliDownloadsRouteHandler(client, scheme),
2✔
67
                        operands.NewServiceHandler(client, scheme, handlers.NewCliDownloadsService),
2✔
68
                        passt.NewPasstServiceAccountHandler(client, scheme),
2✔
69
                        passt.NewPasstSecurityContextConstraintsHandler(client, scheme),
2✔
70
                        waspagent.NewWaspAgentServiceAccountHandler(client, scheme),
2✔
71
                        waspagent.NewWaspAgentSCCHandler(client, scheme),
2✔
72
                        waspagent.NewWaspAgentDaemonSetHandler(client, scheme),
2✔
73
                }...)
2✔
74
        }
2✔
75

76
        operandList = append(operandList, []operands.Operand{
2✔
77
                aiewebhook.NewAIEWebhookServiceAccountHandler(client, scheme),
2✔
78
                aiewebhook.NewAIEWebhookServiceHandler(client, scheme),
2✔
79
        }...)
2✔
80

2✔
81
        if ci.IsOpenshift() && ci.IsConsolePluginImageProvided() {
4✔
82
                operandList = append(operandList, handlers.NewConsoleHandler(client))
2✔
83
                operandList = append(operandList, operands.NewServiceHandler(client, scheme, handlers.NewKvUIPluginSvc))
2✔
84
                operandList = append(operandList, operands.NewServiceHandler(client, scheme, handlers.NewKvUIProxySvc))
2✔
85
        }
2✔
86

87
        if ci.IsManagedByOLM() {
4✔
88
                operandList = append(operandList, handlers.NewCsvHandler(client))
2✔
89
        }
2✔
90

91
        return &OperandHandler{
2✔
92
                client:       client,
2✔
93
                operands:     operandList,
2✔
94
                eventEmitter: eventEmitter,
2✔
95
        }
2✔
96
}
97

98
// FirstUseInitiation is a lazy init function
99
// The k8s client is not available when calling to NewOperandHandler.
100
// Initial operations that need to read/write from the cluster can only be done when the client is already working.
101
func (h *OperandHandler) FirstUseInitiation(scheme *runtime.Scheme, ci hcoutil.ClusterInfo, hc *hcov1beta1.HyperConverged, pwdFS fs.FS) {
2✔
102
        h.objects = make([]client.Object, 0)
2✔
103

2✔
104
        for _, fn := range []operands.GetHandler{
2✔
105
                aiewebhook.NewAIEWebhookConfigMapHandler,
2✔
106
                aiewebhook.NewAIEWebhookClusterRoleHandler,
2✔
107
                aiewebhook.NewAIEWebhookClusterRoleBindingHandler,
2✔
108
                aiewebhook.NewAIEWebhookDeploymentHandler,
2✔
109
                aiewebhook.NewAIEWebhookMutatingWebhookConfigurationHandler,
2✔
110
        } {
4✔
111
                h.addOperand(scheme, hc, fn)
2✔
112
        }
2✔
113

114
        if !ci.IsOpenshift() {
3✔
115
                return
1✔
116
        }
1✔
117

118
        for _, fn := range []operands.GetHandlers{
2✔
119
                handlers.GetQuickStartHandlers,
2✔
120
                handlers.GetDashboardHandlers,
2✔
121
                handlers.GetImageStreamHandlers,
2✔
122
        } {
4✔
123
                h.addOperands(scheme, hc, fn, pwdFS)
2✔
124
        }
2✔
125

126
        getHandlerFuncs := []operands.GetHandler{
2✔
127
                handlers.NewVirtioWinCmHandler,
2✔
128
                handlers.NewVirtioWinCmReaderRoleHandler,
2✔
129
                handlers.NewVirtioWinCmReaderRoleBindingHandler,
2✔
130
                waspagent.NewWaspAgentClusterRoleHandler,
2✔
131
                waspagent.NewWaspAgentClusterRoleBindingHandler,
2✔
132
        }
2✔
133

2✔
134
        if ci.IsConsolePluginImageProvided() {
4✔
135
                getHandlerFuncs = append(getHandlerFuncs,
2✔
136
                        handlers.NewKvUIPluginSAHandler,
2✔
137
                        handlers.NewKvUIProxySAHandler,
2✔
138
                        handlers.NewKvUIPluginDeploymentHandler,
2✔
139
                        handlers.NewKvUIProxyDeploymentHandler,
2✔
140
                        handlers.NewKvUINginxCMHandler,
2✔
141
                        handlers.NewKvUIPluginCRHandler,
2✔
142
                        handlers.NewKvUIUserSettingsCMHandler,
2✔
143
                        handlers.NewKvUIFeaturesCMHandler,
2✔
144
                        handlers.NewKvUIConfigReaderRoleHandler,
2✔
145
                        handlers.NewKvUIConfigReaderRoleBindingHandler,
2✔
146
                        handlers.NewKVConsolePluginNetworkPolicyHandler,
2✔
147
                        handlers.NewKVAPIServerProxyNetworkPolicyHandler,
2✔
148
                )
2✔
149
        }
2✔
150

151
        for _, fn := range getHandlerFuncs {
4✔
152
                h.addOperand(scheme, hc, fn)
2✔
153
        }
2✔
154
}
155

156
func (h *OperandHandler) GetQuickStartNames() []string {
1✔
157
        return handlers.GetQuickStartNames()
1✔
158
}
1✔
159

160
func (h *OperandHandler) GetImageStreamNames() []string {
1✔
161
        return handlers.GetImageStreamNames()
1✔
162
}
1✔
163

164
func (h *OperandHandler) addOperandObject(handler operands.Operand, hc *hcov1beta1.HyperConverged) {
2✔
165
        var (
2✔
166
                obj client.Object
2✔
167
                err error
2✔
168
        )
2✔
169

2✔
170
        if gh, ok := handler.(operands.CRGetter); ok {
4✔
171
                obj, err = gh.GetFullCr(hc)
2✔
172
        } else {
2✔
UNCOV
173
                err = fmt.Errorf("unknown handler with type %T", handler)
×
UNCOV
174
        }
×
175

176
        if err != nil {
2✔
UNCOV
177
                logger.Error(err, "can't create object")
×
178
        } else {
2✔
179
                h.objects = append(h.objects, obj)
2✔
180
        }
2✔
181
}
182

183
func (h *OperandHandler) addOperands(scheme *runtime.Scheme, hc *hcov1beta1.HyperConverged, getHandlers operands.GetHandlers, dir fs.FS) {
2✔
184
        handlers, err := getHandlers(logger, h.client, scheme, hc, dir)
2✔
185
        if err != nil {
2✔
UNCOV
186
                logger.Error(err, "can't create handler")
×
187
        } else if len(handlers) > 0 {
3✔
188
                for _, handler := range handlers {
2✔
189
                        h.addOperandObject(handler, hc)
1✔
190
                }
1✔
191
                h.operands = append(h.operands, handlers...)
1✔
192
        }
193
}
194

195
func (h *OperandHandler) addOperand(scheme *runtime.Scheme, hc *hcov1beta1.HyperConverged, getHandler operands.GetHandler) {
2✔
196
        handler, err := getHandler(logger, h.client, scheme, hc)
2✔
197
        if err != nil {
3✔
198
                logger.Error(err, "can't create handler")
1✔
199
                return
1✔
200
        }
1✔
201

202
        h.addOperandObject(handler, hc)
2✔
203

2✔
204
        h.operands = append(h.operands, handler)
2✔
205
}
206

207
func (h *OperandHandler) Ensure(req *common.HcoRequest) error {
2✔
208
        for _, handler := range h.operands {
4✔
209
                res := handler.Ensure(req)
2✔
210
                if res.Err != nil {
4✔
211
                        req.Logger.Error(res.Err, "failed to Ensure an operand")
2✔
212

2✔
213
                        req.ComponentUpgradeInProgress = false
2✔
214
                        req.Conditions.SetStatusCondition(metav1.Condition{
2✔
215
                                Type:               hcov1beta1.ConditionReconcileComplete,
2✔
216
                                Status:             metav1.ConditionFalse,
2✔
217
                                Reason:             reconcileFailed,
2✔
218
                                Message:            fmt.Sprintf("Error while reconciling: %v", res.Err),
2✔
219
                                ObservedGeneration: req.Instance.Generation,
2✔
220
                        })
2✔
221
                        return res.Err
2✔
222
                }
2✔
223

224
                if res.Created {
4✔
225
                        h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "Created", fmt.Sprintf("Created %s %s", res.Type, res.Name))
2✔
226
                } else if res.Updated {
5✔
227
                        h.handleUpdatedOperand(req, res)
1✔
228
                } else if res.Deleted {
4✔
229
                        h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "Killing", fmt.Sprintf("Removed %s %s", res.Type, res.Name))
1✔
230
                }
1✔
231

232
                req.ComponentUpgradeInProgress = req.ComponentUpgradeInProgress && res.UpgradeDone
2✔
233
        }
234
        return nil
2✔
235

236
}
237

238
func (h *OperandHandler) handleUpdatedOperand(req *common.HcoRequest, res *operands.EnsureResult) {
1✔
239
        if !res.Overwritten {
2✔
240
                h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "Updated", fmt.Sprintf("Updated %s %s", res.Type, res.Name))
1✔
241
        } else {
2✔
242
                h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeWarning, "Overwritten", fmt.Sprintf("Overwritten %s %s", res.Type, res.Name))
1✔
243
                if !req.UpgradeMode {
2✔
244
                        metrics.IncOverwrittenModifications(res.Type, res.Name)
1✔
245
                }
1✔
246
        }
247
}
248

249
func (h *OperandHandler) EnsureDeleted(req *common.HcoRequest) error {
2✔
250

2✔
251
        tCtx, cancel := context.WithTimeout(req.Ctx, deleteTimeOut)
2✔
252
        defer cancel()
2✔
253

2✔
254
        resources := []client.Object{
2✔
255
                handlers.NewNetworkAddonsWithNameOnly(req.Instance),
2✔
256
                handlers.NewSSPWithNameOnly(req.Instance),
2✔
257
                handlers.NewConsoleCLIDownload(req.Instance),
2✔
258
                handlers.NewAAQWithNameOnly(req.Instance),
2✔
259
                handlers.NewMigControllerWithNameOnly(req.Instance),
2✔
260
                passt.NewPasstBindingCNINetworkAttachmentDefinition(req.Instance),
2✔
261
                passt.NewPasstBindingCNISecurityContextConstraints(req.Instance),
2✔
262
                waspagent.NewWaspAgentSCCWithNameOnly(req.Instance),
2✔
263
                aiewebhook.NewAIEWebhookClusterRoleWithNameOnly(req.Instance),
2✔
264
                aiewebhook.NewAIEWebhookClusterRoleBindingWithNameOnly(req.Instance),
2✔
265
                aiewebhook.NewAIEWebhookMutatingWebhookConfigurationWithNameOnly(req.Instance),
2✔
266
        }
2✔
267

2✔
268
        resources = append(resources, h.objects...)
2✔
269

2✔
270
        err := h.deleteMultipleResources(tCtx, req, resources)
2✔
271
        if err != nil {
3✔
272
                return err
1✔
273
        }
1✔
274

275
        err = h.deleteSingleResource(tCtx, req, handlers.NewKubeVirtWithNameOnly(req.Instance), ErrVirtUninstall, uninstallVirtErrorMsg)
2✔
276
        if err != nil {
3✔
277
                return err
1✔
278
        }
1✔
279

280
        return h.deleteSingleResource(tCtx, req, handlers.NewCDIWithNameOnly(req.Instance), ErrCDIUninstall, uninstallCDIErrorMsg)
2✔
281
}
282

283
func (h *OperandHandler) deleteMultipleResources(tCtx context.Context, req *common.HcoRequest, resources []client.Object) error {
2✔
284
        eg, egCtx := errgroup.WithContext(tCtx)
2✔
285

2✔
286
        for _, res := range resources {
4✔
287
                func(o client.Object) {
4✔
288
                        eg.Go(func() error {
4✔
289
                                deleted, err := hcoutil.EnsureDeleted(egCtx, h.client, o, req.Instance.Name, req.Logger, false, true, true)
2✔
290
                                if err != nil {
3✔
291
                                        req.Logger.Error(err, "Failed to manually delete objects")
1✔
292
                                        h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeWarning, ErrHCOUninstall, uninstallHCOErrorMsg)
1✔
293
                                        return err
1✔
294
                                } else if deleted {
5✔
295
                                        h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "Killing", fmt.Sprintf("Removed %s %s", o.GetObjectKind().GroupVersionKind().Kind, o.GetName()))
2✔
296
                                }
2✔
297
                                return nil
2✔
298
                        })
299
                }(res)
300
        }
301

302
        return eg.Wait()
2✔
303
}
304

305
func (h *OperandHandler) deleteSingleResource(ctx context.Context, req *common.HcoRequest, resource client.Object, errT, errMsg string) error {
2✔
306
        deleted, err := hcoutil.EnsureDeleted(ctx, h.client, resource, req.Instance.Name, req.Logger, false, true, true)
2✔
307
        if err != nil {
3✔
308
                req.Logger.Error(err, "Failed to manually delete objects")
1✔
309

1✔
310
                h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeWarning, errT, errMsg+err.Error())
1✔
311
                return err
1✔
312
        } else if deleted {
5✔
313
                h.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "Killing", fmt.Sprintf("Removed %s %s", resource.GetObjectKind().GroupVersionKind().Kind, resource.GetName()))
2✔
314
        }
2✔
315
        return nil
2✔
316
}
317

318
func (h *OperandHandler) Reset() {
1✔
319
        for _, op := range h.operands {
2✔
320
                op.Reset()
1✔
321
        }
1✔
322
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc