• 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

57.38
/pkg/virt-handler/migration-source.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 virthandler
21

22
import (
23
        "context"
24
        "encoding/json"
25
        "errors"
26
        "fmt"
27
        "path/filepath"
28
        "time"
29

30
        "libvirt.org/go/libvirtxml"
31

32
        k8sv1 "k8s.io/api/core/v1"
33
        "k8s.io/apimachinery/pkg/api/equality"
34
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35
        "k8s.io/apimachinery/pkg/util/wait"
36
        "k8s.io/client-go/tools/cache"
37
        "k8s.io/client-go/tools/record"
38
        "k8s.io/client-go/util/workqueue"
39

40
        v1 "kubevirt.io/api/core/v1"
41
        "kubevirt.io/client-go/kubecli"
42
        "kubevirt.io/client-go/log"
43

44
        "kubevirt.io/kubevirt/pkg/controller"
45
        hostdisk "kubevirt.io/kubevirt/pkg/host-disk"
46
        "kubevirt.io/kubevirt/pkg/pointer"
47
        "kubevirt.io/kubevirt/pkg/util/migrations"
48
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
49
        cmdclient "kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
50
        "kubevirt.io/kubevirt/pkg/virt-handler/isolation"
51

52
        launcher_clients "kubevirt.io/kubevirt/pkg/virt-handler/launcher-clients"
53
        migrationproxy "kubevirt.io/kubevirt/pkg/virt-handler/migration-proxy"
54
        "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
55
)
56

57
var errWaitingForTargetPorts = errors.New("waiting for target to publish migration ports")
58

59
type MigrationSourceController struct {
60
        *BaseController
61
        capabilities                *libvirtxml.Caps
62
        clientset                   kubecli.KubevirtClient
63
        queue                       workqueue.TypedRateLimitingInterface[string]
64
        launcherClients             launcher_clients.LauncherClientsManager
65
        migrationProxy              migrationproxy.ProxyManager
66
        podIsolationDetector        isolation.PodIsolationDetector
67
        recorder                    record.EventRecorder
68
        virtLauncherFSRunDirPattern string
69
        vmiExpectations             *controller.UIDTrackingControllerExpectations
70
}
71

72
func NewMigrationSourceController(
73
        recorder record.EventRecorder,
74
        clientset kubecli.KubevirtClient,
75
        host string,
76
        launcherClients launcher_clients.LauncherClientsManager,
77
        vmiInformer cache.SharedIndexInformer,
78
        domainInformer cache.SharedInformer,
79
        clusterConfig *virtconfig.ClusterConfig,
80
        podIsolationDetector isolation.PodIsolationDetector,
81
        migrationProxy migrationproxy.ProxyManager,
82
        virtLauncherFSRunDirPattern string,
83
) (*MigrationSourceController, error) {
1✔
84

1✔
85
        baseCtrl, err := NewBaseController(
1✔
86
                host,
1✔
87
                vmiInformer,
1✔
88
                domainInformer,
1✔
89
                clusterConfig,
1✔
90
                podIsolationDetector,
1✔
91
        )
1✔
92
        if err != nil {
1✔
NEW
93
                return nil, err
×
NEW
94
        }
×
95

96
        queue := workqueue.NewTypedRateLimitingQueueWithConfig[string](
1✔
97
                workqueue.DefaultTypedControllerRateLimiter[string](),
1✔
98
                workqueue.TypedRateLimitingQueueConfig[string]{Name: "virt-handler-source"},
1✔
99
        )
1✔
100

1✔
101
        c := &MigrationSourceController{
1✔
102
                BaseController:              baseCtrl,
1✔
103
                clientset:                   clientset,
1✔
104
                queue:                       queue,
1✔
105
                launcherClients:             launcherClients,
1✔
106
                migrationProxy:              migrationProxy,
1✔
107
                podIsolationDetector:        podIsolationDetector,
1✔
108
                recorder:                    recorder,
1✔
109
                virtLauncherFSRunDirPattern: virtLauncherFSRunDirPattern,
1✔
110
                vmiExpectations:             controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()),
1✔
111
        }
1✔
112

1✔
113
        _, err = vmiInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
1✔
114
                AddFunc:    c.addFunc,
1✔
115
                DeleteFunc: c.deleteFunc,
1✔
116
                UpdateFunc: c.updateFunc,
1✔
117
        })
1✔
118
        if err != nil {
1✔
NEW
119
                return nil, err
×
NEW
120
        }
×
121

122
        _, err = domainInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
1✔
123
                AddFunc:    c.addDeleteDomainFunc,
1✔
124
                DeleteFunc: c.addDeleteDomainFunc,
1✔
125
                UpdateFunc: c.updateDomainFunc,
1✔
126
        })
1✔
127
        if err != nil {
1✔
NEW
128
                return nil, err
×
NEW
129
        }
×
130

131
        return c, nil
1✔
132
}
133

134
func (c *MigrationSourceController) hasTargetDetectedReadyDomain(vmi *v1.VirtualMachineInstance) (bool, int64) {
1✔
135
        // give the target node 60 seconds to discover the libvirt domain via the domain informer
1✔
136
        // before allowing the VMI to be processed. This closes the gap between the
1✔
137
        // VMI's status getting updated to reflect the new source node, and the domain
1✔
138
        // informer firing the event to alert the source node of the new domain.
1✔
139
        migrationTargetDelayTimeout := 60
1✔
140

1✔
141
        if vmi.Status.MigrationState == nil ||
1✔
142
                vmi.Status.MigrationState.EndTimestamp == nil {
2✔
143
                return false, int64(migrationTargetDelayTimeout)
1✔
144
        }
1✔
145

NEW
146
        if vmi.Status.MigrationState != nil &&
×
NEW
147
                vmi.Status.MigrationState.TargetNodeDomainDetected &&
×
NEW
148
                vmi.Status.MigrationState.TargetNodeDomainReadyTimestamp != nil {
×
NEW
149

×
NEW
150
                return true, 0
×
NEW
151
        }
×
152

NEW
153
        nowUnix := time.Now().UTC().Unix()
×
NEW
154
        migrationEndUnix := vmi.Status.MigrationState.EndTimestamp.Time.UTC().Unix()
×
NEW
155

×
NEW
156
        diff := nowUnix - migrationEndUnix
×
NEW
157

×
NEW
158
        if diff > int64(migrationTargetDelayTimeout) {
×
NEW
159
                return false, 0
×
NEW
160
        }
×
161

NEW
162
        timeLeft := int64(migrationTargetDelayTimeout) - diff
×
NEW
163

×
NEW
164
        enqueueTime := timeLeft
×
NEW
165
        if enqueueTime < 5 {
×
NEW
166
                enqueueTime = 5
×
NEW
167
        }
×
168

169
        // re-enqueue the key to ensure it gets processed again within the right time.
NEW
170
        c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Duration(enqueueTime)*time.Second)
×
NEW
171

×
NEW
172
        return false, timeLeft
×
173
}
174

175
func domainMigrated(domain *api.Domain) bool {
1✔
176
        return domain != nil && domain.Status.Status == api.Shutoff && domain.Status.Reason == api.ReasonMigrated
1✔
177
}
1✔
178

179
func (c *MigrationSourceController) setMigrationProgressStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) {
1✔
180
        if domain == nil ||
1✔
181
                domain.Spec.Metadata.KubeVirt.Migration == nil ||
1✔
182
                vmi.Status.MigrationState == nil {
2✔
183
                return
1✔
184
        }
1✔
185

186
        migrationMetadata := domain.Spec.Metadata.KubeVirt.Migration
1✔
187
        if migrationMetadata.UID != vmi.Status.MigrationState.MigrationUID {
2✔
188
                return
1✔
189
        }
1✔
190

191
        vmi.Status.MigrationState.StartTimestamp = migrationMetadata.StartTimestamp
1✔
192

1✔
193
        vmi.Status.MigrationState.Failed = migrationMetadata.Failed
1✔
194

1✔
195
        if migrationMetadata.Failed {
2✔
196
                vmi.Status.MigrationState.EndTimestamp = migrationMetadata.EndTimestamp
1✔
197
                vmi.Status.MigrationState.FailureReason = migrationMetadata.FailureReason
1✔
198
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), fmt.Sprintf("VirtualMachineInstance migration uid %s failed. reason:%s", string(migrationMetadata.UID), migrationMetadata.FailureReason))
1✔
199
        }
1✔
200

201
        vmi.Status.MigrationState.AbortStatus = v1.MigrationAbortStatus(migrationMetadata.AbortStatus)
1✔
202
        if migrationMetadata.AbortStatus == string(v1.MigrationAbortSucceeded) {
1✔
NEW
203
                vmi.Status.MigrationState.EndTimestamp = migrationMetadata.EndTimestamp
×
NEW
204
        }
×
205

206
        vmi.Status.MigrationState.Mode = migrationMetadata.Mode
1✔
207
}
208

209
func (c *MigrationSourceController) updateStatus(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
210
        c.setMigrationProgressStatus(vmi, domain)
1✔
211

1✔
212
        // handle migrations differently than normal status updates.
1✔
213
        //
1✔
214
        // When a successful migration is detected, we must transfer ownership of the VMI
1✔
215
        // from the source node (this node) to the target node (node the domain was migrated to).
1✔
216
        //
1✔
217
        // Transfer ownership by...
1✔
218
        // 1. Marking vmi.Status.MigrationState as completed
1✔
219
        // 2. Update the vmi.Status.NodeName to reflect the target node's name
1✔
220
        // 3. Update the VMI's NodeNameLabel annotation to reflect the target node's name
1✔
221
        // 4. Clear the LauncherContainerImageVersion which virt-controller will detect
1✔
222
        //    and accurately based on the version used on the target pod
1✔
223
        //
1✔
224
        // After a migration, the VMI's phase is no longer owned by this node. Only the
1✔
225
        // MigrationState status field is eligible to be mutated.
1✔
226
        migrationHost := ""
1✔
227
        if vmi.Status.MigrationState != nil {
2✔
228
                migrationHost = vmi.Status.MigrationState.TargetNode
1✔
229
        }
1✔
230

231
        targetNodeDetectedDomain, timeLeft := c.hasTargetDetectedReadyDomain(vmi)
1✔
232
        // If we can't detect where the migration went to, then we have no
1✔
233
        // way of transferring ownership. The only option here is to move the
1✔
234
        // vmi to failed.  The cluster vmi controller will then tear down the
1✔
235
        // resulting pods.
1✔
236
        if migrationHost == "" {
1✔
NEW
237
                // migrated to unknown host.
×
NEW
238
                vmi.Status.Phase = v1.Failed
×
NEW
239
                vmi.Status.MigrationState.Completed = true
×
NEW
240
                vmi.Status.MigrationState.Failed = true
×
NEW
241

×
NEW
242
                log.Log.Object(vmi).Warning("the vmi migrated to an unknown host")
×
NEW
243
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), fmt.Sprintf("The VirtualMachineInstance migrated to unknown host."))
×
244
        } else if !targetNodeDetectedDomain {
2✔
245
                if timeLeft <= 0 {
1✔
NEW
246
                        vmi.Status.Phase = v1.Failed
×
NEW
247
                        vmi.Status.MigrationState.Completed = true
×
NEW
248
                        vmi.Status.MigrationState.Failed = true
×
NEW
249

×
NEW
250
                        log.Log.Object(vmi).Warning("the domain was never observed on the taget after the migration completed within the timeout period")
×
NEW
251
                        c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.Migrated.String(), fmt.Sprintf("The VirtualMachineInstance's domain was never observed on the target after the migration completed within the timeout period."))
×
NEW
252
                }
×
253
        }
254

255
        return nil
1✔
256
}
257

NEW
258
func (c *MigrationSourceController) Run(threadiness int, stopCh chan struct{}) {
×
NEW
259
        defer c.queue.ShutDown()
×
NEW
260
        log.Log.Info("Starting virt-handler source controller.")
×
NEW
261

×
NEW
262
        cache.WaitForCacheSync(stopCh, c.hasSynced)
×
NEW
263

×
NEW
264
        // queue keys for previous Domains on the host that no longer exist
×
NEW
265
        // in the cache. This ensures we perform local cleanup of deleted VMs.
×
NEW
266
        for _, domain := range c.domainStore.List() {
×
NEW
267
                d := domain.(*api.Domain)
×
NEW
268
                vmiRef := v1.NewVMIReferenceWithUUID(
×
NEW
269
                        d.ObjectMeta.Namespace,
×
NEW
270
                        d.ObjectMeta.Name,
×
NEW
271
                        d.Spec.Metadata.KubeVirt.UID)
×
NEW
272

×
NEW
273
                key := controller.VirtualMachineInstanceKey(vmiRef)
×
NEW
274

×
NEW
275
                _, exists, _ := c.vmiStore.GetByKey(key)
×
NEW
276
                if !exists {
×
NEW
277
                        c.queue.Add(key)
×
NEW
278
                }
×
279
        }
280

281
        // Start the actual work
NEW
282
        for i := 0; i < threadiness; i++ {
×
NEW
283
                go wait.Until(c.runWorker, time.Second, stopCh)
×
NEW
284
        }
×
285

NEW
286
        <-stopCh
×
NEW
287
        log.Log.Info("Stopping virt-handler source controller.")
×
288
}
289

NEW
290
func (c *MigrationSourceController) runWorker() {
×
NEW
291
        for c.Execute() {
×
NEW
292
        }
×
293
}
294

295
func (c *MigrationSourceController) Execute() bool {
1✔
296
        key, quit := c.queue.Get()
1✔
297
        if quit {
1✔
NEW
298
                return false
×
NEW
299
        }
×
300
        defer c.queue.Done(key)
1✔
301
        if err := c.execute(key); err != nil {
1✔
NEW
302
                log.Log.Reason(err).Infof("re-enqueuing VirtualMachineInstance %v", key)
×
NEW
303
                c.queue.AddRateLimited(key)
×
304
        } else {
1✔
305
                log.Log.V(4).Infof("processed VirtualMachineInstance %v", key)
1✔
306
                c.queue.Forget(key)
1✔
307
        }
1✔
308
        return true
1✔
309
}
310

311
func (c *MigrationSourceController) sync(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
312
        if domain != nil {
2✔
313
                log.Log.Object(vmi).Infof("VMI is in phase: %v | Domain status: %v, reason: %v", vmi.Status.Phase, domain.Status.Status, domain.Status.Reason)
1✔
314
        } else {
1✔
NEW
315
                log.Log.Object(vmi).Infof("VMI is in phase: %v", vmi.Status.Phase)
×
NEW
316
        }
×
317

318
        oldStatus := vmi.Status.DeepCopy()
1✔
319

1✔
320
        syncErr := c.processVMI(vmi, domain)
1✔
321

1✔
322
        if syncErr != nil {
1✔
NEW
323
                c.recorder.Event(vmi, k8sv1.EventTypeWarning, v1.SyncFailed.String(), syncErr.Error())
×
NEW
324
                // `syncErr` will be propagated anyway, and it will be logged in `re-enqueueing`
×
NEW
325
                // so there is no need to log it twice in hot path without increased verbosity.
×
NEW
326
                log.Log.Object(vmi).Reason(syncErr).Error("Synchronizing the VirtualMachineInstance failed.")
×
NEW
327
        }
×
328

329
        updateErr := c.updateStatus(vmi, domain)
1✔
330

1✔
331
        if updateErr != nil {
1✔
NEW
332
                log.Log.Object(vmi).Reason(updateErr).Error("Updating the migration status failed.")
×
NEW
333
        }
×
334

335
        // update the VMI if necessary
336
        if !equality.Semantic.DeepEqual(*oldStatus, vmi.Status) {
2✔
337
                key := controller.VirtualMachineInstanceKey(vmi)
1✔
338
                c.vmiExpectations.SetExpectations(key, 1, 0)
1✔
339
                _, err := c.clientset.VirtualMachineInstance(vmi.ObjectMeta.Namespace).Update(context.Background(), vmi, metav1.UpdateOptions{})
1✔
340
                if err != nil {
1✔
NEW
341
                        c.vmiExpectations.SetExpectations(key, 0, 0)
×
NEW
342
                        return err
×
NEW
343
                }
×
344
        }
345

346
        if syncErr != nil {
1✔
NEW
347
                return syncErr
×
NEW
348
        }
×
349

350
        if updateErr != nil {
1✔
NEW
351
                return updateErr
×
NEW
352
        }
×
353

354
        log.Log.Object(vmi).V(4).Info("Source synchronization loop succeeded.")
1✔
355
        return nil
1✔
356

357
}
358

359
func (c *MigrationSourceController) execute(key string) error {
1✔
360
        vmi, vmiExists, err := c.getVMIFromCache(key)
1✔
361
        if err != nil {
1✔
NEW
362
                return err
×
NEW
363
        }
×
364

365
        if !vmiExists || vmi.IsFinal() || vmi.DeletionTimestamp != nil {
1✔
NEW
366
                log.Log.V(4).Infof("vmi for key %v is terminating, final or does not exists", key)
×
NEW
367
                return nil
×
NEW
368
        }
×
369

370
        if !c.vmiExpectations.SatisfiedExpectations(key) {
1✔
NEW
371
                log.Log.V(4).Object(vmi).Info("waiting for expectations to be satisfied")
×
NEW
372
                return nil
×
NEW
373
        }
×
374

375
        domain, domainExists, _, err := c.getDomainFromCache(key)
1✔
376
        if err != nil {
1✔
NEW
377
                return err
×
NEW
378
        }
×
379

380
        if domainExists && domain.Spec.Metadata.KubeVirt.UID != vmi.UID {
1✔
NEW
381
                log.Log.V(4).Object(vmi).Infof("Detected stale vmi %s that still needs cleanup before new vmi with identical name/namespace can be processed", vmi.UID)
×
NEW
382
                return nil
×
NEW
383
        }
×
384

385
        if vmi.Status.MigrationState == nil {
1✔
NEW
386
                log.Log.V(4).Object(vmi).Info("no migration is in progress")
×
NEW
387
                return nil
×
NEW
388
        }
×
389

390
        // post migration clean up
391
        if isMigrationDone(vmi.Status.MigrationState) {
1✔
NEW
392
                c.migrationProxy.StopSourceListener(string(vmi.UID))
×
NEW
393
                return nil
×
NEW
394
        }
×
395

396
        if !c.isMigrationSource(vmi) {
1✔
NEW
397
                log.Log.Object(vmi).V(4).Info("not a migration source")
×
NEW
398
                return nil
×
NEW
399
        }
×
400

401
        return c.sync(vmi.DeepCopy(), domain)
1✔
402
}
403

404
func (c *MigrationSourceController) isMigrationSource(vmi *v1.VirtualMachineInstance) bool {
1✔
405

1✔
406
        if vmi.Status.MigrationState != nil &&
1✔
407
                vmi.Status.NodeName == c.host &&
1✔
408
                vmi.Status.MigrationState.SourceNode == c.host {
2✔
409

1✔
410
                return true
1✔
411
        }
1✔
NEW
412
        return false
×
413

414
}
415

416
func (c *MigrationSourceController) handleSourceMigrationProxy(vmi *v1.VirtualMachineInstance) error {
1✔
417

1✔
418
        res, err := c.podIsolationDetector.Detect(vmi)
1✔
419
        if err != nil {
1✔
NEW
420
                return err
×
NEW
421
        }
×
422
        // the migration-proxy is no longer shared via host mount, so we
423
        // pass in the virt-launcher's baseDir to reach the unix sockets.
424
        baseDir := fmt.Sprintf(filepath.Join(c.virtLauncherFSRunDirPattern, "kubevirt"), res.Pid())
1✔
425
        if vmi.Status.MigrationState.TargetDirectMigrationNodePorts == nil {
1✔
NEW
426
                return errWaitingForTargetPorts
×
NEW
427
        }
×
428

429
        err = c.migrationProxy.StartSourceListener(
1✔
430
                string(vmi.UID),
1✔
431
                vmi.Status.MigrationState.TargetNodeAddress,
1✔
432
                vmi.Status.MigrationState.TargetDirectMigrationNodePorts,
1✔
433
                baseDir,
1✔
434
        )
1✔
435
        if err != nil {
1✔
NEW
436
                return err
×
NEW
437
        }
×
438

439
        return nil
1✔
440
}
441

442
func (c *MigrationSourceController) migrateVMI(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
443
        isUnresponsive, isInitialized, err := c.launcherClients.IsLauncherClientUnresponsive(vmi)
1✔
444
        if err != nil {
1✔
NEW
445
                return err
×
NEW
446
        }
×
447
        if !isInitialized {
1✔
NEW
448
                log.Log.Object(vmi).V(4).Info("launcher client is not initialized")
×
NEW
449
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), time.Second*1)
×
NEW
450
                return nil
×
451
        } else if isUnresponsive {
1✔
NEW
452
                return errors.New(fmt.Sprintf("Can not update a VirtualMachineInstance with unresponsive command server."))
×
NEW
453
        }
×
454

455
        client, err := c.launcherClients.GetLauncherClient(vmi)
1✔
456
        if err != nil {
1✔
NEW
457
                return fmt.Errorf(unableCreateVirtLauncherConnectionFmt, err)
×
NEW
458
        }
×
459

460
        if vmi.Status.MigrationState.AbortRequested {
2✔
461
                err = c.handleMigrationAbort(vmi, client)
1✔
462
                return err
1✔
463
        }
1✔
464

465
        if isMigrationInProgress(vmi, domain) {
1✔
NEW
466
                // we already started this migration, no need to rerun this
×
NEW
467
                log.Log.Object(vmi).V(4).Infof("migration %s has already been started", vmi.Status.MigrationState.MigrationUID)
×
NEW
468
                return nil
×
NEW
469
        }
×
470

471
        err = c.handleSourceMigrationProxy(vmi)
1✔
472
        if errors.Is(err, errWaitingForTargetPorts) {
1✔
NEW
473
                log.Log.Object(vmi).V(4).Info("waiting for target node to publish migration ports")
×
NEW
474
                c.queue.AddAfter(controller.VirtualMachineInstanceKey(vmi), 1*time.Second)
×
NEW
475
                return nil
×
476
        } else if err != nil {
1✔
NEW
477
                return fmt.Errorf("failed to handle migration proxy: %v", err)
×
NEW
478
        }
×
479

480
        migrationConfiguration := vmi.Status.MigrationState.MigrationConfiguration
1✔
481
        if migrationConfiguration == nil {
2✔
482
                migrationConfiguration = c.clusterConfig.GetMigrationConfiguration()
1✔
483
        }
1✔
484

485
        options := &cmdclient.MigrationOptions{
1✔
486
                Bandwidth:               *migrationConfiguration.BandwidthPerMigration,
1✔
487
                ProgressTimeout:         *migrationConfiguration.ProgressTimeout,
1✔
488
                CompletionTimeoutPerGiB: *migrationConfiguration.CompletionTimeoutPerGiB,
1✔
489
                UnsafeMigration:         *migrationConfiguration.UnsafeMigrationOverride,
1✔
490
                AllowAutoConverge:       *migrationConfiguration.AllowAutoConverge,
1✔
491
                AllowPostCopy:           *migrationConfiguration.AllowPostCopy,
1✔
492
                AllowWorkloadDisruption: *migrationConfiguration.AllowWorkloadDisruption,
1✔
493
        }
1✔
494

1✔
495
        configureParallelMigrationThreads(options, vmi)
1✔
496

1✔
497
        marshalledOptions, err := json.Marshal(options)
1✔
498
        if err != nil {
1✔
NEW
499
                log.Log.Object(vmi).Warning("failed to marshall matched migration options")
×
500
        } else {
1✔
501
                log.Log.Object(vmi).Infof("migration options matched for vmi %s: %s", vmi.Name, string(marshalledOptions))
1✔
502
        }
1✔
503

504
        vmiCopy := vmi.DeepCopy()
1✔
505
        err = hostdisk.ReplacePVCByHostDisk(vmiCopy)
1✔
506
        if err != nil {
1✔
NEW
507
                return err
×
NEW
508
        }
×
509

510
        err = client.MigrateVirtualMachine(vmiCopy, options)
1✔
511
        if err != nil {
1✔
NEW
512
                return err
×
NEW
513
        }
×
514
        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Migrating.String(), VMIMigrating)
1✔
515
        return nil
1✔
516
}
517

518
func isMigrationDone(state *v1.VirtualMachineInstanceMigrationState) bool {
1✔
519
        return state == nil || (state.EndTimestamp != nil && (state.Completed || state.Failed))
1✔
520
}
1✔
521

522
func (c *MigrationSourceController) processVMI(vmi *v1.VirtualMachineInstance, domain *api.Domain) error {
1✔
523
        domainAlive := domain != nil &&
1✔
524
                domain.Status.Status != api.Shutoff &&
1✔
525
                domain.Status.Status != api.Crashed &&
1✔
526
                domain.Status.Status != ""
1✔
527

1✔
528
        if !domainAlive {
1✔
NEW
529
                log.Log.V(4).Object(vmi).Info("domain is not alive")
×
NEW
530
                return nil
×
NEW
531
        }
×
532

533
        return c.migrateVMI(vmi, domain)
1✔
534
}
535

NEW
536
func (c *MigrationSourceController) addFunc(obj interface{}) {
×
NEW
537
        key, err := controller.KeyFunc(obj)
×
NEW
538
        if err == nil {
×
NEW
539
                c.vmiExpectations.SetExpectations(key, 0, 0)
×
NEW
540
                c.queue.Add(key)
×
NEW
541
        }
×
542
}
543

NEW
544
func (c *MigrationSourceController) deleteFunc(obj interface{}) {
×
NEW
545
        key, err := controller.KeyFunc(obj)
×
NEW
546
        if err == nil {
×
NEW
547
                c.queue.Add(key)
×
NEW
548
        }
×
549
}
550

NEW
551
func (c *MigrationSourceController) updateFunc(_, new interface{}) {
×
NEW
552
        key, err := controller.KeyFunc(new)
×
NEW
553
        if err == nil {
×
NEW
554
                c.vmiExpectations.SetExpectations(key, 0, 0)
×
NEW
555
                c.queue.Add(key)
×
NEW
556
        }
×
557
}
558

NEW
559
func (c *MigrationSourceController) addDeleteDomainFunc(obj interface{}) {
×
NEW
560
        key, err := controller.KeyFunc(obj)
×
NEW
561
        if err == nil {
×
NEW
562
                c.queue.Add(key)
×
NEW
563
        }
×
564
}
565

NEW
566
func (c *MigrationSourceController) updateDomainFunc(_, new interface{}) {
×
NEW
567
        key, err := controller.KeyFunc(new)
×
NEW
568
        if err == nil {
×
NEW
569
                c.queue.Add(key)
×
NEW
570
        }
×
571
}
572

573
func (c *MigrationSourceController) handleMigrationAbort(vmi *v1.VirtualMachineInstance, client cmdclient.LauncherClient) error {
1✔
574
        if vmi.Status.MigrationState.AbortStatus == v1.MigrationAbortInProgress || vmi.Status.MigrationState.AbortStatus == v1.MigrationAbortSucceeded {
2✔
575
                return nil
1✔
576
        }
1✔
577

578
        if err := client.CancelVirtualMachineMigration(vmi); err != nil {
2✔
579
                if err.Error() == migrations.CancelMigrationFailedVmiNotMigratingErr {
1✔
NEW
580
                        // If migration did not even start there is no need to cancel it
×
NEW
581
                        log.Log.Object(vmi).Infof("skipping migration cancellation since vmi is not migrating")
×
NEW
582
                }
×
583
                return err
1✔
584
        }
585

586
        c.recorder.Event(vmi, k8sv1.EventTypeNormal, v1.Migrating.String(), VMIAbortingMigration)
1✔
587
        return nil
1✔
588
}
589

590
func configureParallelMigrationThreads(options *cmdclient.MigrationOptions, vm *v1.VirtualMachineInstance) {
1✔
591
        // When the CPU is limited, there's a risk of the migration threads choking the CPU resources on the compute container.
1✔
592
        // For this reason, we will avoid configuring migration threads in such scenarios.
1✔
593
        if cpuLimit, cpuLimitExists := vm.Spec.Domain.Resources.Limits[k8sv1.ResourceCPU]; cpuLimitExists && !cpuLimit.IsZero() {
2✔
594
                return
1✔
595
        }
1✔
596

597
        options.ParallelMigrationThreads = pointer.P(parallelMultifdMigrationThreads)
1✔
598
}
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