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

kubevirt / kubevirt / 85cf11f1-0f3d-4ae3-b5ee-a2888c90054f

28 Feb 2026 03:28AM UTC coverage: 71.368% (+0.1%) from 71.243%
85cf11f1-0f3d-4ae3-b5ee-a2888c90054f

push

prow

web-flow
Merge pull request #16776 from 0xFelix/virt-template-deployment-tls

feat(virt-operator): Allow setting TLS options for virt-template

26 of 30 new or added lines in 2 files covered. (86.67%)

1992 existing lines in 28 files now uncovered.

75450 of 105719 relevant lines covered (71.37%)

536.01 hits per line

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

74.09
/pkg/virt-handler/device-manager/device_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 device_manager
21

22
import (
23
        "fmt"
24
        "math"
25
        "os"
26
        "path"
27
        "strings"
28
        "sync"
29
        "time"
30

31
        k8sv1 "k8s.io/api/core/v1"
32
        "k8s.io/client-go/tools/cache"
33

34
        "kubevirt.io/kubevirt/pkg/virt-controller/services"
35

36
        "kubevirt.io/kubevirt/pkg/virt-handler/cgroup"
37

38
        "kubevirt.io/client-go/log"
39

40
        "kubevirt.io/kubevirt/pkg/storage/reservation"
41
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
42
        "kubevirt.io/kubevirt/pkg/virt-handler/selinux"
43
)
44

45
var defaultBackoffTime = []time.Duration{1 * time.Second, 2 * time.Second, 5 * time.Second, 10 * time.Second}
46

47
type controlledDevice struct {
48
        devicePlugin Device
49
        started      bool
50
        stopChan     chan struct{}
51
        backoff      []time.Duration
52
}
53

54
func (c *controlledDevice) Start() {
8✔
55
        if c.started {
8✔
UNCOV
56
                return
×
UNCOV
57
        }
×
58

59
        stop := make(chan struct{})
8✔
60

8✔
61
        logger := log.DefaultLogger()
8✔
62
        dev := c.devicePlugin
8✔
63
        deviceName := dev.GetDeviceName()
8✔
64
        logger.Infof("Starting a device plugin for device: %s", deviceName)
8✔
65
        retries := 0
8✔
66

8✔
67
        backoff := c.backoff
8✔
68
        if backoff == nil {
8✔
UNCOV
69
                backoff = defaultBackoffTime
×
UNCOV
70
        }
×
71

72
        go func() {
16✔
73
                for {
18✔
74
                        err := dev.Start(stop)
10✔
75
                        if err != nil {
12✔
76
                                logger.Reason(err).Errorf("Error starting %s device plugin", deviceName)
2✔
77
                                retries = int(math.Min(float64(retries+1), float64(len(backoff)-1)))
2✔
78
                        } else {
10✔
79
                                retries = 0
8✔
80
                        }
8✔
81

82
                        select {
10✔
83
                        case <-stop:
8✔
84
                                // Ok we don't want to re-register
8✔
85
                                return
8✔
86
                        case <-time.After(backoff[retries]):
2✔
87
                                // Wait a little and re-register
2✔
88
                                continue
2✔
89
                        }
90
                }
91
        }()
92

93
        c.stopChan = stop
8✔
94
        c.started = true
8✔
95
}
96

97
func (c *controlledDevice) Stop() {
8✔
98
        if !c.started {
8✔
UNCOV
99
                return
×
UNCOV
100
        }
×
101
        close(c.stopChan)
8✔
102

8✔
103
        c.stopChan = nil
8✔
104
        c.started = false
8✔
105
}
106

UNCOV
107
func (c *controlledDevice) GetName() string {
×
UNCOV
108
        return c.devicePlugin.GetDeviceName()
×
UNCOV
109
}
×
110

111
func PermanentHostDevicePlugins(maxDevices int, permissions string) []Device {
119✔
112
        var permanentDevicePluginPaths = map[string]string{
119✔
113
                "kvm":       "/dev/kvm",
119✔
114
                "tun":       "/dev/net/tun",
119✔
115
                "vhost-net": "/dev/vhost-net",
119✔
116
        }
119✔
117

119✔
118
        ret := make([]Device, 0, len(permanentDevicePluginPaths))
119✔
119
        for name, path := range permanentDevicePluginPaths {
476✔
120
                ret = append(ret, NewGenericDevicePlugin(name, path, maxDevices, permissions, name != "kvm"))
357✔
121
        }
357✔
122
        return ret
119✔
123
}
124

125
type DeviceControllerInterface interface {
126
        Initialized() bool
127
        RefreshMediatedDeviceTypes()
128
}
129

130
type DeviceController struct {
131
        permanentPlugins         map[string]Device
132
        startedPlugins           map[string]controlledDevice
133
        startedPluginsMutex      sync.Mutex
134
        host                     string
135
        maxDevices               int
136
        permissions              string
137
        backoff                  []time.Duration
138
        virtConfig               *virtconfig.ClusterConfig
139
        mdevTypesManager         *MDEVTypesManager
140
        nodeStore                cache.Store
141
        mdevRefreshWG            *sync.WaitGroup
142
        lastTDXAttestationConfig *tdxConfigState
143
}
144

145
type tdxConfigState struct {
146
        socketPath string
147
        requireQGS bool
148
}
149

150
func NewDeviceController(
151
        host string,
152
        maxDevices int,
153
        permissions string,
154
        permanentPlugins []Device,
155
        clusterConfig *virtconfig.ClusterConfig,
156
        nodeStore cache.Store,
157
) *DeviceController {
132✔
158
        permanentPluginsMap := make(map[string]Device, len(permanentPlugins))
132✔
159
        for i := range permanentPlugins {
495✔
160
                permanentPluginsMap[permanentPlugins[i].GetDeviceName()] = permanentPlugins[i]
363✔
161
        }
363✔
162

163
        controller := &DeviceController{
132✔
164
                permanentPlugins: permanentPluginsMap,
132✔
165
                startedPlugins:   map[string]controlledDevice{},
132✔
166
                host:             host,
132✔
167
                maxDevices:       maxDevices,
132✔
168
                permissions:      permissions,
132✔
169
                backoff:          defaultBackoffTime,
132✔
170
                virtConfig:       clusterConfig,
132✔
171
                mdevTypesManager: NewMDEVTypesManager(),
132✔
172
                nodeStore:        nodeStore,
132✔
173
                mdevRefreshWG:    &sync.WaitGroup{},
132✔
174
        }
132✔
175

132✔
176
        return controller
132✔
177
}
178

179
func (c *DeviceController) NodeHasDevice(devicePath string) bool {
4✔
180
        _, err := os.Stat(devicePath)
4✔
181
        // Since this is a boolean question, any error means "no"
4✔
182
        return err == nil
4✔
183
}
4✔
184

UNCOV
185
func (c *DeviceController) updateTdxDevice() (Device, error) {
×
UNCOV
186
        maxTDXVMs, err := cgroup.GetMiscCapacity("tdx")
×
UNCOV
187
        if err != nil {
×
188
                return nil, fmt.Errorf("failed to get TDX capacity from misc.capacity: %v", err)
×
189
        } else if maxTDXVMs > 0 {
×
190
                var selinuxExecutor selinux.SELinuxExecutor
×
191
                socketPath := c.virtConfig.GetQGSSocketPath()
×
192
                socketDir := path.Dir(socketPath)
×
UNCOV
193
                socketFile := path.Base(socketPath)
×
UNCOV
194
                var tdxPlugin Device
×
UNCOV
195
                var err error
×
196
                if c.virtConfig.RequireQGS() {
×
197
                        tdxPlugin, err = NewSocketDevicePlugin(services.TdxDeviceName, socketDir, socketFile, maxTDXVMs, selinuxExecutor, nil, true)
×
198
                } else {
×
199
                        tdxPlugin = NewOptionalSocketDevicePlugin(services.TdxDeviceName, socketDir, socketFile, maxTDXVMs, selinuxExecutor, nil, true)
×
200
                }
×
201
                return tdxPlugin, err
×
UNCOV
202
        } else {
×
UNCOV
203
                return nil, fmt.Errorf("an invalid device capacity of %d was report for tdx", maxTDXVMs)
×
UNCOV
204
        }
×
205
}
206

207
// updatePermittedHostDevicePlugins returns a slice of device plugins for permitted devices which are present on the node
208
func (c *DeviceController) updatePermittedHostDevicePlugins() []Device {
14✔
209
        var permittedDevices []Device
14✔
210

14✔
211
        if c.virtConfig.WorkloadEncryptionTDXEnabled() {
14✔
UNCOV
212
                tdxPlugin, err := c.updateTdxDevice()
×
UNCOV
213
                if err != nil {
×
UNCOV
214
                        log.Log.Reason(err).Errorf("failed to configure the TDX-QGS device plugin")
×
UNCOV
215
                } else {
×
UNCOV
216
                        permittedDevices = append(permittedDevices, tdxPlugin)
×
UNCOV
217
                }
×
218
        }
219

220
        var featureGatedGenericDevices = []struct {
14✔
221
                Name      string
14✔
222
                Path      string
14✔
223
                IsAllowed func() bool
14✔
224
        }{
14✔
225
                {"sev", "/dev/sev", c.virtConfig.WorkloadEncryptionSEVEnabled},
14✔
226
                {"vhost-vsock", "/dev/vhost-vsock", c.virtConfig.VSOCKEnabled},
14✔
227
        }
14✔
228

14✔
229
        for _, dev := range featureGatedGenericDevices {
42✔
230
                if dev.IsAllowed() {
28✔
UNCOV
231
                        permittedDevices = append(
×
UNCOV
232
                                permittedDevices,
×
UNCOV
233
                                NewGenericDevicePlugin(dev.Name, dev.Path, c.maxDevices, c.permissions, true),
×
UNCOV
234
                        )
×
UNCOV
235
                }
×
236
        }
237

238
        if c.virtConfig.PersistentReservationEnabled() {
14✔
UNCOV
239
                d, err := NewSocketDevicePlugin(reservation.GetPrResourceName(), reservation.GetPrHelperSocketDir(), reservation.GetPrHelperSocket(), c.maxDevices, selinux.SELinuxExecutor{}, NewPermissionManager(), false)
×
UNCOV
240
                if err != nil {
×
UNCOV
241
                        log.Log.Reason(err).Errorf("failed to configure the desired mdev types, failed to get node details")
×
UNCOV
242
                } else {
×
UNCOV
243
                        permittedDevices = append(permittedDevices, d)
×
UNCOV
244
                }
×
245
        }
246

247
        hostDevs := c.virtConfig.GetPermittedHostDevices()
14✔
248
        if hostDevs == nil {
18✔
249
                return permittedDevices
4✔
250
        }
4✔
251

252
        if len(hostDevs.PciHostDevices) != 0 {
17✔
253
                supportedPCIDeviceMap := make(map[string]string)
7✔
254
                for _, pciDev := range hostDevs.PciHostDevices {
20✔
255
                        log.Log.V(4).Infof("Permitted PCI device in the cluster, ID: %s, resourceName: %s, externalProvider: %t",
13✔
256
                                strings.ToLower(pciDev.PCIVendorSelector),
13✔
257
                                pciDev.ResourceName,
13✔
258
                                pciDev.ExternalResourceProvider)
13✔
259
                        // do not add a device plugin for this resource if it's being provided via an external device plugin
13✔
260
                        if !pciDev.ExternalResourceProvider {
14✔
261
                                supportedPCIDeviceMap[strings.ToLower(pciDev.PCIVendorSelector)] = pciDev.ResourceName
1✔
262
                        }
1✔
263
                }
264
                for pciResourceName, pciDevices := range discoverPermittedHostPCIDevices(supportedPCIDeviceMap) {
8✔
265
                        log.Log.V(4).Infof("Discovered PCIs %d devices on the node for the resource: %s", len(pciDevices), pciResourceName)
1✔
266
                        // add a device plugin only for new devices
1✔
267
                        permittedDevices = append(permittedDevices, NewPCIDevicePlugin(pciDevices, pciResourceName))
1✔
268
                }
1✔
269
        }
270
        if len(hostDevs.MediatedDevices) != 0 {
11✔
271
                supportedMdevsMap := make(map[string]string)
1✔
272
                for _, supportedMdev := range hostDevs.MediatedDevices {
2✔
273
                        log.Log.V(4).Infof("Permitted mediated device in the cluster, ID: %s, resourceName: %s",
1✔
274
                                supportedMdev.MDEVNameSelector,
1✔
275
                                supportedMdev.ResourceName)
1✔
276
                        // do not add a device plugin for this resource if it's being provided via an external device plugin
1✔
277
                        if !supportedMdev.ExternalResourceProvider {
2✔
278
                                selector := removeSelectorSpaces(supportedMdev.MDEVNameSelector)
1✔
279
                                supportedMdevsMap[selector] = supportedMdev.ResourceName
1✔
280
                        }
1✔
281
                }
282
                for mdevTypeName, mdevUUIDs := range discoverPermittedHostMediatedDevices(supportedMdevsMap) {
2✔
283
                        mdevResourceName := supportedMdevsMap[mdevTypeName]
1✔
284
                        log.Log.V(4).Infof("Discovered mediated device on the node, type: %s, resourceName: %s", mdevTypeName, mdevResourceName)
1✔
285

1✔
286
                        permittedDevices = append(permittedDevices, NewMediatedDevicePlugin(mdevUUIDs, mdevResourceName))
1✔
287
                }
1✔
288
        }
289

290
        for resourceName, pluginDevices := range discoverAllowedUSBDevices(hostDevs.USB) {
10✔
UNCOV
291
                permittedDevices = append(permittedDevices, NewUSBDevicePlugin(resourceName, pluginDevices))
×
UNCOV
292
        }
×
293

294
        return permittedDevices
10✔
295
}
296

297
func removeSelectorSpaces(selectorName string) string {
5✔
298
        // The name usually contain spaces which should be replaced with _
5✔
299
        // Such as GRID T4-1Q
5✔
300
        typeNameStr := strings.Replace(selectorName, " ", "_", -1)
5✔
301
        typeNameStr = strings.TrimSpace(typeNameStr)
5✔
302
        return typeNameStr
5✔
303
}
5✔
304

305
func (c *DeviceController) splitPermittedDevices(devices []Device) (map[string]Device, map[string]struct{}) {
14✔
306
        devicePluginsToRun := make(map[string]Device)
14✔
307
        devicePluginsToStop := make(map[string]struct{})
14✔
308

14✔
309
        // generate a map of currently started device plugins
14✔
310
        for resourceName := range c.startedPlugins {
30✔
311
                _, isPermanent := c.permanentPlugins[resourceName]
16✔
312
                if !isPermanent {
20✔
313
                        devicePluginsToStop[resourceName] = struct{}{}
4✔
314
                }
4✔
315
        }
316

317
        for _, device := range devices {
16✔
318
                if _, isRunning := c.startedPlugins[device.GetDeviceName()]; !isRunning {
4✔
319
                        devicePluginsToRun[device.GetDeviceName()] = device
2✔
320
                } else {
2✔
UNCOV
321
                        delete(devicePluginsToStop, device.GetDeviceName())
×
322
                }
×
323
        }
324

325
        return devicePluginsToRun, devicePluginsToStop
14✔
326
}
327

UNCOV
328
func (c *DeviceController) RefreshMediatedDeviceTypes() {
×
UNCOV
329
        go func() {
×
330
                if c.refreshMediatedDeviceTypes() {
×
331
                        c.refreshPermittedDevices()
×
332
                }
×
333
        }()
334
}
335

336
func (c *DeviceController) getExternallyProvidedMdevs() map[string]struct{} {
11✔
337
        externalMdevResourcesMap := make(map[string]struct{})
11✔
338
        if hostDevs := c.virtConfig.GetPermittedHostDevices(); hostDevs != nil {
11✔
UNCOV
339
                for _, supportedMdev := range hostDevs.MediatedDevices {
×
340
                        if supportedMdev.ExternalResourceProvider {
×
341
                                selector := removeSelectorSpaces(supportedMdev.MDEVNameSelector)
×
UNCOV
342
                                externalMdevResourcesMap[selector] = struct{}{}
×
UNCOV
343
                        }
×
344
                }
345
        }
346
        return externalMdevResourcesMap
11✔
347
}
348

349
func (c *DeviceController) refreshMediatedDeviceTypes() bool {
16✔
350
        // the handling of mediated device is disabled
16✔
351
        if c.virtConfig.MediatedDevicesHandlingDisabled() {
16✔
UNCOV
352
                return false
×
UNCOV
353
        }
×
354

355
        node, err := c.getNode()
16✔
356
        if err != nil {
21✔
357
                log.Log.Reason(err).Errorf("failed to configure the desired mdev types, failed to get node details")
5✔
358
                return false
5✔
359
        }
5✔
360
        externallyProvidedMdevMap := c.getExternallyProvidedMdevs()
11✔
361

11✔
362
        nodeDesiredMdevTypesList := c.virtConfig.GetDesiredMDEVTypes(node)
11✔
363
        requiresDevicePluginsUpdate, err := c.mdevTypesManager.updateMDEVTypesConfiguration(nodeDesiredMdevTypesList, externallyProvidedMdevMap)
11✔
364
        if err != nil {
11✔
UNCOV
365
                log.Log.Reason(err).Errorf("failed to configure the desired mdev types: %s", strings.Join(nodeDesiredMdevTypesList, ", "))
×
366
        }
×
367
        return requiresDevicePluginsUpdate
11✔
368
}
369

370
func (c *DeviceController) getNode() (*k8sv1.Node, error) {
16✔
371
        nodeObj, exists, err := c.nodeStore.GetByKey(c.host)
16✔
372
        if err != nil {
16✔
UNCOV
373
                log.DefaultLogger().Errorf("Unable to get node: %s", err.Error())
×
UNCOV
374
                return nil, err
×
UNCOV
375
        }
×
376
        if !exists {
21✔
377
                log.DefaultLogger().Errorf("node %s does not exist", c.host)
5✔
378
                return nil, fmt.Errorf("node %s does not exist", c.host)
5✔
379
        }
5✔
380

381
        node, ok := nodeObj.(*k8sv1.Node)
11✔
382
        if !ok {
11✔
UNCOV
383
                return nil, fmt.Errorf("unknown object type found in node informer")
×
UNCOV
384
        }
×
385

386
        return node, nil
11✔
387
}
388

389
func (c *DeviceController) refreshTDXConfig() bool {
10✔
390
        if !c.virtConfig.WorkloadEncryptionTDXEnabled() {
20✔
391
                // TDX not enabled, reset tracking
10✔
392
                c.lastTDXAttestationConfig = nil
10✔
393
                return false
10✔
394
        }
10✔
395

UNCOV
396
        currentTDXAttestationConfig := tdxConfigState{
×
UNCOV
397
                socketPath: c.virtConfig.GetQGSSocketPath(),
×
UNCOV
398
                requireQGS: c.virtConfig.RequireQGS(),
×
UNCOV
399
        }
×
UNCOV
400

×
UNCOV
401
        changed := c.lastTDXAttestationConfig == nil || *c.lastTDXAttestationConfig != currentTDXAttestationConfig
×
UNCOV
402

×
UNCOV
403
        if changed {
×
UNCOV
404
                c.lastTDXAttestationConfig = &currentTDXAttestationConfig
×
UNCOV
405
        }
×
406

UNCOV
407
        return changed
×
408
}
409

410
func (c *DeviceController) refreshPermittedDevices() {
10✔
411
        c.mdevRefreshWG.Add(1)
10✔
412
        logger := log.DefaultLogger()
10✔
413
        var debugDevAdded []string
10✔
414
        var debugDevRemoved []string
10✔
415

10✔
416
        // This function can be called multiple times in parallel, either because of multiple
10✔
417
        //   informer callbacks for the same event, or because the configmap was quickly updated
10✔
418
        //   multiple times in a row. To avoid starting/stopping device plugins multiple times,
10✔
419
        //   we need to protect c.startedPlugins, which we read from in
10✔
420
        //   c.updatePermittedHostDevicePlugins() and write to below.
10✔
421
        c.startedPluginsMutex.Lock()
10✔
422
        defer c.startedPluginsMutex.Unlock()
10✔
423

10✔
424
        // Check if QGS config changed and restart the QGS device plugin if needed
10✔
425
        if changed := c.refreshTDXConfig(); changed {
10✔
UNCOV
426
                if _, exists := c.startedPlugins[services.TdxDevice]; exists {
×
UNCOV
427
                        logger.Infof("QGS config changed, restarting QGS device plugin")
×
UNCOV
428
                        // only call stopDevice here,
×
UNCOV
429
                        // startDevice will be called when updatePermittedHostDevicePlugins() is called
×
UNCOV
430
                        c.stopDevice(services.TdxDevice)
×
UNCOV
431
                }
×
432
        }
433

434
        enabledDevicePlugins, disabledDevicePlugins := c.splitPermittedDevices(
10✔
435
                c.updatePermittedHostDevicePlugins(),
10✔
436
        )
10✔
437

10✔
438
        // start device plugin for newly permitted devices
10✔
439
        for resourceName, dev := range enabledDevicePlugins {
10✔
UNCOV
440
                c.startDevice(resourceName, dev)
×
UNCOV
441
                debugDevAdded = append(debugDevAdded, resourceName)
×
UNCOV
442
        }
×
443
        // remove device plugin for now forbidden devices
444
        for resourceName := range disabledDevicePlugins {
12✔
445
                c.stopDevice(resourceName)
2✔
446
                debugDevRemoved = append(debugDevRemoved, resourceName)
2✔
447
        }
2✔
448

449
        logger.V(3).Info("refreshed device plugins for permitted/forbidden host devices")
10✔
450
        if len(debugDevAdded) > 0 {
10✔
UNCOV
451
                logger.Infof("enabled device-plugins for: %v", debugDevAdded)
×
UNCOV
452
        }
×
453
        if len(debugDevRemoved) > 0 {
11✔
454
                logger.Infof("disabled device-plugins for: %v", debugDevRemoved)
1✔
455
        }
1✔
456
        c.mdevRefreshWG.Done()
10✔
457
}
458

459
func (c *DeviceController) startDevice(resourceName string, dev Device) {
8✔
460
        c.stopDevice(resourceName)
8✔
461
        controlledDev := controlledDevice{
8✔
462
                devicePlugin: dev,
8✔
463
                backoff:      c.backoff,
8✔
464
        }
8✔
465
        controlledDev.Start()
8✔
466
        c.startedPlugins[resourceName] = controlledDev
8✔
467
}
8✔
468

469
func (c *DeviceController) stopDevice(resourceName string) {
16✔
470
        dev, exists := c.startedPlugins[resourceName]
16✔
471
        if exists {
24✔
472
                dev.Stop()
8✔
473
                delete(c.startedPlugins, resourceName)
8✔
474
        }
8✔
475
}
476

477
func (c *DeviceController) Run(stop chan struct{}) {
5✔
478
        logger := log.DefaultLogger()
5✔
479

5✔
480
        // start the permanent DevicePlugins
5✔
481
        func() {
10✔
482
                c.startedPluginsMutex.Lock()
5✔
483
                defer c.startedPluginsMutex.Unlock()
5✔
484
                for name, dev := range c.permanentPlugins {
11✔
485
                        c.startDevice(name, dev)
6✔
486
                }
6✔
487
        }()
488

489
        refreshMediatedDeviceTypesFn := func() {
10✔
490
                c.refreshMediatedDeviceTypes()
5✔
491
        }
5✔
492
        c.virtConfig.SetConfigModifiedCallback(refreshMediatedDeviceTypesFn)
5✔
493
        c.virtConfig.SetConfigModifiedCallback(c.refreshPermittedDevices)
5✔
494
        c.refreshPermittedDevices()
5✔
495

5✔
496
        // keep running until stop
5✔
497
        <-stop
5✔
498

5✔
499
        // stop all device plugins
5✔
500
        func() {
10✔
501
                c.startedPluginsMutex.Lock()
5✔
502
                defer c.startedPluginsMutex.Unlock()
5✔
503
                for name := range c.startedPlugins {
11✔
504
                        c.stopDevice(name)
6✔
505
                }
6✔
506
        }()
507

508
        // wait for any concurrent mdev refreshes to finish
509
        c.mdevRefreshWG.Wait()
5✔
510

5✔
511
        logger.Info("Shutting down device plugin controller")
5✔
512
}
513

514
func (c *DeviceController) Initialized() bool {
1✔
515
        c.startedPluginsMutex.Lock()
1✔
516
        defer c.startedPluginsMutex.Unlock()
1✔
517
        for _, dev := range c.startedPlugins {
2✔
518
                if !dev.devicePlugin.GetInitialized() {
1✔
UNCOV
519
                        return false
×
UNCOV
520
                }
×
521
        }
522

523
        return true
1✔
524
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc