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

kubevirt / kubevirt / 7af9a78d-5351-4ad9-84f5-6eb2327002ec

17 Jun 2025 11:41PM UTC coverage: 71.21% (+0.04%) from 71.175%
7af9a78d-5351-4ad9-84f5-6eb2327002ec

push

prow

web-flow
Merge pull request #14941 from vladikr/fix_sev_labels

node-labeller: SEV and realtime labels should have a value

2 of 3 new or added lines in 1 file covered. (66.67%)

118 existing lines in 7 files now uncovered.

66425 of 93280 relevant lines covered (71.21%)

0.79 hits per line

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

78.3
/pkg/virt-handler/node-labeller/node_labeller.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 nodelabeller
21

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

30
        "k8s.io/client-go/tools/record"
31
        "libvirt.org/go/libvirtxml"
32

33
        v1 "k8s.io/api/core/v1"
34
        "k8s.io/apimachinery/pkg/api/equality"
35
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36
        "k8s.io/apimachinery/pkg/types"
37
        "k8s.io/apimachinery/pkg/util/wait"
38
        k8scli "k8s.io/client-go/kubernetes/typed/core/v1"
39
        "k8s.io/client-go/util/workqueue"
40

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

44
        "kubevirt.io/kubevirt/pkg/apimachinery/patch"
45
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
46
)
47

48
var nodeLabellerLabels = []string{
49
        kubevirtv1.CPUFeatureLabel,
50
        kubevirtv1.CPUModelLabel,
51
        kubevirtv1.SupportedHostModelMigrationCPU,
52
        kubevirtv1.CPUTimerLabel,
53
        kubevirtv1.HypervLabel,
54
        kubevirtv1.RealtimeLabel,
55
        kubevirtv1.SEVLabel,
56
        kubevirtv1.SEVESLabel,
57
        kubevirtv1.HostModelCPULabel,
58
        kubevirtv1.HostModelRequiredFeaturesLabel,
59
        kubevirtv1.NodeHostModelIsObsoleteLabel,
60
        kubevirtv1.SupportedMachineTypeLabel,
61
}
62

63
// NodeLabeller struct holds information needed to run node-labeller
64
type NodeLabeller struct {
65
        recorder                record.EventRecorder
66
        nodeClient              k8scli.NodeInterface
67
        host                    string
68
        logger                  *log.FilteredLogger
69
        clusterConfig           *virtconfig.ClusterConfig
70
        hypervFeatures          supportedFeatures
71
        hostCapabilities        supportedFeatures
72
        queue                   workqueue.TypedRateLimitingInterface[string]
73
        supportedFeatures       []string
74
        cpuModelVendor          string
75
        volumePath              string
76
        domCapabilitiesFileName string
77
        cpuCounter              *libvirtxml.CapsHostCPUCounter
78
        guestCaps               []libvirtxml.CapsGuest
79
        hostCPUModel            hostCPUModel
80
        SEV                     SEVConfiguration
81
        SecureExecution         SecureExecutionConfiguration
82
        arch                    archLabeller
83
}
84

85
func NewNodeLabeller(clusterConfig *virtconfig.ClusterConfig, nodeClient k8scli.NodeInterface, host string, recorder record.EventRecorder, cpuCounter *libvirtxml.CapsHostCPUCounter, guestCaps []libvirtxml.CapsGuest) (*NodeLabeller, error) {
×
86
        return newNodeLabeller(clusterConfig, nodeClient, host, NodeLabellerVolumePath, recorder, cpuCounter, guestCaps)
×
87

×
88
}
×
89
func newNodeLabeller(clusterConfig *virtconfig.ClusterConfig, nodeClient k8scli.NodeInterface, host, volumePath string, recorder record.EventRecorder, cpuCounter *libvirtxml.CapsHostCPUCounter, guestCaps []libvirtxml.CapsGuest) (*NodeLabeller, error) {
1✔
90
        n := &NodeLabeller{
1✔
91
                recorder:      recorder,
1✔
92
                nodeClient:    nodeClient,
1✔
93
                host:          host,
1✔
94
                logger:        log.DefaultLogger(),
1✔
95
                clusterConfig: clusterConfig,
1✔
96
                queue: workqueue.NewTypedRateLimitingQueueWithConfig[string](
1✔
97
                        workqueue.DefaultTypedControllerRateLimiter[string](),
1✔
98
                        workqueue.TypedRateLimitingQueueConfig[string]{Name: "virt-handler-node-labeller"},
1✔
99
                ),
1✔
100
                volumePath:              volumePath,
1✔
101
                domCapabilitiesFileName: "virsh_domcapabilities.xml",
1✔
102
                cpuCounter:              cpuCounter,
1✔
103
                guestCaps:               guestCaps,
1✔
104
                hostCPUModel:            hostCPUModel{requiredFeatures: make(map[string]bool)},
1✔
105
                arch:                    newArchLabeller(runtime.GOARCH),
1✔
106
        }
1✔
107

1✔
108
        err := n.loadAll()
1✔
109
        if err != nil {
1✔
110
                return n, err
×
111
        }
×
112
        return n, nil
1✔
113
}
114

115
// Run runs node-labeller
116
func (n *NodeLabeller) Run(threadiness int, stop chan struct{}) {
×
117
        defer n.queue.ShutDown()
×
118

×
119
        n.logger.Infof("node-labeller is running")
×
120

×
121
        if !n.hasTSCCounter() {
×
122
                n.logger.Error("failed to get tsc cpu frequency, will continue without the tsc frequency label")
×
123
        }
×
124

125
        n.clusterConfig.SetConfigModifiedCallback(func() {
×
126
                n.queue.Add(n.host)
×
127
        })
×
128

129
        interval := 3 * time.Minute
×
130
        go wait.JitterUntil(func() { n.queue.Add(n.host) }, interval, 1.2, true, stop)
×
131

132
        for i := 0; i < threadiness; i++ {
×
133
                go wait.Until(n.runWorker, time.Second, stop)
×
134
        }
×
135
        <-stop
×
136
}
137

138
func (n *NodeLabeller) runWorker() {
×
139
        for n.execute() {
×
140
        }
×
141
}
142

143
func (n *NodeLabeller) execute() bool {
1✔
144
        key, quit := n.queue.Get()
1✔
145
        if quit {
1✔
146
                return false
×
147
        }
×
148
        defer n.queue.Done(key)
1✔
149

1✔
150
        err := n.run()
1✔
151

1✔
152
        if err != nil {
2✔
153
                n.logger.Errorf("node-labeller sync error encountered: %v", err)
1✔
154
                n.queue.AddRateLimited(key)
1✔
155
        } else {
2✔
156
                n.queue.Forget(key)
1✔
157
        }
1✔
158
        return true
1✔
159
}
160

161
func (n *NodeLabeller) loadAll() error {
1✔
162
        // host supported features is only available on AMD64 and S390X nodes.
1✔
163
        // This is because hypervisor-cpu-baseline virsh command doesnt work for ARM64 architecture.
1✔
164
        if n.arch.hasHostSupportedFeatures() {
2✔
165
                err := n.loadHostSupportedFeatures()
1✔
166
                if err != nil {
1✔
167
                        n.logger.Errorf("node-labeller could not load supported features: " + err.Error())
×
168
                        return err
×
169
                }
×
170
        }
171

172
        err := n.loadDomCapabilities()
1✔
173
        if err != nil {
1✔
174
                n.logger.Errorf("node-labeller could not load host dom capabilities: " + err.Error())
×
175
                return err
×
176
        }
×
177

178
        n.loadHypervFeatures()
1✔
179

1✔
180
        return nil
1✔
181
}
182

183
func (n *NodeLabeller) run() error {
1✔
184
        originalNode, err := n.nodeClient.Get(context.Background(), n.host, metav1.GetOptions{})
1✔
185
        if err != nil {
1✔
186
                return err
×
187
        }
×
188

189
        node := originalNode.DeepCopy()
1✔
190

1✔
191
        if !skipNodeLabelling(node) {
2✔
192
                //prepare new labels
1✔
193
                newLabels := n.prepareLabels(node)
1✔
194
                //remove old labeller labels
1✔
195
                n.removeLabellerLabels(node)
1✔
196
                //add new labels
1✔
197
                n.addLabellerLabels(node, newLabels)
1✔
198
        }
1✔
199

200
        err = n.patchNode(originalNode, node)
1✔
201

1✔
202
        return err
1✔
203
}
204

205
func skipNodeLabelling(node *v1.Node) bool {
1✔
206
        _, exists := node.Annotations[kubevirtv1.LabellerSkipNodeAnnotation]
1✔
207
        return exists
1✔
208
}
1✔
209

210
func (n *NodeLabeller) patchNode(originalNode, node *v1.Node) error {
1✔
211
        if equality.Semantic.DeepEqual(originalNode.Labels, node.Labels) {
2✔
212
                return nil
1✔
213
        }
1✔
214

215
        patchBytes, err := patch.New(
1✔
216
                patch.WithTest("/metadata/labels", originalNode.Labels),
1✔
217
                patch.WithReplace("/metadata/labels", node.Labels),
1✔
218
        ).GeneratePayload()
1✔
219

1✔
220
        if err != nil {
1✔
221
                return err
×
222
        }
×
223

224
        _, err = n.nodeClient.Patch(context.Background(), node.Name, types.JSONPatchType, patchBytes, metav1.PatchOptions{})
1✔
225
        return err
1✔
226
}
227

228
func (n *NodeLabeller) loadHypervFeatures() {
1✔
229
        n.hypervFeatures.items = getCapLabels()
1✔
230
}
1✔
231

232
// prepareLabels converts cpu models, features, hyperv features to map[string]string format
233
// e.g. "cpu-feature.node.kubevirt.io/Penryn": "true"
234
func (n *NodeLabeller) prepareLabels(node *v1.Node) map[string]string {
1✔
235
        obsoleteCPUsx86 := n.clusterConfig.GetObsoleteCPUModels()
1✔
236
        hostCpuModel := n.GetHostCpuModel()
1✔
237
        newLabels := make(map[string]string)
1✔
238

1✔
239
        if n.arch.hasHostSupportedFeatures() {
2✔
240
                for key := range n.getSupportedCpuFeatures() {
2✔
241
                        newLabels[kubevirtv1.CPUFeatureLabel+key] = "true"
1✔
242
                }
1✔
243
        }
244

245
        if n.arch.supportsNamedModels() {
2✔
246
                for _, value := range n.getSupportedCpuModels(obsoleteCPUsx86) {
2✔
247
                        newLabels[kubevirtv1.CPUModelLabel+value] = "true"
1✔
248
                        newLabels[kubevirtv1.SupportedHostModelMigrationCPU+value] = "true"
1✔
249
                }
1✔
250
        }
251

252
        for _, machine := range n.getSupportedMachines() {
2✔
253
                labelKey := kubevirtv1.SupportedMachineTypeLabel + machine.Name
1✔
254
                newLabels[labelKey] = "true"
1✔
255
        }
1✔
256

257
        for _, key := range n.hypervFeatures.items {
1✔
258
                newLabels[kubevirtv1.HypervLabel+key] = "true"
×
259
        }
×
260

261
        if n.hasTSCCounter() {
2✔
262
                newLabels[kubevirtv1.CPUTimerLabel+"tsc-frequency"] = fmt.Sprintf("%d", n.cpuCounter.Frequency)
1✔
263
                newLabels[kubevirtv1.CPUTimerLabel+"tsc-scalable"] = fmt.Sprintf("%t", n.cpuCounter.Scaling == "yes")
1✔
264
        }
1✔
265

266
        if n.arch.supportsHostModel() {
2✔
267
                if _, hostModelObsolete := obsoleteCPUsx86[hostCpuModel.Name]; !hostModelObsolete {
2✔
268
                        newLabels[kubevirtv1.SupportedHostModelMigrationCPU+hostCpuModel.Name] = "true"
1✔
269
                } else {
2✔
270
                        newLabels[kubevirtv1.NodeHostModelIsObsoleteLabel] = "true"
1✔
271
                        err := n.alertIfHostModelIsObsolete(node, hostCpuModel.Name, obsoleteCPUsx86)
1✔
272
                        if err != nil {
1✔
273
                                n.logger.Reason(err).Error(err.Error())
×
274
                        }
×
275
                }
276

277
                for feature := range hostCpuModel.requiredFeatures {
2✔
278
                        newLabels[kubevirtv1.HostModelRequiredFeaturesLabel+feature] = "true"
1✔
279
                }
1✔
280

281
                newLabels[kubevirtv1.CPUModelVendorLabel+n.cpuModelVendor] = "true"
1✔
282
                newLabels[kubevirtv1.HostModelCPULabel+hostCpuModel.Name] = "true"
1✔
283
        }
284

285
        capable, err := isNodeRealtimeCapable()
1✔
286
        if err != nil {
2✔
287
                n.logger.Reason(err).Error("failed to identify if a node is capable of running realtime workloads")
1✔
288
        }
1✔
289
        if capable {
1✔
NEW
290
                newLabels[kubevirtv1.RealtimeLabel] = "true"
×
291
        }
×
292

293
        if n.SEV.Supported == "yes" {
2✔
294
                newLabels[kubevirtv1.SEVLabel] = "true"
1✔
295
        }
1✔
296

297
        if n.SEV.SupportedES == "yes" {
2✔
298
                newLabels[kubevirtv1.SEVESLabel] = "true"
1✔
299
        }
1✔
300
        if n.SecureExecution.Supported == "yes" {
2✔
301
                newLabels[kubevirtv1.SecureExecutionLabel] = "true"
1✔
302
        }
1✔
303

304
        return newLabels
1✔
305
}
306

307
// addNodeLabels adds labels to node.
308
func (n *NodeLabeller) addLabellerLabels(node *v1.Node, labels map[string]string) {
1✔
309
        for key, value := range labels {
2✔
310
                node.Labels[key] = value
1✔
311
        }
1✔
312
}
313

314
// removeLabellerLabels removes labels from node
315
func (n *NodeLabeller) removeLabellerLabels(node *v1.Node) {
1✔
316
        for label := range node.Labels {
2✔
317
                if isNodeLabellerLabel(label) {
2✔
318
                        delete(node.Labels, label)
1✔
319
                }
1✔
320
        }
321
}
322

323
const kernelSchedRealtimeRuntimeInMicrosecods = "kernel.sched_rt_runtime_us"
324

325
// isNodeRealtimeCapable Checks if a node is capable of running realtime workloads. Currently by validating if the kernel system setting value
326
// for `kernel.sched_rt_runtime_us` is set to allow running realtime scheduling with unlimited time (==-1)
327
// TODO: This part should be improved to validate against key attributes that determine best if a host is able to run realtime
328
// workloads at peak performance.
329

330
func isNodeRealtimeCapable() (bool, error) {
1✔
331
        ret, err := exec.Command("sysctl", kernelSchedRealtimeRuntimeInMicrosecods).CombinedOutput()
1✔
332
        if err != nil {
2✔
333
                return false, err
1✔
334
        }
1✔
335
        st := strings.Trim(string(ret), "\n")
×
336
        return fmt.Sprintf("%s = -1", kernelSchedRealtimeRuntimeInMicrosecods) == st, nil
×
337
}
338

339
func isNodeLabellerLabel(label string) bool {
1✔
340
        for _, prefix := range nodeLabellerLabels {
2✔
341
                if strings.HasPrefix(label, prefix) {
2✔
342
                        return true
1✔
343
                }
1✔
344
        }
345

346
        return false
1✔
347
}
348

349
func (n *NodeLabeller) alertIfHostModelIsObsolete(originalNode *v1.Node, hostModel string, ObsoleteCPUModels map[string]bool) error {
1✔
350
        warningMsg := fmt.Sprintf("This node has %v host-model cpu that is included in ObsoleteCPUModels: %v", hostModel, ObsoleteCPUModels)
1✔
351
        n.recorder.Eventf(originalNode, v1.EventTypeWarning, "HostModelIsObsolete", warningMsg)
1✔
352
        return nil
1✔
353
}
1✔
354

355
func (n *NodeLabeller) hasTSCCounter() bool {
1✔
356
        return n.cpuCounter != nil && n.cpuCounter.Name == "tsc"
1✔
357
}
1✔
358

359
func (n *NodeLabeller) getSupportedMachines() []libvirtxml.CapsGuestMachine {
1✔
360
        var supportedMachines []libvirtxml.CapsGuestMachine
1✔
361
        for _, guest := range n.guestCaps {
2✔
362
                supportedMachines = append(supportedMachines, guest.Arch.Machines...)
1✔
363
        }
1✔
364
        return supportedMachines
1✔
365
}
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