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

kubevirt / kubevirt / 7ddace03-8422-4788-878a-867c6549df67

03 Jun 2025 08:52PM UTC coverage: 71.076% (+0.08%) from 71.001%
7ddace03-8422-4788-878a-867c6549df67

push

prow

web-flow
Merge pull request #14784 from awels/add_synchronization_controller

Introducing vmi status synchronization controller

839 of 1085 new or added lines in 15 files covered. (77.33%)

2 existing lines in 1 file now uncovered.

65495 of 92148 relevant lines covered (71.08%)

0.79 hits per line

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

69.5
/pkg/synchronization-controller/synchronization-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.  * See the License for the specific language governing permissions and
13
 * limitations under the License.
14
 *
15
 * Copyright The KubeVirt Authors.
16
 *
17
 */
18

19
package synchronization
20

21
import (
22
        "crypto/tls"
23
        "encoding/json"
24
        "fmt"
25
        "io"
26
        "net"
27
        "os"
28
        "strconv"
29
        "sync"
30
        "time"
31

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

39
        virtv1 "kubevirt.io/api/core/v1"
40

41
        "kubevirt.io/kubevirt/pkg/apimachinery/patch"
42
        "kubevirt.io/kubevirt/pkg/controller"
43

44
        context "golang.org/x/net/context"
45
        "google.golang.org/grpc"
46
        "google.golang.org/grpc/credentials"
47
        "kubevirt.io/client-go/kubecli"
48
        "kubevirt.io/client-go/log"
49

50
        syncv1 "kubevirt.io/kubevirt/pkg/synchronizer-com/synchronization/v1"
51
)
52

53
const (
54
        defaultTimeout = 30
55

56
        MyPodIP = "MY_POD_IP"
57

58
        noSourceStatusErrorMsg               = "must pass source status"
59
        noTargetStatusErrorMsg               = "must pass target status"
60
        unableToLocateVMIMigrationIDErrorMsg = "unable to locate VMI for migrationID %s"
61

62
        successMessage = "success"
63
)
64

65
type SynchronizationController struct {
66
        client   kubecli.KubevirtClient
67
        connChan chan io.ReadWriteCloser
68

69
        vmiInformer       cache.SharedIndexInformer
70
        migrationInformer cache.SharedIndexInformer
71

72
        listener        net.Listener
73
        bindAddress     string
74
        bindPort        int
75
        clientTLSConfig *tls.Config
76
        serverTLSConfig *tls.Config
77
        timeout         int
78

79
        queue     workqueue.TypedRateLimitingInterface[string]
80
        hasSynced func() bool
81

82
        syncOutboundConnectionMap  *sync.Map
83
        syncReceivingConnectionMap *sync.Map
84
        grpcServer                 *grpc.Server
85
}
86

87
func NewSynchronizationController(
88
        client kubecli.KubevirtClient,
89
        vmiInformer cache.SharedIndexInformer,
90
        migrationInformer cache.SharedIndexInformer,
91
        clientTLSConfig,
92
        serverTLSConfig *tls.Config,
93
        bindAddress string,
94
        bindPort int,
95
) (*SynchronizationController, error) {
1✔
96
        syncController := &SynchronizationController{
1✔
97
                vmiInformer:       vmiInformer,
1✔
98
                migrationInformer: migrationInformer,
1✔
99
                clientTLSConfig:   clientTLSConfig,
1✔
100
                serverTLSConfig:   serverTLSConfig,
1✔
101
                timeout:           defaultTimeout,
1✔
102
                bindAddress:       bindAddress,
1✔
103
                bindPort:          bindPort,
1✔
104
                client:            client,
1✔
105
        }
1✔
106

1✔
107
        queue := workqueue.NewTypedRateLimitingQueueWithConfig[string](
1✔
108
                workqueue.DefaultTypedControllerRateLimiter[string](),
1✔
109
                workqueue.TypedRateLimitingQueueConfig[string]{Name: "sync-vmi-status"},
1✔
110
        )
1✔
111
        syncController.queue = queue
1✔
112

1✔
113
        syncController.hasSynced = func() bool {
1✔
NEW
114
                return vmiInformer.HasSynced()
×
NEW
115
        }
×
116

117
        syncController.syncOutboundConnectionMap = &sync.Map{}
1✔
118
        syncController.syncReceivingConnectionMap = &sync.Map{}
1✔
119

1✔
120
        _, err := vmiInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
1✔
121
                AddFunc:    syncController.addVmiFunc,
1✔
122
                DeleteFunc: syncController.deleteVmiFunc,
1✔
123
                UpdateFunc: syncController.updateVmiFunc,
1✔
124
        })
1✔
125
        if err != nil {
1✔
NEW
126
                return nil, err
×
NEW
127
        }
×
128

129
        if err := syncController.migrationInformer.AddIndexers(map[string]cache.IndexFunc{
1✔
130
                "byUID":               indexByMigrationUID,
1✔
131
                "byVMIName":           indexByVmiName,
1✔
132
                "byTargetMigrationID": indexByTargetMigrationID,
1✔
133
                "bySourceMigrationID": indexBySourceMigrationID,
1✔
134
        }); err != nil {
1✔
NEW
135
                return nil, err
×
NEW
136
        }
×
137

138
        if _, err := syncController.migrationInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
1✔
139
                AddFunc:    syncController.addMigrationFunc,
1✔
140
                DeleteFunc: syncController.deleteMigrationFunc,
1✔
141
                UpdateFunc: syncController.updateMigrationFunc,
1✔
142
        }); err != nil {
1✔
NEW
143
                return nil, err
×
NEW
144
        }
×
145

146
        syncController.grpcServer = grpc.NewServer(grpc.Creds(credentials.NewTLS(serverTLSConfig)))
1✔
147
        syncv1.RegisterSynchronizeServer(syncController.grpcServer, syncController)
1✔
148

1✔
149
        return syncController, nil
1✔
150
}
151

NEW
152
func (s *SynchronizationController) addVmiFunc(addObj interface{}) {
×
NEW
153
        s.enqueueVirtualMachineInstance(addObj)
×
NEW
154
}
×
155

NEW
156
func (s *SynchronizationController) deleteVmiFunc(addObj interface{}) {
×
NEW
157
        s.enqueueVirtualMachineInstance(addObj)
×
NEW
158
}
×
159

NEW
160
func (s *SynchronizationController) updateVmiFunc(_, curr interface{}) {
×
NEW
161
        s.enqueueVirtualMachineInstance(curr)
×
NEW
162
}
×
163

NEW
164
func (s *SynchronizationController) enqueueVirtualMachineInstance(obj interface{}) {
×
NEW
165
        vmi, ok := obj.(*virtv1.VirtualMachineInstance)
×
NEW
166
        if ok {
×
NEW
167
                key, err := controller.KeyFunc(vmi)
×
NEW
168
                if err != nil {
×
NEW
169
                        log.Log.Object(vmi).Reason(err).Error("failed to extract key from virtualmachine.")
×
NEW
170
                        return
×
NEW
171
                }
×
NEW
172
                s.queue.Add(key)
×
173
        }
174
}
175

NEW
176
func (s *SynchronizationController) addMigrationFunc(addObj interface{}) {
×
NEW
177
        s.enqueueVirtualMachineInstanceFromMigration(addObj)
×
NEW
178
}
×
179

NEW
180
func (s *SynchronizationController) deleteMigrationFunc(delObj interface{}) {
×
NEW
181
        // Clean up any synchronization connections in the map.
×
NEW
182
        s.enqueueVirtualMachineInstanceFromMigration(delObj)
×
NEW
183
        // Close any connections associated with this migration.
×
NEW
184
        migration, ok := delObj.(*virtv1.VirtualMachineInstanceMigration)
×
NEW
185
        if ok {
×
NEW
186
                if !migration.IsDecentralized() {
×
NEW
187
                        return
×
NEW
188
                }
×
NEW
189
                if migration.Spec.Receive != nil {
×
NEW
190
                        s.closeConnectionForMigrationID(s.syncReceivingConnectionMap, migration.Spec.Receive.MigrationID)
×
NEW
191
                } else if migration.Spec.SendTo != nil {
×
NEW
192
                        s.closeConnectionForMigrationID(s.syncOutboundConnectionMap, migration.Spec.SendTo.MigrationID)
×
NEW
193
                }
×
194
        }
195
}
196

NEW
197
func (s *SynchronizationController) closeConnectionForMigrationID(syncMap *sync.Map, migrationID string) {
×
NEW
198
        obj, loaded := syncMap.LoadAndDelete(migrationID)
×
NEW
199
        if loaded {
×
NEW
200
                log.Log.V(4).Infof("closing connection associated with migrationID %s", migrationID)
×
NEW
201
                outboundConnection, ok := obj.(*SynchronizationConnection)
×
NEW
202
                if ok {
×
NEW
203
                        if err := outboundConnection.Close(); err != nil {
×
NEW
204
                                log.Log.Warningf("unable to close connection for migrationID %s, %v", migrationID, err)
×
NEW
205
                        }
×
NEW
206
                } else {
×
NEW
207
                        log.Log.Warningf("unable to close connection for migrationID %s, type is %v", migrationID, obj)
×
NEW
208
                }
×
209
        }
210
}
211

NEW
212
func (s *SynchronizationController) updateMigrationFunc(_, curr interface{}) {
×
NEW
213
        s.enqueueVirtualMachineInstanceFromMigration(curr)
×
NEW
214
}
×
215

NEW
216
func (s *SynchronizationController) enqueueVirtualMachineInstanceFromMigration(obj interface{}) {
×
NEW
217
        migration, ok := obj.(*virtv1.VirtualMachineInstanceMigration)
×
NEW
218
        if ok {
×
NEW
219
                key := controller.NamespacedKey(migration.Namespace, migration.Spec.VMIName)
×
NEW
220
                s.queue.Add(key)
×
NEW
221
        }
×
222
}
223

NEW
224
func (s *SynchronizationController) Run(threadiness int, stopCh <-chan struct{}) error {
×
NEW
225
        defer controller.HandlePanic()
×
NEW
226
        defer s.queue.ShutDown()
×
NEW
227
        defer s.closeConnections()
×
NEW
228

×
NEW
229
        log.Log.Info("starting vmi status synchronization controller.")
×
NEW
230

×
NEW
231
        // Wait for cache sync before we start the pod controller
×
NEW
232
        cache.WaitForCacheSync(stopCh, s.hasSynced)
×
NEW
233

×
NEW
234
        // Start the actual work
×
NEW
235
        for i := 0; i < threadiness; i++ {
×
NEW
236
                go wait.Until(s.runWorker, time.Second, stopCh)
×
NEW
237
        }
×
238

NEW
239
        conn, err := s.createTcpListener()
×
NEW
240
        if err != nil {
×
NEW
241
                log.Log.Criticalf("received error %v, exiting", err)
×
NEW
242
                return err
×
NEW
243
        } else {
×
NEW
244
                go func() {
×
NEW
245
                        s.grpcServer.Serve(conn)
×
NEW
246
                }()
×
247
        }
248

NEW
249
        log.Log.Info("waiting on stop signal")
×
NEW
250
        <-stopCh
×
NEW
251
        log.Log.Info("normally stopping vmi status synchronization controller.")
×
NEW
252
        return nil
×
253
}
254

255
func (s *SynchronizationController) closeConnections() {
1✔
256
        log.Log.V(1).Info("closing listener and grpcserver")
1✔
257
        if s.listener != nil {
2✔
258
                s.listener.Close()
1✔
259
        }
1✔
260
        if s.grpcServer != nil {
2✔
261
                s.grpcServer.GracefulStop()
1✔
262
        }
1✔
263
        log.Log.V(1).Infof("closing outbound connections")
1✔
264
        s.syncOutboundConnectionMap.Range(closeMapConnections)
1✔
265
        log.Log.V(1).Infof("closing inbound connections")
1✔
266
        s.syncReceivingConnectionMap.Range(closeMapConnections)
1✔
267
}
268

269
func closeMapConnections(k, obj interface{}) bool {
1✔
270
        outboundConnection, ok := obj.(*SynchronizationConnection)
1✔
271
        if ok {
2✔
272
                log.Log.V(1).Infof("closing connection for migration ID: %s", outboundConnection.migrationID)
1✔
273
                if err := outboundConnection.Close(); err != nil {
1✔
NEW
274
                        log.Log.Warningf("unable to close connection for VMI %s during shutdown, %v", k, err)
×
NEW
275
                }
×
NEW
276
        } else {
×
NEW
277
                log.Log.Warningf("unable to close connection for VMI %s during shutdown", k)
×
NEW
278
        }
×
279
        return true
1✔
280
}
281

NEW
282
func (s *SynchronizationController) runWorker() {
×
NEW
283
        for s.Execute() {
×
NEW
284
        }
×
285
}
286

287
func (s *SynchronizationController) Execute() bool {
1✔
288
        key, quit := s.queue.Get()
1✔
289
        if quit {
1✔
NEW
290
                return false
×
NEW
291
        }
×
292

293
        defer s.queue.Done(key)
1✔
294
        err := s.execute(key)
1✔
295

1✔
296
        if err != nil {
1✔
NEW
297
                log.Log.Reason(err).Infof("reenqueuing VirtualMachineInstance %v", key)
×
NEW
298
                s.queue.AddRateLimited(key)
×
299
        } else {
1✔
300
                log.Log.V(4).Infof("processed VirtualMachineInstance %v", key)
1✔
301
                s.queue.Forget(key)
1✔
302
        }
1✔
303
        return true
1✔
304
}
305

306
func (s *SynchronizationController) execute(key string) error {
1✔
307
        // Fetch the latest VMI state from cache
1✔
308
        obj, exists, _ := s.vmiInformer.GetStore().GetByKey(key)
1✔
309
        if !exists {
1✔
NEW
310
                return nil
×
NEW
311
        }
×
312
        vmi := obj.(*virtv1.VirtualMachineInstance)
1✔
313

1✔
314
        migration, err := s.getMigrationForVMI(vmi)
1✔
315
        if err != nil {
1✔
NEW
316
                return err
×
NEW
317
        }
×
318
        if migration != nil && migration.IsDecentralized() {
2✔
319
                if migration.IsSource() {
2✔
320
                        if err := s.handleSourceState(vmi); err != nil {
1✔
NEW
321
                                return err
×
NEW
322
                        }
×
323
                }
324
                if migration.IsTarget() {
2✔
325
                        return s.handleTargetState(vmi)
1✔
326
                }
1✔
327
                return nil
1✔
328
        } else {
1✔
329
                // No migration found don't do anything
1✔
330
                log.Log.Object(vmi).V(4).Info("no decentralized migration found for VMI")
1✔
331
                return nil
1✔
332
        }
1✔
333
}
334

335
func (s *SynchronizationController) getMigrationIDFromUID(migrationUID types.UID) (string, error) {
1✔
336
        objs, err := s.migrationInformer.GetIndexer().ByIndex("byUID", string(migrationUID))
1✔
337
        if err != nil {
1✔
NEW
338
                return "", err
×
NEW
339
        }
×
340
        if len(objs) > 1 {
1✔
NEW
341
                return "", fmt.Errorf("found more than one migration with same UID")
×
NEW
342
        }
×
343
        if len(objs) == 0 {
1✔
NEW
344
                return "", nil
×
NEW
345
        }
×
346
        migration, ok := objs[0].(*virtv1.VirtualMachineInstanceMigration)
1✔
347
        if !ok {
1✔
NEW
348
                return "", fmt.Errorf("found unknown object in migration cache")
×
NEW
349
        }
×
350
        var migrationID string
1✔
351
        if migration.Spec.Receive != nil {
2✔
352
                migrationID = migration.Spec.Receive.MigrationID
1✔
353
        }
1✔
354
        if migration.Spec.SendTo != nil {
2✔
355
                migrationID = migration.Spec.SendTo.MigrationID
1✔
356
        }
1✔
357
        return migrationID, nil
1✔
358
}
359

360
func (s *SynchronizationController) getOutboundSourceConnection(vmi *virtv1.VirtualMachineInstance, migrationState *virtv1.VirtualMachineInstanceMigrationState) (*SynchronizationConnection, error) {
1✔
361
        if migrationState.TargetState.SyncAddress == nil || *migrationState.TargetState.SyncAddress == "" {
1✔
NEW
362
                return nil, nil
×
NEW
363
        }
×
364
        return s.getOutboundConnection(vmi, migrationState.SourceState.MigrationUID, *migrationState.TargetState.SyncAddress, s.syncOutboundConnectionMap)
1✔
365
}
366

367
func (s *SynchronizationController) getOutboundTargetConnection(vmi *virtv1.VirtualMachineInstance, migrationState *virtv1.VirtualMachineInstanceMigrationState) (*SynchronizationConnection, error) {
1✔
368
        if migrationState.SourceState.SyncAddress == nil || *migrationState.SourceState.SyncAddress == "" {
1✔
NEW
369
                return nil, nil
×
NEW
370
        }
×
371
        return s.getOutboundConnection(vmi, migrationState.TargetState.MigrationUID, *migrationState.SourceState.SyncAddress, s.syncReceivingConnectionMap)
1✔
372
}
373

374
func (s *SynchronizationController) getOutboundConnection(vmi *virtv1.VirtualMachineInstance, migrationUID types.UID, syncAddress string, connectionMap *sync.Map) (*SynchronizationConnection, error) {
1✔
375
        if migrationUID == "" {
1✔
NEW
376
                return nil, nil
×
NEW
377
        }
×
378
        migrationID, err := s.getMigrationIDFromUID(migrationUID)
1✔
379
        if err != nil {
1✔
NEW
380
                return nil, err
×
NEW
381
        }
×
382
        log.Log.Object(vmi).V(4).Infof("found migration ID %s", migrationID)
1✔
383
        obj, ok := connectionMap.Load(migrationID)
1✔
384
        if !ok {
2✔
385
                grpcClientConnection, err := s.createOutboundConnection(syncAddress)
1✔
386
                if err != nil {
1✔
NEW
387
                        return nil, err
×
NEW
388
                }
×
389
                conn := &SynchronizationConnection{
1✔
390
                        migrationID:          migrationID,
1✔
391
                        grpcClientConnection: grpcClientConnection,
1✔
392
                }
1✔
393
                connectionMap.Store(migrationID, conn)
1✔
394
                return conn, nil
1✔
395
        }
396
        outboundSyncConnection, ok := obj.(*SynchronizationConnection)
1✔
397
        if !ok {
1✔
NEW
398
                return nil, fmt.Errorf("found unknown object in outbound connection cache %#v", outboundSyncConnection)
×
NEW
399
        }
×
400
        return outboundSyncConnection, nil
1✔
401
}
402

403
func (s *SynchronizationController) handleSourceState(vmi *virtv1.VirtualMachineInstance) error {
1✔
404
        var outboundConnection *SynchronizationConnection
1✔
405
        var err error
1✔
406
        if vmi.Status.MigrationState == nil {
2✔
407
                // No migration state, don't do anything
1✔
408
                return nil
1✔
409
        }
1✔
410
        if vmi.Status.MigrationState.SourceState == nil || vmi.Status.MigrationState.TargetState == nil {
2✔
411
                // No migration state, don't do anything
1✔
412
                return nil
1✔
413
        }
1✔
414
        sourceState := vmi.Status.MigrationState.SourceState
1✔
415
        if sourceState.SyncAddress == nil || *sourceState.SyncAddress == "" {
2✔
416
                syncAddress, err := s.getMyURL()
1✔
417
                if err != nil {
1✔
NEW
418
                        return err
×
NEW
419
                }
×
420
                sourceState.SyncAddress = &syncAddress
1✔
421
        }
422
        targetState := vmi.Status.MigrationState.TargetState
1✔
423
        if targetState.SyncAddress != nil && sourceState.MigrationUID != "" {
2✔
424
                if outboundConnection, err = s.getOutboundSourceConnection(vmi, vmi.Status.MigrationState); err != nil {
1✔
NEW
425
                        return err
×
NEW
426
                }
×
427
        }
428
        if outboundConnection == nil {
1✔
NEW
429
                log.Log.Object(vmi).V(4).Info("no synchronization connection found for source, doing nothing")
×
NEW
430
                return nil
×
NEW
431
        }
×
432
        vmiStatusJson, err := json.Marshal(vmi.Status)
1✔
433
        if err != nil {
1✔
NEW
434
                return err
×
NEW
435
        }
×
436
        client := syncv1.NewSynchronizeClient(outboundConnection.grpcClientConnection)
1✔
437
        ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.timeout)*time.Second)
1✔
438
        defer cancel()
1✔
439

1✔
440
        if _, err := client.SyncSourceMigrationStatus(ctx, &syncv1.VMIStatusRequest{
1✔
441
                MigrationID: outboundConnection.migrationID,
1✔
442
                VmiStatus: &syncv1.VMIStatus{
1✔
443
                        VmiStatusJson: vmiStatusJson,
1✔
444
                },
1✔
445
        }); err != nil {
2✔
446
                return err
1✔
447
        }
1✔
448
        return nil
1✔
449
}
450

451
func (s *SynchronizationController) handleTargetState(vmi *virtv1.VirtualMachineInstance) error {
1✔
452
        if vmi.Status.MigrationState == nil {
2✔
453
                // No migration state, don't do anything
1✔
454
                return nil
1✔
455
        }
1✔
456
        if vmi.Status.MigrationState.TargetState == nil || vmi.Status.MigrationState.SourceState == nil {
2✔
457
                // No migration state, don't do anything
1✔
458
                return nil
1✔
459
        }
1✔
460

461
        var outboundConnection *SynchronizationConnection
1✔
462
        var err error
1✔
463
        sourceState := vmi.Status.MigrationState.SourceState
1✔
464
        targetState := vmi.Status.MigrationState.TargetState
1✔
465
        if targetState.SyncAddress == nil || *targetState.SyncAddress == "" {
2✔
466
                syncAddress, err := s.getMyURL()
1✔
467
                if err != nil {
1✔
NEW
468
                        return err
×
NEW
469
                }
×
470
                targetState.SyncAddress = &syncAddress
1✔
471
        }
472

473
        if sourceState.SyncAddress != nil && targetState.MigrationUID != "" {
2✔
474
                if outboundConnection, err = s.getOutboundTargetConnection(vmi, vmi.Status.MigrationState); err != nil {
1✔
NEW
475
                        return err
×
NEW
476
                }
×
477
        }
478
        if outboundConnection == nil {
1✔
NEW
479
                log.Log.Object(vmi).V(4).Info("no synchronization connection found for target, doing nothing")
×
NEW
480
                return nil
×
NEW
481
        }
×
482
        vmiStatusJson, err := json.Marshal(vmi.Status)
1✔
483
        if err != nil {
1✔
NEW
484
                return err
×
NEW
485
        }
×
486
        client := syncv1.NewSynchronizeClient(outboundConnection.grpcClientConnection)
1✔
487
        ctx, cancel := context.WithTimeout(context.Background(), time.Duration(s.timeout)*time.Second)
1✔
488
        defer cancel()
1✔
489

1✔
490
        _, err = client.SyncTargetMigrationStatus(ctx, &syncv1.VMIStatusRequest{
1✔
491
                MigrationID: outboundConnection.migrationID,
1✔
492
                VmiStatus: &syncv1.VMIStatus{
1✔
493
                        VmiStatusJson: vmiStatusJson,
1✔
494
                },
1✔
495
        })
1✔
496
        return err
1✔
497
}
498

499
func (s *SynchronizationController) getMigrationForVMI(vmi *virtv1.VirtualMachineInstance) (*virtv1.VirtualMachineInstanceMigration, error) {
1✔
500
        objects, err := s.migrationInformer.GetIndexer().ByIndex("byVMIName", vmi.Name)
1✔
501
        if err != nil {
1✔
NEW
502
                return nil, err
×
NEW
503
        }
×
504
        if len(objects) > 0 {
2✔
505
                count := 0
1✔
506
                var res *virtv1.VirtualMachineInstanceMigration
1✔
507
                for _, migrationObj := range objects {
2✔
508
                        migration, ok := migrationObj.(*virtv1.VirtualMachineInstanceMigration)
1✔
509
                        if !ok {
1✔
NEW
510
                                return nil, fmt.Errorf("not a virtual machine instance migration")
×
NEW
511
                        }
×
512
                        if migration.Namespace == vmi.Namespace {
2✔
513
                                count++
1✔
514
                                res = migration
1✔
515
                        }
1✔
516
                }
517
                if count > 1 {
1✔
NEW
518
                        return nil, fmt.Errorf("found more than one migration pointing to same VMI")
×
519
                } else if count == 0 {
1✔
NEW
520
                        return nil, nil
×
NEW
521
                }
×
522
                return res, nil
1✔
523
        }
524
        return nil, nil
1✔
525
}
526

527
func (s *SynchronizationController) getMyURL() (string, error) {
1✔
528
        myIp := os.Getenv(MyPodIP)
1✔
529
        if myIp != "" {
1✔
NEW
530
                names, err := net.LookupAddr(myIp)
×
NEW
531
                if err != nil {
×
NEW
532
                        return "", err
×
NEW
533
                }
×
NEW
534
                for _, name := range names {
×
NEW
535
                        log.Log.V(4).Infof("found DNS name for my IP address: %s", name)
×
NEW
536
                        return fmt.Sprintf("%s:%d", name, s.bindPort), nil
×
NEW
537
                }
×
NEW
538
                return fmt.Sprintf("%s:%d", myIp, s.bindPort), nil
×
539
        }
540
        if s.listener == nil {
1✔
NEW
541
                return fmt.Sprintf("%s:%d", s.bindAddress, s.bindPort), nil
×
NEW
542
        }
×
543
        // TODO figure out how to get my URL with or without submariner (url changes based on export)
544
        return s.listener.Addr().String(), nil
1✔
545
}
546

547
func (s *SynchronizationController) createOutboundConnection(connectionURL string) (*grpc.ClientConn, error) {
1✔
548
        logger := log.Log.With("outbound", connectionURL)
1✔
549
        logger.Info("creating new synchronization grpc connection")
1✔
550

1✔
551
        client, err := grpc.NewClient(connectionURL, grpc.WithTransportCredentials(credentials.NewTLS(s.clientTLSConfig)))
1✔
552
        return client, err
1✔
553
}
1✔
554

555
func (s *SynchronizationController) createTcpListener() (net.Listener, error) {
1✔
556
        if s.listener != nil {
1✔
NEW
557
                return s.listener, nil
×
NEW
558
        }
×
559
        var ln net.Listener
1✔
560
        var err error
1✔
561
        addr := net.JoinHostPort(s.bindAddress, strconv.Itoa(s.bindPort))
1✔
562
        ln, err = net.Listen("tcp", addr)
1✔
563
        if err != nil {
1✔
NEW
564
                log.Log.Reason(err).Error("failed to create tcp listener")
×
NEW
565
                return nil, err
×
NEW
566
        }
×
567
        s.listener = ln
1✔
568
        return ln, nil
1✔
569
}
570

571
func (s *SynchronizationController) findTargetMigrationFromMigrationID(migrationID string) (*virtv1.VirtualMachineInstanceMigration, error) {
1✔
572
        return s.findMigrationFromMigrationIDByIndex("byTargetMigrationID", migrationID)
1✔
573
}
1✔
574

575
func (s *SynchronizationController) findSourceMigrationFromMigrationID(migrationID string) (*virtv1.VirtualMachineInstanceMigration, error) {
1✔
576
        return s.findMigrationFromMigrationIDByIndex("bySourceMigrationID", migrationID)
1✔
577
}
1✔
578

579
func (s *SynchronizationController) findMigrationFromMigrationIDByIndex(indexName, migrationID string) (*virtv1.VirtualMachineInstanceMigration, error) {
1✔
580
        objs, err := s.migrationInformer.GetIndexer().ByIndex(indexName, migrationID)
1✔
581
        if err != nil {
1✔
NEW
582
                return nil, err
×
NEW
583
        }
×
584
        if len(objs) > 1 {
1✔
NEW
585
                log.Log.Warningf("found multiple migrations for migrationID %s, picking first one", migrationID)
×
NEW
586
        }
×
587
        for _, obj := range objs {
2✔
588
                migration, _ := obj.(*virtv1.VirtualMachineInstanceMigration)
1✔
589
                return migration, nil
1✔
590
        }
1✔
591
        return nil, nil
1✔
592
}
593

594
func (s *SynchronizationController) SyncSourceMigrationStatus(ctx context.Context, request *syncv1.VMIStatusRequest) (*syncv1.VMIStatusResponse, error) {
1✔
595
        if request.VmiStatus == nil || len(request.VmiStatus.VmiStatusJson) == 0 {
2✔
596
                return &syncv1.VMIStatusResponse{
1✔
597
                        Message: noSourceStatusErrorMsg,
1✔
598
                }, fmt.Errorf(noSourceStatusErrorMsg)
1✔
599
        }
1✔
600
        migration, err := s.findTargetMigrationFromMigrationID(request.MigrationID)
1✔
601
        if migration == nil {
2✔
602
                return &syncv1.VMIStatusResponse{
1✔
603
                        Message: fmt.Sprintf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID),
1✔
604
                }, fmt.Errorf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID)
1✔
605
        }
1✔
606
        key := controller.NamespacedKey(migration.Namespace, migration.Spec.VMIName)
1✔
607
        log.Log.Object(migration).V(5).Infof("looking up VMI %s", key)
1✔
608
        obj, exists, err := s.vmiInformer.GetStore().GetByKey(key)
1✔
609
        if err != nil || !exists {
2✔
610
                if err == nil {
2✔
611
                        err = fmt.Errorf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID)
1✔
612
                }
1✔
613
                return &syncv1.VMIStatusResponse{
1✔
614
                        Message: fmt.Sprintf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID),
1✔
615
                }, err
1✔
616
        }
617
        vmi := obj.(*virtv1.VirtualMachineInstance)
1✔
618

1✔
619
        remoteStatus := &virtv1.VirtualMachineInstanceStatus{}
1✔
620
        if err := json.Unmarshal(request.VmiStatus.VmiStatusJson, remoteStatus); err != nil {
2✔
621
                return &syncv1.VMIStatusResponse{
1✔
622
                        Message: fmt.Sprintf("unable to unmarshal vmistatus for migrationID %s", request.MigrationID),
1✔
623
                }, err
1✔
624
        }
1✔
625
        if remoteStatus.MigrationState == nil {
2✔
626
                return &syncv1.VMIStatusResponse{
1✔
627
                        Message: noSourceStatusErrorMsg,
1✔
628
                }, fmt.Errorf(noSourceStatusErrorMsg)
1✔
629
        }
1✔
630
        origVMI := vmi.DeepCopy()
1✔
631
        if vmi.Status.MigrationState == nil {
2✔
632
                vmi.Status.MigrationState = &virtv1.VirtualMachineInstanceMigrationState{}
1✔
633
        }
1✔
634
        log.Log.Object(vmi).V(5).Infof("vmi migration source state: %#v", vmi.Status.MigrationState.SourceState)
1✔
635
        log.Log.Object(vmi).V(5).Infof("remote migration source state: %#v", remoteStatus.MigrationState.SourceState)
1✔
636
        vmi.Status.MigrationState.SourceState = remoteStatus.MigrationState.SourceState.DeepCopy()
1✔
637
        if !apiequality.Semantic.DeepEqual(origVMI.Status, vmi.Status) {
2✔
638
                if err := s.patchVMI(origVMI, vmi); err != nil {
2✔
639
                        return &syncv1.VMIStatusResponse{
1✔
640
                                Message: fmt.Sprintf("unable to synchronize VMI for migrationID %s", request.MigrationID),
1✔
641
                        }, err
1✔
642
                }
1✔
643
                log.Log.Object(vmi).With("MigrationID", request.MigrationID).V(5).Info("successfully patched VMI with source state")
1✔
644
        }
645
        log.Log.Object(vmi).V(5).Info("returning success to grpc caller, source")
1✔
646
        return &syncv1.VMIStatusResponse{
1✔
647
                Message: successMessage,
1✔
648
        }, nil
1✔
649
}
650

651
func (s *SynchronizationController) SyncTargetMigrationStatus(ctx context.Context, request *syncv1.VMIStatusRequest) (*syncv1.VMIStatusResponse, error) {
1✔
652
        if request.VmiStatus == nil || len(request.VmiStatus.VmiStatusJson) == 0 {
2✔
653
                return &syncv1.VMIStatusResponse{
1✔
654
                        Message: noTargetStatusErrorMsg,
1✔
655
                }, fmt.Errorf(noTargetStatusErrorMsg)
1✔
656
        }
1✔
657

658
        migration, err := s.findSourceMigrationFromMigrationID(request.MigrationID)
1✔
659
        if migration == nil {
2✔
660
                return &syncv1.VMIStatusResponse{
1✔
661
                        Message: fmt.Sprintf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID),
1✔
662
                }, fmt.Errorf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID)
1✔
663
        }
1✔
664

665
        key := controller.NamespacedKey(migration.Namespace, migration.Spec.VMIName)
1✔
666
        obj, exists, err := s.vmiInformer.GetStore().GetByKey(key)
1✔
667
        if err != nil || !exists {
2✔
668
                if err == nil {
2✔
669
                        err = fmt.Errorf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID)
1✔
670
                }
1✔
671
                return &syncv1.VMIStatusResponse{
1✔
672
                        Message: fmt.Sprintf(unableToLocateVMIMigrationIDErrorMsg, request.MigrationID),
1✔
673
                }, err
1✔
674
        }
675
        vmi := obj.(*virtv1.VirtualMachineInstance)
1✔
676

1✔
677
        remoteStatus := &virtv1.VirtualMachineInstanceStatus{}
1✔
678
        if err := json.Unmarshal(request.VmiStatus.VmiStatusJson, remoteStatus); err != nil {
2✔
679
                return &syncv1.VMIStatusResponse{
1✔
680
                        Message: fmt.Sprintf("unable to unmarshal vmistatus for migrationID %s", request.MigrationID),
1✔
681
                }, err
1✔
682
        }
1✔
683
        if remoteStatus.MigrationState == nil {
2✔
684
                return &syncv1.VMIStatusResponse{
1✔
685
                        Message: noTargetStatusErrorMsg,
1✔
686
                }, fmt.Errorf(noTargetStatusErrorMsg)
1✔
687
        }
1✔
688
        origVMI := vmi.DeepCopy()
1✔
689
        if vmi.Status.MigrationState == nil {
2✔
690
                vmi.Status.MigrationState = &virtv1.VirtualMachineInstanceMigrationState{}
1✔
691
        }
1✔
692

693
        log.Log.Object(vmi).V(5).Infof("vmi migration target state: %#v", vmi.Status.MigrationState.TargetState)
1✔
694
        log.Log.Object(vmi).V(5).Infof("remote migration target state: %#v", remoteStatus.MigrationState.TargetState)
1✔
695
        vmi.Status.MigrationState.TargetState = remoteStatus.MigrationState.TargetState.DeepCopy()
1✔
696
        if !apiequality.Semantic.DeepEqual(origVMI.Status, vmi.Status) {
2✔
697
                if err := s.patchVMI(origVMI, vmi); err != nil {
2✔
698
                        return &syncv1.VMIStatusResponse{
1✔
699
                                Message: fmt.Sprintf("unable to synchronize VMI for migrationID %s", request.MigrationID),
1✔
700
                        }, err
1✔
701
                }
1✔
702
                log.Log.Object(vmi).With("MigrationID", request.MigrationID).V(5).Info("successfully patched VMI with target state")
1✔
703
        }
704
        log.Log.Object(vmi).V(5).Info("returning success to grpc caller, target")
1✔
705
        return &syncv1.VMIStatusResponse{
1✔
706
                Message: successMessage,
1✔
707
        }, nil
1✔
708
}
709

710
func (s *SynchronizationController) patchVMI(origVMI, newVMI *virtv1.VirtualMachineInstance) error {
1✔
711
        patchSet := patch.New()
1✔
712

1✔
713
        if origVMI.Status.MigrationState == nil {
2✔
714
                patchSet.AddOption(
1✔
715
                        patch.WithAdd("/status/migrationState", newVMI.Status.MigrationState))
1✔
716
        } else {
2✔
717
                if !apiequality.Semantic.DeepEqual(origVMI.Status.MigrationState.SourceState, newVMI.Status.MigrationState.SourceState) {
2✔
718
                        if origVMI.Status.MigrationState.SourceState == nil {
2✔
719
                                patchSet.AddOption(
1✔
720
                                        patch.WithTest("/status/migrationState/sourceState", origVMI.Status.MigrationState.SourceState),
1✔
721
                                        patch.WithAdd("/status/migrationState/sourceState", newVMI.Status.MigrationState.SourceState))
1✔
722
                        } else {
2✔
723
                                patchSet.AddOption(
1✔
724
                                        patch.WithTest("/status/migrationState/sourceState", origVMI.Status.MigrationState.SourceState),
1✔
725
                                        patch.WithReplace("/status/migrationState/sourceState", newVMI.Status.MigrationState.SourceState),
1✔
726
                                )
1✔
727
                        }
1✔
728
                }
729
                if !apiequality.Semantic.DeepEqual(origVMI.Status.MigrationState.TargetState, newVMI.Status.MigrationState.TargetState) {
2✔
730
                        if origVMI.Status.MigrationState.TargetState == nil {
2✔
731
                                patchSet.AddOption(
1✔
732
                                        patch.WithTest("/status/migrationState/targetState", origVMI.Status.MigrationState.TargetState),
1✔
733
                                        patch.WithAdd("/status/migrationState/targetState", newVMI.Status.MigrationState.TargetState))
1✔
734
                        } else {
2✔
735
                                patchSet.AddOption(
1✔
736
                                        patch.WithTest("/status/migrationState/targetState", origVMI.Status.MigrationState.TargetState),
1✔
737
                                        patch.WithReplace("/status/migrationState/targetState", newVMI.Status.MigrationState.TargetState),
1✔
738
                                )
1✔
739
                        }
1✔
740
                }
741
        }
742

743
        if !patchSet.IsEmpty() {
2✔
744
                patchBytes, err := patchSet.GeneratePayload()
1✔
745
                if err != nil {
1✔
NEW
746
                        return err
×
NEW
747
                }
×
748
                log.Log.Object(origVMI).Infof("patch VMI with %s", string(patchBytes))
1✔
749
                if _, err := s.client.VirtualMachineInstance(origVMI.Namespace).Patch(context.Background(), origVMI.Name, types.JSONPatchType, patchBytes, metav1.PatchOptions{}); err != nil {
2✔
750
                        return err
1✔
751
                }
1✔
752
        }
753
        return nil
1✔
754
}
755

756
func indexByMigrationUID(obj interface{}) ([]string, error) {
1✔
757
        migration, ok := obj.(*virtv1.VirtualMachineInstanceMigration)
1✔
758
        if !ok {
1✔
NEW
759
                return nil, nil
×
NEW
760
        }
×
761
        return []string{string(migration.UID)}, nil
1✔
762
}
763

764
func indexByVmiName(obj interface{}) ([]string, error) {
1✔
765
        migration, ok := obj.(*virtv1.VirtualMachineInstanceMigration)
1✔
766
        if !ok {
1✔
NEW
767
                return nil, nil
×
NEW
768
        }
×
769
        return []string{migration.Spec.VMIName}, nil
1✔
770
}
771

772
func indexByTargetMigrationID(obj interface{}) ([]string, error) {
1✔
773
        migration, ok := obj.(*virtv1.VirtualMachineInstanceMigration)
1✔
774
        if !ok {
1✔
NEW
775
                return nil, nil
×
NEW
776
        }
×
777
        if migration.Spec.Receive != nil {
2✔
778
                return []string{migration.Spec.Receive.MigrationID}, nil
1✔
779
        }
1✔
780
        return []string{}, nil
1✔
781
}
782

783
func indexBySourceMigrationID(obj interface{}) ([]string, error) {
1✔
784
        migration, ok := obj.(*virtv1.VirtualMachineInstanceMigration)
1✔
785
        if !ok {
1✔
NEW
786
                return nil, nil
×
NEW
787
        }
×
788
        if migration.Spec.SendTo != nil {
2✔
789
                return []string{migration.Spec.SendTo.MigrationID}, nil
1✔
790
        }
1✔
791
        return []string{}, nil
1✔
792
}
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