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

kubevirt / kubevirt / db9927fa-6ef4-4b9f-b267-16b157867172

26 Apr 2026 07:37PM UTC coverage: 71.762% (+0.006%) from 71.756%
db9927fa-6ef4-4b9f-b267-16b157867172

push

prow

web-flow
Merge pull request #17606 from akalenyu/revert-cross-ns-mig-dq

e2e quarantine: revert accidental cross ns migration dequarantine

77616 of 108158 relevant lines covered (71.76%)

572.11 hits per line

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

90.91
/pkg/dra/utils.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 dra
21

22
import (
23
        "encoding/json"
24
        "fmt"
25
        "os"
26
        "path/filepath"
27
        "strings"
28

29
        k8sv1 "k8s.io/api/core/v1"
30
        v1 "kubevirt.io/api/core/v1"
31
        "kubevirt.io/client-go/log"
32

33
        // TODO: Replace with k8s.io types when KEP-5304 is implemented in kubernetes
34
        "kubevirt.io/kubevirt/pkg/dra/metadata"
35
)
36

37
// IsGPUDRA returns true if the GPU is a DRA GPU
38
func IsGPUDRA(gpu v1.GPU) bool {
33✔
39
        return gpu.DeviceName == "" && gpu.ClaimRequest != nil
33✔
40
}
33✔
41

42
// IsHostDeviceDRA returns true if the HostDevice is a DRA HostDevice
43
func IsHostDeviceDRA(hd v1.HostDevice) bool {
23✔
44
        return hd.DeviceName == "" && hd.ClaimRequest != nil
23✔
45
}
23✔
46

47
// KEP-5304 defines the in-container directory layout for DRA device metadata files.
48
// See: kubernetes/enhancements#5304, k8s.io/dynamic-resource-allocation/api/metadata
49
const (
50
        DefaultMetadataBasePath      = "/var/run/kubernetes.io/dra-device-attributes"
51
        resourceClaimsSubdir         = "resourceclaims"
52
        resourceClaimTemplatesSubdir = "resourceclaimtemplates"
53
        metadataFileSuffix           = "-metadata.json"
54
)
55

56
// GetPCIAddressForClaim returns the PCI address for a device in the given claim and request.
57
// It lazily reads the KEP-5304 metadata file at lookup time.
58
func GetPCIAddressForClaim(basePath string, resourceClaims []k8sv1.PodResourceClaim, claimRefName, requestName string) (string, error) {
25✔
59
        device, err := resolveDevice(basePath, resourceClaims, claimRefName, requestName)
25✔
60
        if err != nil {
38✔
61
                return "", err
13✔
62
        }
13✔
63

64
        if attr, ok := device.Attributes[metadata.PCIBusIDAttribute]; ok {
23✔
65
                if attr.StringValue != nil && *attr.StringValue != "" {
22✔
66
                        return *attr.StringValue, nil
11✔
67
                }
11✔
68
        }
69
        return "", fmt.Errorf("pciBusID not found for claim %q request %q", claimRefName, requestName)
1✔
70
}
71

72
// GetMDevUUIDForClaim returns the mdev UUID for a device in the given claim and request.
73
// It lazily reads the KEP-5304 metadata file at lookup time.
74
func GetMDevUUIDForClaim(basePath string, resourceClaims []k8sv1.PodResourceClaim, claimRefName, requestName string) (string, error) {
19✔
75
        device, err := resolveDevice(basePath, resourceClaims, claimRefName, requestName)
19✔
76
        if err != nil {
22✔
77
                return "", err
3✔
78
        }
3✔
79

80
        if attr, ok := device.Attributes[metadata.MDevUUIDAttribute]; ok {
26✔
81
                if attr.StringValue != nil && *attr.StringValue != "" {
20✔
82
                        return *attr.StringValue, nil
10✔
83
                }
10✔
84
        }
85
        return "", fmt.Errorf("mdevUUID not found for claim %q request %q", claimRefName, requestName)
6✔
86
}
87

88
// resolveDevice finds and reads the metadata file for a specific claim ref and
89
// request, returning the single device from that request.
90
func resolveDevice(basePath string, resourceClaims []k8sv1.PodResourceClaim, claimRefName, requestName string) (*metadata.Device, error) {
44✔
91
        md, err := resolveClaimMetadata(basePath, resourceClaims, claimRefName, requestName)
44✔
92
        if err != nil {
58✔
93
                return nil, err
14✔
94
        }
14✔
95

96
        for _, req := range md.Requests {
60✔
97
                if req.Name == requestName {
59✔
98
                        if len(req.Devices) == 0 {
29✔
99
                                return nil, fmt.Errorf("request %q has no devices", requestName)
×
100
                        }
×
101
                        if len(req.Devices) > 1 {
30✔
102
                                return nil, fmt.Errorf("request %q has %d devices but KubeVirt only supports exactly one device per request (count > 1 is not supported)", requestName, len(req.Devices))
1✔
103
                        }
1✔
104
                        return &req.Devices[0], nil
28✔
105
                }
106
        }
107
        return nil, fmt.Errorf("request %q not found in metadata for claim %q (available requests: %v)", requestName, md.Name, metadataRequestNames(md))
1✔
108
}
109

110
// resolveClaimMetadata reads the metadata file for a claim ref + request pair.
111
// Direct claims:   {base}/resourceclaims/{claimName}/{requestName}/{driverName}-metadata.json
112
// Template claims: {base}/resourceclaimtemplates/{podClaimName}/{requestName}/{driverName}-metadata.json
113
func resolveClaimMetadata(basePath string, resourceClaims []k8sv1.PodResourceClaim, claimRefName, requestName string) (*metadata.DeviceMetadata, error) {
44✔
114
        for _, rc := range resourceClaims {
93✔
115
                if rc.Name != claimRefName {
56✔
116
                        continue
7✔
117
                }
118
                if rc.ResourceClaimName != nil && *rc.ResourceClaimName != "" {
79✔
119
                        return readMetadataFromDir(filepath.Join(basePath, resourceClaimsSubdir), *rc.ResourceClaimName, requestName)
37✔
120
                }
37✔
121
                return readMetadataFromDir(filepath.Join(basePath, resourceClaimTemplatesSubdir), rc.Name, requestName)
5✔
122
        }
123
        return nil, fmt.Errorf("metadata not found for claim %q", claimRefName)
2✔
124
}
125

126
// readMetadataFromDir reads the single metadata file matching
127
// {basePath}/{claimName}/{requestName}/*-metadata.json.
128
// KubeVirt expects exactly one driver per request; multiple files indicate
129
// a count > 1 allocation across drivers which is not supported.
130
func readMetadataFromDir(basePath, claimName, requestName string) (*metadata.DeviceMetadata, error) {
42✔
131
        pattern := filepath.Join(basePath, claimName, requestName, "*"+metadataFileSuffix)
42✔
132
        matches, err := filepath.Glob(pattern)
42✔
133
        if err != nil {
42✔
134
                return nil, fmt.Errorf("failed to glob metadata for claim %q request %q: %w", claimName, requestName, err)
×
135
        }
×
136
        if len(matches) == 0 {
49✔
137
                return nil, fmt.Errorf("failed to read metadata for claim %q request %q: no files matching %s", claimName, requestName, pattern)
7✔
138
        }
7✔
139
        if len(matches) > 1 {
36✔
140
                return nil, fmt.Errorf("found %d metadata files for claim %q request %q but KubeVirt only supports exactly one driver per request", len(matches), claimName, requestName)
1✔
141
        }
1✔
142
        log.Log.Infof("Reading DRA device metadata file %s", matches[0])
34✔
143
        return readMetadataFile(matches[0])
34✔
144
}
145

146
func metadataRequestNames(md *metadata.DeviceMetadata) []string {
1✔
147
        names := make([]string, 0, len(md.Requests))
1✔
148
        for _, req := range md.Requests {
2✔
149
                names = append(names, req.Name)
1✔
150
        }
1✔
151
        return names
1✔
152
}
153

154
// readMetadataFile reads a KEP-5304 metadata file. The file is a JSON stream
155
// containing the same data encoded once per API version (newest first).
156
// We iterate through the stream and decode the first object whose apiVersion
157
// we understand, skipping unknown versions so that a driver upgrade does not
158
// break older consumers.
159
func readMetadataFile(path string) (*metadata.DeviceMetadata, error) {
34✔
160
        f, err := os.Open(path)
34✔
161
        if err != nil {
34✔
162
                return nil, fmt.Errorf("opening metadata file %q: %w", path, err)
×
163
        }
×
164
        defer f.Close()
34✔
165

34✔
166
        md, err := decodeMetadataFromStream(json.NewDecoder(f))
34✔
167
        if err != nil {
38✔
168
                return nil, fmt.Errorf("%s: %w", path, err)
4✔
169
        }
4✔
170
        return md, nil
30✔
171
}
172

173
// decodeMetadataFromStream is a lightweight equivalent of
174
// devicemetadata.DecodeMetadataFromStream that iterates a JSON stream and
175
// returns the first object whose apiVersion is supported. Entries whose
176
// apiVersion is not supported by KubeVirt (e.g. a newer version written by an
177
// upgraded driver) are skipped silently. Any other failure is fatal.
178
func decodeMetadataFromStream(dec *json.Decoder) (*metadata.DeviceMetadata, error) {
34✔
179
        var unknownVersions []string
34✔
180
        for dec.More() {
68✔
181
                var raw json.RawMessage
34✔
182
                if err := dec.Decode(&raw); err != nil {
34✔
183
                        return nil, fmt.Errorf("read object from metadata stream: %w", err)
×
184
                }
×
185

186
                var peek struct {
34✔
187
                        APIVersion string `json:"apiVersion"`
34✔
188
                }
34✔
189
                if err := json.Unmarshal(raw, &peek); err != nil {
35✔
190
                        return nil, fmt.Errorf("decode metadata object: %w", err)
1✔
191
                }
1✔
192
                if peek.APIVersion == "" {
33✔
193
                        return nil, fmt.Errorf("decode metadata object: missing apiVersion")
×
194
                }
×
195

196
                if !metadata.IsSupportedAPIVersion(peek.APIVersion) {
35✔
197
                        unknownVersions = append(unknownVersions, peek.APIVersion)
2✔
198
                        continue
2✔
199
                }
200

201
                var md metadata.DeviceMetadata
31✔
202
                if err := json.Unmarshal(raw, &md); err != nil {
32✔
203
                        return nil, fmt.Errorf("decode %s: %w", peek.APIVersion, err)
1✔
204
                }
1✔
205
                return &md, nil
30✔
206
        }
207
        if len(unknownVersions) == 0 {
3✔
208
                return nil, fmt.Errorf("no metadata objects found in stream")
1✔
209
        }
1✔
210
        return nil, fmt.Errorf("no compatible metadata version found in stream (unknown versions: %s)", strings.Join(unknownVersions, ", "))
1✔
211
}
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