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

kubevirt / kubevirt / 439f3882-b2a9-4f75-8aca-8b8715be636b

19 Jun 2025 03:58AM UTC coverage: 70.705% (-0.5%) from 71.186%
439f3882-b2a9-4f75-8aca-8b8715be636b

push

prow

web-flow
Merge pull request #14705 from jean-edouard/update_13792

Fix migration completion logic (along with other bugs)

1113 of 1994 new or added lines in 13 files covered. (55.82%)

251 existing lines in 10 files now uncovered.

66217 of 93652 relevant lines covered (70.71%)

0.79 hits per line

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

77.68
/pkg/controller/controller.go
1
/*
2
 * This file is part of the KubeVirt project
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
 * Copyright The KubeVirt Authors.
17
 *
18
 */
19

20
package controller
21

22
import (
23
        "context"
24
        "fmt"
25
        "runtime/debug"
26
        "strings"
27
        "time"
28

29
        k8sv1 "k8s.io/api/core/v1"
30
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31
        "k8s.io/apimachinery/pkg/fields"
32
        "k8s.io/apimachinery/pkg/labels"
33
        "k8s.io/apimachinery/pkg/runtime"
34
        "k8s.io/apimachinery/pkg/watch"
35
        "k8s.io/client-go/tools/cache"
36
        "k8s.io/client-go/util/workqueue"
37

38
        v1 "kubevirt.io/api/core/v1"
39
        "kubevirt.io/client-go/log"
40
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
41
)
42

43
const (
44
        // BurstReplicas is the maximum amount of requests in a row for CRUD operations on resources by controllers,
45
        // to avoid unintentional DoS
46
        BurstReplicas uint = 250
47
)
48

49
// Reasons for vmi events
50
const (
51
        // FailedCreatePodReason is added in an event and in a vmi controller condition
52
        // when a pod for a vmi controller failed to be created.
53
        FailedCreatePodReason = "FailedCreate"
54
        // SuccessfulCreatePodReason is added in an event when a pod for a vmi controller
55
        // is successfully created.
56
        SuccessfulCreatePodReason = "SuccessfulCreate"
57
        // FailedDeletePodReason is added in an event and in a vmi controller condition
58
        // when a pod for a vmi controller failed to be deleted.
59
        FailedDeletePodReason = "FailedDelete"
60
        // SuccessfulDeletePodReason is added in an event when a pod for a vmi controller
61
        // is successfully deleted.
62
        SuccessfulDeletePodReason = "SuccessfulDelete"
63
        // FailedHandOverPodReason is added in an event and in a vmi controller condition
64
        // when transferring the pod ownership from the controller to virt-hander fails.
65
        FailedHandOverPodReason = "FailedHandOver"
66
        // FailedBackendStorageCreateReason is added when the creation of the backend storage PVC fails.
67
        FailedBackendStorageCreateReason = "FailedBackendStorageCreate"
68
        // FailedBackendStorageProbeReason is added when probing the backend storage PVC fails.
69
        FailedBackendStorageProbeReason = "FailedBackendStorageProbe"
70
        // BackendStorageNotReadyReason is added when the backend storage PVC is pending.
71
        BackendStorageNotReadyReason = "BackendStorageNotReady"
72
        // SuccessfulHandOverPodReason is added in an event
73
        // when the pod ownership transfer from the controller to virt-hander succeeds.
74
        SuccessfulHandOverPodReason = "SuccessfulHandOver"
75
        // FailedDataVolumeImportReason is added in an event when a dynamically generated
76
        // dataVolume reaches the failed status phase.
77
        FailedDataVolumeImportReason = "FailedDataVolumeImport"
78
        // FailedGuaranteePodResourcesReason is added in an event and in a vmi controller condition
79
        // when a pod has been created without a Guaranteed resources.
80
        FailedGuaranteePodResourcesReason = "FailedGuaranteeResources"
81
        // FailedGatherhingClusterTopologyHints is added if the cluster topology hints can't be collected for a VMI by virt-controller
82
        FailedGatherhingClusterTopologyHints = "FailedGatherhingClusterTopologyHints"
83
        // FailedPvcNotFoundReason is added in an event
84
        // when a PVC for a volume was not found.
85
        FailedPvcNotFoundReason = "FailedPvcNotFound"
86
        // SuccessfulMigrationReason is added when a migration attempt completes successfully
87
        SuccessfulMigrationReason = "SuccessfulMigration"
88
        // FailedMigrationReason is added when a migration attempt fails
89
        FailedMigrationReason = "FailedMigration"
90
        // SuccessfulAbortMigrationReason is added when an attempt to abort migration completes successfully
91
        SuccessfulAbortMigrationReason = "SuccessfulAbortMigration"
92
        // MigrationTargetPodUnschedulable is added a migration target pod enters Unschedulable phase
93
        MigrationTargetPodUnschedulable = "migrationTargetPodUnschedulable"
94
        // FailedAbortMigrationReason is added when an attempt to abort migration fails
95
        FailedAbortMigrationReason = "FailedAbortMigration"
96
        // MissingAttachmentPodReason is set when we have a hotplugged volume, but the attachment pod is missing
97
        MissingAttachmentPodReason = "MissingAttachmentPod"
98
        // PVCNotReadyReason is set when the PVC is not ready to be hot plugged.
99
        PVCNotReadyReason = "PVCNotReady"
100
        // FailedHotplugSyncReason is set when a hotplug specific failure occurs during sync
101
        FailedHotplugSyncReason = "FailedHotplugSync"
102
        // ErrImagePullReason is set when an error has occured while pulling an image for a containerDisk VM volume.
103
        ErrImagePullReason = "ErrImagePull"
104
        // ImagePullBackOffReason is set when an error has occured while pulling an image for a containerDisk VM volume,
105
        // and that kubelet is backing off before retrying.
106
        ImagePullBackOffReason = "ImagePullBackOff"
107
        // NoSuitableNodesForHostModelMigration is set when a VMI with host-model CPU mode tries to migrate but no node
108
        // is suitable for migration (since CPU model / required features are not supported)
109
        NoSuitableNodesForHostModelMigration = "NoSuitableNodesForHostModelMigration"
110
        // FailedPodPatchReason is set when a pod patch error occurs during sync
111
        FailedPodPatchReason = "FailedPodPatch"
112
        // MigrationBackoffReason is set when an error has occured while migrating
113
        // and virt-controller is backing off before retrying.
114
        MigrationBackoffReason = "MigrationBackoff"
115
)
116

117
type PodCacheStore struct {
118
        indexer cache.Indexer
119
}
120

121
func NewPodCacheStore(indexer cache.Indexer) *PodCacheStore {
×
122
        return &PodCacheStore{indexer: indexer}
×
123
}
×
124

125
func (p *PodCacheStore) CurrentPod(vmi *v1.VirtualMachineInstance) (*k8sv1.Pod, error) {
×
126
        return CurrentVMIPod(vmi, p.indexer)
×
127
}
×
128

129
// NewListWatchFromClient creates a new ListWatch from the specified client, resource, kubevirtNamespace and field selector.
130
func NewListWatchFromClient(c cache.Getter, resource string, namespace string, fieldSelector fields.Selector, labelSelector labels.Selector) *cache.ListWatch {
×
131
        listFunc := func(options metav1.ListOptions) (runtime.Object, error) {
×
132
                options.FieldSelector = fieldSelector.String()
×
133
                options.LabelSelector = labelSelector.String()
×
134
                return c.Get().
×
135
                        Namespace(namespace).
×
136
                        Resource(resource).
×
137
                        VersionedParams(&options, metav1.ParameterCodec).
×
138
                        Do(context.Background()).
×
139
                        Get()
×
140
        }
×
141
        watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
×
142
                options.FieldSelector = fieldSelector.String()
×
143
                options.LabelSelector = labelSelector.String()
×
144
                options.Watch = true
×
145
                return c.Get().
×
146
                        Namespace(namespace).
×
147
                        Resource(resource).
×
148
                        VersionedParams(&options, metav1.ParameterCodec).
×
149
                        Watch(context.Background())
×
150
        }
×
151
        return &cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
×
152
}
153

154
func HandlePanic() {
1✔
155
        if r := recover(); r != nil {
2✔
156
                // Ignoring error - There is nothing to do, if logging fails
1✔
157
                _ = log.Log.Level(log.FATAL).Log("stacktrace", debug.Stack(), "msg", r)
1✔
158
        }
1✔
159
}
160

161
func NewResourceEventHandlerFuncsForWorkqueue(queue workqueue.RateLimitingInterface) cache.ResourceEventHandlerFuncs {
×
162
        return cache.ResourceEventHandlerFuncs{
×
163
                AddFunc: func(obj interface{}) {
×
164
                        key, err := KeyFunc(obj)
×
165
                        if err == nil {
×
166
                                queue.Add(key)
×
167
                        }
×
168
                },
169
                UpdateFunc: func(old interface{}, new interface{}) {
×
170
                        key, err := KeyFunc(new)
×
171
                        if err == nil {
×
172
                                queue.Add(key)
×
173
                        }
×
174
                },
175
                DeleteFunc: func(obj interface{}) {
×
176
                        key, err := KeyFunc(obj)
×
177
                        if err == nil {
×
178
                                queue.Add(key)
×
179
                        }
×
180
                },
181
        }
182
}
183

184
func MigrationKey(migration *v1.VirtualMachineInstanceMigration) string {
1✔
185
        return fmt.Sprintf("%v/%v", migration.ObjectMeta.Namespace, migration.ObjectMeta.Name)
1✔
186
}
1✔
187

188
func VirtualMachineInstanceKey(vmi *v1.VirtualMachineInstance) string {
1✔
189
        return fmt.Sprintf("%v/%v", vmi.ObjectMeta.Namespace, vmi.ObjectMeta.Name)
1✔
190
}
1✔
191

192
func VirtualMachineKey(vm *v1.VirtualMachine) string {
1✔
193
        return fmt.Sprintf("%v/%v", vm.ObjectMeta.Namespace, vm.ObjectMeta.Name)
1✔
194
}
1✔
195

196
func PodKey(pod *k8sv1.Pod) string {
1✔
197
        return fmt.Sprintf("%v/%v", pod.Namespace, pod.Name)
1✔
198
}
1✔
199

200
func DataVolumeKey(dataVolume *cdiv1.DataVolume) string {
×
201
        return fmt.Sprintf("%v/%v", dataVolume.Namespace, dataVolume.Name)
×
202
}
×
203

204
func VirtualMachineInstanceKeys(vmis []*v1.VirtualMachineInstance) []string {
1✔
205
        keys := []string{}
1✔
206
        for _, vmi := range vmis {
2✔
207
                keys = append(keys, VirtualMachineInstanceKey(vmi))
1✔
208
        }
1✔
209
        return keys
1✔
210
}
211

212
func VirtualMachineKeys(vms []*v1.VirtualMachine) []string {
1✔
213
        keys := []string{}
1✔
214
        for _, vm := range vms {
2✔
215
                keys = append(keys, VirtualMachineKey(vm))
1✔
216
        }
1✔
217
        return keys
1✔
218
}
219

220
func HasFinalizer(object metav1.Object, finalizer string) bool {
1✔
221
        for _, f := range object.GetFinalizers() {
2✔
222
                if f == finalizer {
2✔
223
                        return true
1✔
224
                }
1✔
225
        }
226
        return false
1✔
227
}
228

229
func RemoveFinalizer(object metav1.Object, finalizer string) {
1✔
230
        filtered := []string{}
1✔
231
        for _, f := range object.GetFinalizers() {
2✔
232
                if f != finalizer {
2✔
233
                        filtered = append(filtered, f)
1✔
234
                }
1✔
235
        }
236
        object.SetFinalizers(filtered)
1✔
237
}
238

239
func AddFinalizer(object metav1.Object, finalizer string) {
1✔
240
        if HasFinalizer(object, finalizer) {
2✔
241
                return
1✔
242
        }
1✔
243
        object.SetFinalizers(append(object.GetFinalizers(), finalizer))
1✔
244
}
245

246
func ObservedLatestApiVersionAnnotation(object metav1.Object) bool {
1✔
247
        annotations := object.GetAnnotations()
1✔
248
        if annotations == nil {
1✔
249
                return false
×
250
        }
×
251

252
        version, ok := annotations[v1.ControllerAPILatestVersionObservedAnnotation]
1✔
253
        if !ok || version != v1.ApiLatestVersion {
1✔
254
                return false
×
255
        }
×
256
        return true
1✔
257
}
258

259
func SetLatestApiVersionAnnotation(object metav1.Object) {
1✔
260
        annotations := object.GetAnnotations()
1✔
261
        if annotations == nil {
2✔
262
                annotations = make(map[string]string)
1✔
263
        }
1✔
264

265
        annotations[v1.ControllerAPILatestVersionObservedAnnotation] = v1.ApiLatestVersion
1✔
266
        annotations[v1.ControllerAPIStorageVersionObservedAnnotation] = v1.ApiStorageVersion
1✔
267
        object.SetAnnotations(annotations)
1✔
268
}
269

270
func ApplyVolumeRequestOnVMISpec(vmiSpec *v1.VirtualMachineInstanceSpec, request *v1.VirtualMachineVolumeRequest) *v1.VirtualMachineInstanceSpec {
1✔
271
        if request.AddVolumeOptions != nil {
2✔
272
                alreadyAdded := false
1✔
273
                for _, volume := range vmiSpec.Volumes {
2✔
274
                        if volume.Name == request.AddVolumeOptions.Name {
2✔
275
                                alreadyAdded = true
1✔
276
                                break
1✔
277
                        }
278
                }
279

280
                if !alreadyAdded {
2✔
281
                        newVolume := v1.Volume{
1✔
282
                                Name: request.AddVolumeOptions.Name,
1✔
283
                        }
1✔
284

1✔
285
                        if request.AddVolumeOptions.VolumeSource.PersistentVolumeClaim != nil {
2✔
286
                                pvcSource := request.AddVolumeOptions.VolumeSource.PersistentVolumeClaim.DeepCopy()
1✔
287
                                pvcSource.Hotpluggable = true
1✔
288
                                newVolume.VolumeSource.PersistentVolumeClaim = pvcSource
1✔
289
                        } else if request.AddVolumeOptions.VolumeSource.DataVolume != nil {
3✔
290
                                dvSource := request.AddVolumeOptions.VolumeSource.DataVolume.DeepCopy()
1✔
291
                                dvSource.Hotpluggable = true
1✔
292
                                newVolume.VolumeSource.DataVolume = dvSource
1✔
293
                        }
1✔
294

295
                        vmiSpec.Volumes = append(vmiSpec.Volumes, newVolume)
1✔
296

1✔
297
                        if request.AddVolumeOptions.Disk != nil {
2✔
298
                                newDisk := request.AddVolumeOptions.Disk.DeepCopy()
1✔
299
                                newDisk.Name = request.AddVolumeOptions.Name
1✔
300

1✔
301
                                vmiSpec.Domain.Devices.Disks = append(vmiSpec.Domain.Devices.Disks, *newDisk)
1✔
302
                        }
1✔
303
                }
304

305
        } else if request.RemoveVolumeOptions != nil {
2✔
306

1✔
307
                newVolumesList := []v1.Volume{}
1✔
308
                newDisksList := []v1.Disk{}
1✔
309

1✔
310
                for _, volume := range vmiSpec.Volumes {
2✔
311
                        if volume.Name != request.RemoveVolumeOptions.Name {
2✔
312
                                newVolumesList = append(newVolumesList, volume)
1✔
313
                        }
1✔
314
                }
315

316
                for _, disk := range vmiSpec.Domain.Devices.Disks {
2✔
317
                        if disk.Name != request.RemoveVolumeOptions.Name {
2✔
318
                                newDisksList = append(newDisksList, disk)
1✔
319
                        }
1✔
320
                }
321

322
                vmiSpec.Volumes = newVolumesList
1✔
323
                vmiSpec.Domain.Devices.Disks = newDisksList
1✔
324
        }
325

326
        return vmiSpec
1✔
327
}
328

329
func CurrentVMIPod(vmi *v1.VirtualMachineInstance, podIndexer cache.Indexer) (*k8sv1.Pod, error) {
1✔
330

1✔
331
        // current pod is the most recent pod created on the current VMI node
1✔
332
        // OR the most recent pod created if no VMI node is set.
1✔
333

1✔
334
        // Get all pods from the namespace
1✔
335
        objs, err := podIndexer.ByIndex(cache.NamespaceIndex, vmi.Namespace)
1✔
336
        if err != nil {
1✔
337
                return nil, err
×
338
        }
×
339
        pods := []*k8sv1.Pod{}
1✔
340
        for _, obj := range objs {
2✔
341
                pod := obj.(*k8sv1.Pod)
1✔
342
                pods = append(pods, pod)
1✔
343
        }
1✔
344

345
        var curPod *k8sv1.Pod = nil
1✔
346
        for _, pod := range pods {
2✔
347
                if !IsControlledBy(pod, vmi) {
2✔
348
                        continue
1✔
349
                }
350

351
                if vmi.Status.NodeName != "" &&
1✔
352
                        vmi.Status.NodeName != pod.Spec.NodeName {
2✔
353
                        // This pod isn't scheduled to the current node.
1✔
354
                        // This can occur during the initial migration phases when
1✔
355
                        // a new target node is being prepared for the VMI.
1✔
356
                        continue
1✔
357
                }
358

359
                if curPod == nil || curPod.CreationTimestamp.Before(&pod.CreationTimestamp) {
2✔
360
                        curPod = pod
1✔
361
                }
1✔
362
        }
363

364
        return curPod, nil
1✔
365
}
366

367
func VMIActivePodsCount(vmi *v1.VirtualMachineInstance, vmiPodIndexer cache.Indexer) int {
1✔
368

1✔
369
        objs, err := vmiPodIndexer.ByIndex(cache.NamespaceIndex, vmi.Namespace)
1✔
370
        if err != nil {
1✔
371
                return 0
×
372
        }
×
373

374
        running := 0
1✔
375
        for _, obj := range objs {
2✔
376
                pod := obj.(*k8sv1.Pod)
1✔
377

1✔
378
                if pod.Status.Phase == k8sv1.PodSucceeded || pod.Status.Phase == k8sv1.PodFailed {
2✔
379
                        // not interested in terminated pods
1✔
380
                        continue
1✔
381
                } else if !IsControlledBy(pod, vmi) {
2✔
382
                        // not interested pods not associated with the vmi
1✔
383
                        continue
1✔
384
                }
385
                running++
1✔
386
        }
387

388
        return running
1✔
389
}
390

391
func GeneratePatchBytes(ops []string) []byte {
×
392
        return []byte(fmt.Sprintf("[%s]", strings.Join(ops, ", ")))
×
393
}
×
394

395
func SetVMIPhaseTransitionTimestamp(oldStatus *v1.VirtualMachineInstanceStatus, newStatus *v1.VirtualMachineInstanceStatus) {
1✔
396
        if oldStatus.Phase != newStatus.Phase {
2✔
397
                for _, transitionTimeStamp := range newStatus.PhaseTransitionTimestamps {
1✔
NEW
398
                        if transitionTimeStamp.Phase == newStatus.Phase {
×
399
                                // already exists.
×
400
                                return
×
401
                        }
×
402
                }
403

404
                now := metav1.NewTime(time.Now())
1✔
405
                newStatus.PhaseTransitionTimestamps = append(newStatus.PhaseTransitionTimestamps, v1.VirtualMachineInstancePhaseTransitionTimestamp{
1✔
406
                        Phase:                    newStatus.Phase,
1✔
407
                        PhaseTransitionTimestamp: now,
1✔
408
                })
1✔
409
        }
410
}
411

412
func SetVMIMigrationPhaseTransitionTimestamp(oldVMIMigration *v1.VirtualMachineInstanceMigration, newVMIMigration *v1.VirtualMachineInstanceMigration) {
1✔
413
        if oldVMIMigration.Status.Phase != newVMIMigration.Status.Phase {
2✔
414
                for _, transitionTimeStamp := range newVMIMigration.Status.PhaseTransitionTimestamps {
1✔
415
                        if transitionTimeStamp.Phase == newVMIMigration.Status.Phase {
×
416
                                // already exists.
×
417
                                return
×
418
                        }
×
419
                }
420

421
                now := metav1.NewTime(time.Now())
1✔
422
                newVMIMigration.Status.PhaseTransitionTimestamps = append(newVMIMigration.Status.PhaseTransitionTimestamps, v1.VirtualMachineInstanceMigrationPhaseTransitionTimestamp{
1✔
423
                        Phase:                    newVMIMigration.Status.Phase,
1✔
424
                        PhaseTransitionTimestamp: now,
1✔
425
                })
1✔
426
        }
427
}
428

429
func SetSourcePod(migration *v1.VirtualMachineInstanceMigration, vmi *v1.VirtualMachineInstance, podIndexer cache.Indexer) {
1✔
430
        if migration.Status.Phase != v1.MigrationPending {
2✔
431
                return
1✔
432
        }
1✔
433
        sourcePod, err := CurrentVMIPod(vmi, podIndexer)
1✔
434
        if err != nil {
1✔
435
                log.Log.Object(vmi).Reason(err).Warning("migration source pod not found")
×
436
        }
×
437
        if sourcePod != nil {
2✔
438
                if migration.Status.MigrationState == nil {
2✔
439
                        migration.Status.MigrationState = &v1.VirtualMachineInstanceMigrationState{}
1✔
440
                }
1✔
441
                migration.Status.MigrationState.SourcePod = sourcePod.Name
1✔
442
        }
443

444
}
445

446
func VMIHasHotplugVolumes(vmi *v1.VirtualMachineInstance) bool {
1✔
447
        for _, volumeStatus := range vmi.Status.VolumeStatus {
2✔
448
                if volumeStatus.HotplugVolume != nil {
2✔
449
                        return true
1✔
450
                }
1✔
451
        }
452
        for _, volume := range vmi.Spec.Volumes {
1✔
453
                if volume.DataVolume != nil && volume.DataVolume.Hotpluggable {
×
454
                        return true
×
455
                }
×
456
                if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.Hotpluggable {
×
457
                        return true
×
458
                }
×
459
        }
460
        return false
1✔
461
}
462

463
func vmiHasCondition(vmi *v1.VirtualMachineInstance, conditionType v1.VirtualMachineInstanceConditionType) bool {
1✔
464
        vmiConditionManager := NewVirtualMachineInstanceConditionManager()
1✔
465
        return vmiConditionManager.HasCondition(vmi, conditionType)
1✔
466
}
1✔
467

468
func VMIHasHotplugCPU(vmi *v1.VirtualMachineInstance) bool {
1✔
469
        return vmiHasCondition(vmi, v1.VirtualMachineInstanceVCPUChange)
1✔
470
}
1✔
471

472
func VMIHasHotplugMemory(vmi *v1.VirtualMachineInstance) bool {
1✔
473
        return vmiHasCondition(vmi, v1.VirtualMachineInstanceMemoryChange)
1✔
474
}
1✔
475

476
func AttachmentPods(ownerPod *k8sv1.Pod, podIndexer cache.Indexer) ([]*k8sv1.Pod, error) {
1✔
477
        objs, err := podIndexer.ByIndex(cache.NamespaceIndex, ownerPod.Namespace)
1✔
478
        if err != nil {
1✔
479
                return nil, err
×
480
        }
×
481
        attachmentPods := []*k8sv1.Pod{}
1✔
482
        for _, obj := range objs {
2✔
483
                pod := obj.(*k8sv1.Pod)
1✔
484
                ownerRef := GetControllerOf(pod)
1✔
485
                if ownerRef == nil || ownerRef.UID != ownerPod.UID {
2✔
486
                        continue
1✔
487
                }
488
                attachmentPods = append(attachmentPods, pod)
1✔
489
        }
490
        return attachmentPods, nil
1✔
491
}
492

493
// IsPodReady treats the pod as ready to be handed over to virt-handler, as soon as all pods except
494
// the compute pod are ready.
495
func IsPodReady(pod *k8sv1.Pod) bool {
1✔
496
        if IsPodDownOrGoingDown(pod) {
2✔
497
                return false
1✔
498
        }
1✔
499

500
        for _, containerStatus := range pod.Status.ContainerStatuses {
2✔
501
                // The compute container potentially holds a readiness probe for the VMI. Therefore
1✔
502
                // don't wait for the compute container to become ready (the VMI later on will trigger the change to ready)
1✔
503
                // and only check that the container started
1✔
504
                if containerStatus.Name == "compute" {
2✔
505
                        if containerStatus.State.Running == nil {
2✔
506
                                return false
1✔
507
                        }
1✔
508
                } else if containerStatus.Name == "istio-proxy" {
2✔
509
                        // When using istio the istio-proxy container will not be ready
1✔
510
                        // until there is a service pointing to this pod.
1✔
511
                        // We need to start the VM anyway
1✔
512
                        if containerStatus.State.Running == nil {
2✔
513
                                return false
1✔
514
                        }
1✔
515

516
                } else if containerStatus.Ready == false {
2✔
517
                        return false
1✔
518
                }
1✔
519
        }
520

521
        return pod.Status.Phase == k8sv1.PodRunning
1✔
522
}
523

524
func IsPodDownOrGoingDown(pod *k8sv1.Pod) bool {
1✔
525
        return PodIsDown(pod) || isComputeContainerDown(pod) || pod.DeletionTimestamp != nil
1✔
526
}
1✔
527

528
func IsPodFailedOrGoingDown(pod *k8sv1.Pod) bool {
1✔
529
        return isPodFailed(pod) || isComputeContainerFailed(pod) || pod.DeletionTimestamp != nil
1✔
530
}
1✔
531

532
func isComputeContainerDown(pod *k8sv1.Pod) bool {
1✔
533
        for _, containerStatus := range pod.Status.ContainerStatuses {
2✔
534
                if containerStatus.Name == "compute" {
2✔
535
                        return containerStatus.State.Terminated != nil
1✔
536
                }
1✔
537
        }
538
        return false
1✔
539
}
540

541
func isComputeContainerFailed(pod *k8sv1.Pod) bool {
1✔
542
        for _, containerStatus := range pod.Status.ContainerStatuses {
2✔
543
                if containerStatus.Name == "compute" {
2✔
544
                        return containerStatus.State.Terminated != nil && containerStatus.State.Terminated.ExitCode != 0
1✔
545
                }
1✔
546
        }
547
        return false
1✔
548
}
549

550
func PodIsDown(pod *k8sv1.Pod) bool {
1✔
551
        return pod.Status.Phase == k8sv1.PodSucceeded || pod.Status.Phase == k8sv1.PodFailed
1✔
552
}
1✔
553

554
func isPodFailed(pod *k8sv1.Pod) bool {
1✔
555
        return pod.Status.Phase == k8sv1.PodFailed
1✔
556
}
1✔
557

558
func PodExists(pod *k8sv1.Pod) bool {
1✔
559
        return pod != nil
1✔
560
}
1✔
561

562
func GetHotplugVolumes(vmi *v1.VirtualMachineInstance, virtlauncherPod *k8sv1.Pod) []*v1.Volume {
1✔
563
        hotplugVolumes := make([]*v1.Volume, 0)
1✔
564
        podVolumes := virtlauncherPod.Spec.Volumes
1✔
565
        vmiVolumes := vmi.Spec.Volumes
1✔
566

1✔
567
        podVolumeMap := make(map[string]k8sv1.Volume)
1✔
568
        for _, podVolume := range podVolumes {
2✔
569
                podVolumeMap[podVolume.Name] = podVolume
1✔
570
        }
1✔
571
        for _, vmiVolume := range vmiVolumes {
2✔
572
                if _, ok := podVolumeMap[vmiVolume.Name]; !ok && (vmiVolume.DataVolume != nil || vmiVolume.PersistentVolumeClaim != nil || vmiVolume.MemoryDump != nil) {
2✔
573
                        hotplugVolumes = append(hotplugVolumes, vmiVolume.DeepCopy())
1✔
574
                }
1✔
575
        }
576
        return hotplugVolumes
1✔
577
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc