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

kubevirt / containerized-data-importer / #5624

08 Oct 2025 08:13PM UTC coverage: 59.095% (+0.004%) from 59.091%
#5624

push

travis-ci

web-flow
DV Recreated with Wrong Status (#3912)

* Add check during updateStatus that prevents DVs from updating
its status from a PVC that is marked for deletion

Signed-off-by: dsanatar <dsanatar@redhat.com>

* add unit test to make sure PVCs marked for deletion do not
update the status of existing DVs

Signed-off-by: dsanatar <dsanatar@redhat.com>

---------

Signed-off-by: dsanatar <dsanatar@redhat.com>

1 of 1 new or added line in 1 file covered. (100.0%)

223 existing lines in 5 files now uncovered.

17245 of 29182 relevant lines covered (59.09%)

0.65 hits per line

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

36.22
/pkg/operator/controller/controller.go
1
/*
2
Copyright 2018 The CDI Authors.
3

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

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

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

17
package controller
18

19
import (
20
        "context"
21
        "fmt"
22
        "os"
23
        "strconv"
24
        "time"
25

26
        "github.com/kelseyhightower/envconfig"
27
        conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1"
28

29
        corev1 "k8s.io/api/core/v1"
30
        "k8s.io/apimachinery/pkg/api/errors"
31
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32
        "k8s.io/apimachinery/pkg/runtime"
33
        "k8s.io/client-go/tools/record"
34

35
        "sigs.k8s.io/controller-runtime/pkg/cache"
36
        "sigs.k8s.io/controller-runtime/pkg/client"
37
        "sigs.k8s.io/controller-runtime/pkg/controller"
38
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
39
        logf "sigs.k8s.io/controller-runtime/pkg/log"
40
        "sigs.k8s.io/controller-runtime/pkg/manager"
41
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
42

43
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
44
        metrics "kubevirt.io/containerized-data-importer/pkg/monitoring/metrics/operator-controller"
45
        "kubevirt.io/containerized-data-importer/pkg/monitoring/rules"
46
        "kubevirt.io/containerized-data-importer/pkg/operator"
47
        cdicerts "kubevirt.io/containerized-data-importer/pkg/operator/resources/cert"
48
        cdicluster "kubevirt.io/containerized-data-importer/pkg/operator/resources/cluster"
49
        "kubevirt.io/containerized-data-importer/pkg/operator/resources/generate/install"
50
        cdinamespaced "kubevirt.io/containerized-data-importer/pkg/operator/resources/namespaced"
51
        "kubevirt.io/containerized-data-importer/pkg/util"
52
        "kubevirt.io/controller-lifecycle-operator-sdk/pkg/sdk/callbacks"
53
        sdkr "kubevirt.io/controller-lifecycle-operator-sdk/pkg/sdk/reconciler"
54
)
55

56
const (
57
        finalizerName = "operator.cdi.kubevirt.io"
58

59
        createVersionLabel = "operator.cdi.kubevirt.io/createVersion"
60
        updateVersionLabel = "operator.cdi.kubevirt.io/updateVersion"
61
        // LastAppliedConfigAnnotation is the annotation that holds the last resource state which we put on resources under our governance
62
        LastAppliedConfigAnnotation = "operator.cdi.kubevirt.io/lastAppliedConfiguration"
63

64
        certPollInterval = 1 * time.Minute
65

66
        createResourceFailed  = "CreateResourceFailed"
67
        createResourceSuccess = "CreateResourceSuccess"
68

69
        deleteResourceFailed   = "DeleteResourceFailed"
70
        deleteResourceSuccess  = "DeleteResourceSuccess"
71
        dumpInstallStrategyKey = "DUMP_INSTALL_STRATEGY"
72

73
        annInjectUploadProxyCert = "operator.cdi.kubevirt.io/injectUploadProxyCert"
74
        updateUserRouteSuccess   = "UploadProxyRouteInjectSuccess"
75
)
76

77
var (
78
        log = logf.Log.WithName("cdi-operator")
79
)
80

81
func init() {
1✔
82
        // Setup metrics for our various controllers
1✔
83
        err := metrics.SetupMetrics()
1✔
84
        if err != nil {
1✔
85
                log.Info("Error setting up metrics: %v", err)
×
86
        }
×
87
        metrics.SetInit()
1✔
88
}
89

90
// Add creates a new CDI Controller and adds it to the Manager. The Manager will set fields on the Controller
91
// and Start it when the Manager is Started.
92
func Add(mgr manager.Manager) error {
×
93
        r, err := newReconciler(mgr)
×
94
        if err != nil {
×
95
                return err
×
96
        }
×
97
        return r.add(mgr)
×
98
}
99

100
// newReconciler returns a new reconcile.Reconciler
101
func newReconciler(mgr manager.Manager) (*ReconcileCDI, error) {
×
102
        var namespacedArgs cdinamespaced.FactoryArgs
×
103
        namespace := util.GetNamespace()
×
104
        restClient := mgr.GetClient()
×
105
        clusterArgs := &cdicluster.FactoryArgs{
×
106
                Namespace: namespace,
×
107
                Client:    restClient,
×
108
                Logger:    log,
×
109
        }
×
110
        dumpInstallStrategy := false
×
111
        if value, ok := os.LookupEnv(dumpInstallStrategyKey); ok {
×
112
                ret, err := strconv.ParseBool(value)
×
113
                if err != nil {
×
114
                        return nil, err
×
115
                }
×
116
                dumpInstallStrategy = ret
×
117
                log.Info("Dump Install Strategy", "VARS", ret)
×
118
        }
119

120
        err := envconfig.Process("", &namespacedArgs)
×
121
        if err != nil {
×
122
                return nil, err
×
123
        }
×
124

125
        namespacedArgs.Namespace = namespace
×
126

×
127
        log.Info("", "VARS", fmt.Sprintf("%+v", namespacedArgs))
×
128

×
129
        scheme := mgr.GetScheme()
×
130
        uncachedClient, err := client.New(mgr.GetConfig(), client.Options{
×
131
                Scheme: scheme,
×
132
                Mapper: mgr.GetRESTMapper(),
×
133
        })
×
134
        if err != nil {
×
135
                return nil, err
×
136
        }
×
137

138
        err = rules.SetupRules(namespace)
×
139
        if err != nil {
×
140
                return nil, err
×
141
        }
×
142

143
        recorder := mgr.GetEventRecorderFor("operator-controller")
×
144

×
145
        haveRoutes, err := haveRoutes(uncachedClient)
×
146
        if err != nil {
×
147
                return nil, err
×
148
        }
×
UNCOV
149
        haveVolumeDataSourceValidator, err := haveVolumeDataSourceValidator(uncachedClient)
×
150
        if err != nil {
×
151
                return nil, err
×
152
        }
×
153

154
        r := &ReconcileCDI{
×
155
                client:                        restClient,
×
156
                uncachedClient:                uncachedClient,
×
157
                scheme:                        scheme,
×
158
                getCache:                      mgr.GetCache,
×
159
                recorder:                      recorder,
×
160
                namespace:                     namespace,
×
161
                clusterArgs:                   clusterArgs,
×
162
                namespacedArgs:                &namespacedArgs,
×
163
                dumpInstallStrategy:           dumpInstallStrategy,
×
164
                haveRoutes:                    haveRoutes,
×
165
                haveVolumeDataSourceValidator: haveVolumeDataSourceValidator,
×
166
        }
×
167
        callbackDispatcher := callbacks.NewCallbackDispatcher(log, restClient, uncachedClient, scheme, namespace)
×
168
        r.reconciler = sdkr.NewReconciler(r, log, restClient, callbackDispatcher, scheme, mgr.GetCache, createVersionLabel, updateVersionLabel, LastAppliedConfigAnnotation, certPollInterval, finalizerName, false, recorder)
×
UNCOV
169

×
UNCOV
170
        r.registerHooks()
×
UNCOV
171
        addReconcileCallbacks(r)
×
UNCOV
172

×
UNCOV
173
        return r, nil
×
174
}
175

176
var _ reconcile.Reconciler = &ReconcileCDI{}
177

178
// ReconcileCDI reconciles a CDI object
179
type ReconcileCDI struct {
180
        // This Client, initialized using mgr.client() above, is a split Client
181
        // that reads objects from the cache and writes to the apiserver
182
        client client.Client
183

184
        // use this for getting any resources not in the install namespace or cluster scope
185
        uncachedClient client.Client
186
        scheme         *runtime.Scheme
187
        getCache       func() cache.Cache
188
        recorder       record.EventRecorder
189
        controller     controller.Controller
190

191
        namespace      string
192
        clusterArgs    *cdicluster.FactoryArgs
193
        namespacedArgs *cdinamespaced.FactoryArgs
194

195
        certManager                   CertManager
196
        reconciler                    *sdkr.Reconciler
197
        dumpInstallStrategy           bool
198
        haveRoutes                    bool
199
        haveVolumeDataSourceValidator bool
200
}
201

202
// SetController sets the controller dependency
UNCOV
203
func (r *ReconcileCDI) SetController(controller controller.Controller) {
×
UNCOV
204
        r.controller = controller
×
UNCOV
205
        r.reconciler.WithController(controller)
×
UNCOV
206
}
×
207

208
// Reconcile reads that state of the cluster for a CDI object and makes changes based on the state read
209
// and what is in the CDI.Spec
210
// Note:
211
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
212
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
213
func (r *ReconcileCDI) Reconcile(_ context.Context, request reconcile.Request) (reconcile.Result, error) {
1✔
214
        reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
1✔
215
        reqLogger.Info("Reconciling CDI CR")
1✔
216
        operatorVersion := r.namespacedArgs.OperatorVersion
1✔
217
        cr := &cdiv1.CDI{}
1✔
218
        crKey := client.ObjectKey{Namespace: "", Name: request.NamespacedName.Name}
1✔
219
        err := r.client.Get(context.TODO(), crKey, cr)
1✔
220
        if err != nil {
1✔
UNCOV
221
                if errors.IsNotFound(err) {
×
UNCOV
222
                        reqLogger.Info("CDI CR does not exist")
×
UNCOV
223
                        return reconcile.Result{}, nil
×
224
                }
×
225
                reqLogger.Error(err, "Failed to get CDI object")
×
226
                return reconcile.Result{}, err
×
227
        }
228

229
        if r.dumpInstallStrategy {
1✔
230
                reqLogger.Info("Dumping Install Strategy")
×
231
                objects, err := r.GetAllResources(cr)
×
232
                if err != nil {
×
233
                        reqLogger.Error(err, "Failed to get all CDI object")
×
234
                        return reconcile.Result{}, err
×
235
                }
×
236
                var runtimeObjects []runtime.Object
×
237
                for _, obj := range objects {
×
238
                        runtimeObjects = append(runtimeObjects, obj)
×
239
                }
×
UNCOV
240
                installerLabels := util.GetRecommendedInstallerLabelsFromCr(cr)
×
UNCOV
241
                err = install.DumpInstallStrategyToConfigMap(r.client, runtimeObjects, reqLogger, r.namespace, installerLabels)
×
UNCOV
242
                if err != nil {
×
UNCOV
243
                        reqLogger.Error(err, "Failed to dump CDI object in configmap")
×
UNCOV
244
                        return reconcile.Result{}, err
×
UNCOV
245
                }
×
246
        }
247

248
        // Ready metric so we can alert whenever we are not ready for a while
249
        if conditionsv1.IsStatusConditionTrue(cr.Status.Conditions, conditionsv1.ConditionAvailable) {
2✔
250
                metrics.SetReady()
1✔
251
        } else if !conditionsv1.IsStatusConditionTrue(cr.Status.Conditions, conditionsv1.ConditionProgressing) {
3✔
252
                // Not an issue if progress is still ongoing
1✔
253
                metrics.SetNotReady()
1✔
254
        }
1✔
255
        return r.reconciler.Reconcile(request, operatorVersion, reqLogger)
1✔
256
}
257

258
func (r *ReconcileCDI) add(mgr manager.Manager) error {
×
259
        // Create a new controller
×
260
        c, err := controller.New("cdi-operator-controller", mgr, controller.Options{
×
UNCOV
261
                MaxConcurrentReconciles: 3,
×
262
                Reconciler:              r,
×
263
        })
×
264
        if err != nil {
×
265
                return err
×
266
        }
×
267

268
        r.SetController(c)
×
269

×
270
        if err = r.reconciler.WatchCR(); err != nil {
×
271
                return err
×
UNCOV
272
        }
×
273

274
        cm, err := NewCertManager(mgr, r.namespace)
×
275
        if err != nil {
×
UNCOV
276
                return err
×
UNCOV
277
        }
×
278

UNCOV
279
        r.certManager = cm
×
UNCOV
280

×
UNCOV
281
        return nil
×
282
}
283

284
func (r *ReconcileCDI) getCertificateDefinitions(cdi *cdiv1.CDI) []cdicerts.CertificateDefinition {
1✔
285
        args := &cdicerts.FactoryArgs{Namespace: r.namespace}
1✔
286

1✔
287
        if cdi != nil && cdi.Spec.CertConfig != nil {
2✔
288
                if cdi.Spec.CertConfig.CA != nil {
2✔
289
                        if cdi.Spec.CertConfig.CA.Duration != nil {
2✔
290
                                args.SignerDuration = &cdi.Spec.CertConfig.CA.Duration.Duration
1✔
291
                        }
1✔
292

293
                        if cdi.Spec.CertConfig.CA.RenewBefore != nil {
2✔
294
                                args.SignerRenewBefore = &cdi.Spec.CertConfig.CA.RenewBefore.Duration
1✔
295
                        }
1✔
296
                }
297

298
                if cdi.Spec.CertConfig.Server != nil {
2✔
299
                        if cdi.Spec.CertConfig.Server.Duration != nil {
2✔
300
                                args.ServerDuration = &cdi.Spec.CertConfig.Server.Duration.Duration
1✔
301
                        }
1✔
302

303
                        if cdi.Spec.CertConfig.Server.RenewBefore != nil {
2✔
304
                                args.ServerRenewBefore = &cdi.Spec.CertConfig.Server.RenewBefore.Duration
1✔
305
                        }
1✔
306
                }
307

308
                if cdi.Spec.CertConfig.Client != nil {
2✔
309
                        if cdi.Spec.CertConfig.Client.Duration != nil {
2✔
310
                                args.ClientDuration = &cdi.Spec.CertConfig.Client.Duration.Duration
1✔
311
                        }
1✔
312

313
                        if cdi.Spec.CertConfig.Client.RenewBefore != nil {
2✔
314
                                args.ClientRenewBefore = &cdi.Spec.CertConfig.Client.RenewBefore.Duration
1✔
315
                        }
1✔
316
                }
317
        }
318

319
        return cdicerts.CreateCertificateDefinitions(args)
1✔
320
}
321

322
func (r *ReconcileCDI) getConfigMap() (*corev1.ConfigMap, error) {
1✔
323
        cm := &corev1.ConfigMap{}
1✔
324
        key := client.ObjectKey{Name: operator.ConfigMapName, Namespace: r.namespace}
1✔
325

1✔
326
        if err := r.client.Get(context.TODO(), key, cm); err != nil {
2✔
327
                if errors.IsNotFound(err) {
2✔
328
                        return nil, nil
1✔
329
                }
1✔
UNCOV
330
                return nil, err
×
331
        }
332

333
        return cm, nil
1✔
334
}
335

336
// createOperatorConfig creates operator config map
337
func (r *ReconcileCDI) createOperatorConfig(cr client.Object) error {
1✔
338
        cdiCR := cr.(*cdiv1.CDI)
1✔
339
        installerLabels := util.GetRecommendedInstallerLabelsFromCr(cdiCR)
1✔
340

1✔
341
        cm := &corev1.ConfigMap{
1✔
342
                ObjectMeta: metav1.ObjectMeta{
1✔
343
                        Name:      operator.ConfigMapName,
1✔
344
                        Namespace: r.namespace,
1✔
345
                        Labels:    map[string]string{"operator.cdi.kubevirt.io": ""},
1✔
346
                },
1✔
347
        }
1✔
348
        util.SetRecommendedLabels(cm, installerLabels, "cdi-operator")
1✔
349

1✔
350
        if err := controllerutil.SetControllerReference(cr, cm, r.scheme); err != nil {
1✔
UNCOV
351
                return err
×
UNCOV
352
        }
×
353

354
        return r.client.Create(context.TODO(), cm)
1✔
355
}
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

© 2025 Coveralls, Inc