@@ -377,6 +377,99 @@ func (d *qemu) getAgentClient() (*http.Client, error) {
377
377
return agent , nil
378
378
}
379
379
380
+ // getClusterCPUFlags identifies CPU flags that are consistently present across all
381
+ // CPU cores in the cluster.
382
+ //
383
+ // 1. Retrieving all nodes in the cluster and their CPU information.
384
+ // 2. Counting which flags are present on every core across all nodes.
385
+ // 3. Building a set of flags common to all cores.
386
+ //
387
+ // This is useful in a migration scenario where we need to find a minimum common denominator
388
+ // of CPU flags to properly start a VM on a different node in a cluster.
389
+ func (d * qemu ) getClusterCPUFlags () ([]string , error ) {
390
+ // Get the list of cluster members.
391
+ var nodes []db.RaftNode
392
+ err := d .state .DB .Node .Transaction (context .TODO (), func (ctx context.Context , tx * db.NodeTx ) error {
393
+ var err error
394
+ nodes , err = tx .GetRaftNodes (ctx )
395
+ return err
396
+ })
397
+ if err != nil {
398
+ return nil , err
399
+ }
400
+
401
+ // Get architecture name.
402
+ arch , err := osarch .ArchitectureName (d .architecture )
403
+ if err != nil {
404
+ return nil , err
405
+ }
406
+
407
+ // Get all the CPU flags for the architecture.
408
+ flagMembers := map [string ]int {}
409
+ coreCount := 0
410
+
411
+ for _ , node := range nodes {
412
+ // Attempt to load the cached resources.
413
+ resourcesPath := internalUtil .CachePath ("resources" , fmt .Sprintf ("%s.yaml" , node .Name ))
414
+
415
+ data , err := os .ReadFile (resourcesPath )
416
+ if err != nil {
417
+ if os .IsNotExist (err ) {
418
+ continue
419
+ }
420
+
421
+ return nil , err
422
+ }
423
+
424
+ res := api.Resources {}
425
+ err = json .Unmarshal (data , & res )
426
+ if err != nil {
427
+ return nil , err
428
+ }
429
+
430
+ // Skip if not the correct architecture.
431
+ if res .CPU .Architecture != arch {
432
+ continue
433
+ }
434
+
435
+ // Add the CPU flags to the map.
436
+ for _ , socket := range res .CPU .Sockets {
437
+ for _ , core := range socket .Cores {
438
+ coreCount += 1
439
+ for _ , flag := range core .Flags {
440
+ flagMembers [flag ] += 1
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ // Get the host flags.
447
+ info := DriverStatuses ()[instancetype .VM ].Info
448
+ hostFlags , ok := info .Features ["flags" ].(map [string ]bool )
449
+ if ! ok {
450
+ // No CPU flags found.
451
+ return nil , nil
452
+ }
453
+
454
+ // Build a set of flags common to all cores.
455
+ flags := []string {}
456
+
457
+ for k , v := range flagMembers {
458
+ if v != coreCount {
459
+ continue
460
+ }
461
+
462
+ hostVal , ok := hostFlags [k ]
463
+ if ! ok || hostVal {
464
+ continue
465
+ }
466
+
467
+ flags = append (flags , k )
468
+ }
469
+
470
+ return flags , nil
471
+ }
472
+
380
473
func (d * qemu ) getMonitorEventHandler () func (event string , data map [string ]any ) {
381
474
// Create local variables from instance properties we need so as not to keep references to instance around
382
475
// after we have returned the callback function.
@@ -1480,6 +1573,19 @@ func (d *qemu) start(stateful bool, op *operationlock.InstanceOperation) error {
1480
1573
}
1481
1574
1482
1575
cpuType := "host"
1576
+
1577
+ // Get CPU flags if clustered and migration is enabled.
1578
+ if d .state .ServerClustered && util .IsTrue (d .expandedConfig ["migration.stateful" ]) {
1579
+ cpuFlags , err := d .getClusterCPUFlags ()
1580
+ if err != nil {
1581
+ op .Done (err )
1582
+ return err
1583
+ }
1584
+
1585
+ cpuType = "kvm64"
1586
+ cpuExtensions = append (cpuExtensions , cpuFlags ... )
1587
+ }
1588
+
1483
1589
if len (cpuExtensions ) > 0 {
1484
1590
cpuType += "," + strings .Join (cpuExtensions , "," )
1485
1591
}
@@ -8929,6 +9035,24 @@ func (d *qemu) checkFeatures(hostArch int, qemuPath string) (map[string]any, err
8929
9035
features ["vhost_net" ] = struct {}{}
8930
9036
}
8931
9037
9038
+ // Get the host CPU model.
9039
+ model , err := monitor .QueryCPUModel ("kvm64" )
9040
+ if err != nil {
9041
+ return nil , err
9042
+ }
9043
+
9044
+ cpuFlags := map [string ]bool {}
9045
+ for k , v := range model .Flags {
9046
+ value , ok := v .(bool )
9047
+ if ! ok {
9048
+ continue
9049
+ }
9050
+
9051
+ cpuFlags [k ] = value
9052
+ }
9053
+
9054
+ features ["flags" ] = cpuFlags
9055
+
8932
9056
return features , nil
8933
9057
}
8934
9058
0 commit comments