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

kubevirt / kubevirt / b53a81f4-0618-4002-b726-c9c09d13a927

26 Mar 2026 08:12PM UTC coverage: 71.685% (+0.04%) from 71.643%
b53a81f4-0618-4002-b726-c9c09d13a927

push

prow

web-flow
Merge pull request #17042 from awels/fix_missing_source_state_pvc_after_cclm

Ensure migration state is reset after decentralized live migration

27 of 28 new or added lines in 2 files covered. (96.43%)

125 existing lines in 2 files now uncovered.

77246 of 107757 relevant lines covered (71.69%)

473.15 hits per line

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

88.93
/pkg/util/openapi/openapi.go
1
package openapi
2

3
import (
4
        "encoding/json"
5
        "fmt"
6
        "log"
7
        "net/http"
8
        "os"
9
        "path"
10
        "strings"
11

12
        "github.com/emicklei/go-restful/v3"
13
        openapi_spec "github.com/go-openapi/spec"
14
        "github.com/go-openapi/strfmt"
15
        openapi_validate "github.com/go-openapi/validate"
16
        "k8s.io/apimachinery/pkg/runtime/schema"
17
        "k8s.io/kube-openapi/pkg/builder"
18
        builderv3 "k8s.io/kube-openapi/pkg/builder3"
19
        "k8s.io/kube-openapi/pkg/common"
20
        "k8s.io/kube-openapi/pkg/common/restfuladapter"
21
        handler3 "k8s.io/kube-openapi/pkg/handler3"
22
        "k8s.io/kube-openapi/pkg/spec3"
23
        "k8s.io/kube-openapi/pkg/validation/errors"
24
        "k8s.io/kube-openapi/pkg/validation/spec"
25
        v1 "kubevirt.io/api/core/v1"
26
        "kubevirt.io/client-go/api"
27
)
28

29
type Validator struct {
30
        specSchemes   *openapi_spec.Schema
31
        statusSchemes *openapi_spec.Schema
32
        topLevelKeys  map[string]interface{}
33
}
34

35
type V3Spec struct {
36
        service *handler3.OpenAPIService
37
}
38

39
func NewV3Spec(subwss []*restful.WebService) (*V3Spec, error) {
10✔
40
        v3spec := &V3Spec{
10✔
41
                service: handler3.NewOpenAPIService(),
10✔
42
        }
10✔
43

10✔
44
        for i, gv := range v1.SubresourceGroupVersions {
30✔
45
                gvPath := path.Join("apis", gv.Group, gv.Version)
20✔
46
                openapiV3Spec, err := buildV3Spec(subwss[i], gv.Version)
20✔
47
                if err != nil {
20✔
UNCOV
48
                        return nil, fmt.Errorf("failed to build OpenAPI v3 spec for %s: %w", gvPath, err)
×
UNCOV
49
                }
×
50
                v3spec.service.UpdateGroupVersion(gvPath, openapiV3Spec)
20✔
51
        }
52

53
        return v3spec, nil
10✔
54
}
55

56
func (c *V3Spec) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
1✔
57
        c.service.HandleDiscovery(w, r)
1✔
58
}
1✔
59

60
func (c *V3Spec) HandleGroupVersion(w http.ResponseWriter, r *http.Request) {
2✔
61
        c.service.HandleGroupVersion(w, r)
2✔
62
}
2✔
63

64
func buildV3Spec(ws *restful.WebService, version string) (*spec3.OpenAPI, error) {
20✔
65
        config := CreateV3Config()
20✔
66
        config.GetDefinitions = api.GetOpenAPIDefinitions
20✔
67
        openapiV3Spec, err := builderv3.BuildOpenAPISpecFromRoutes(
20✔
68
                restfuladapter.AdaptWebServices([]*restful.WebService{ws}), config)
20✔
69
        if err != nil {
20✔
UNCOV
70
                return nil, err
×
UNCOV
71
        }
×
72
        openapiV3Spec.Info.Version = version
20✔
73

20✔
74
        return openapiV3Spec, nil
20✔
75
}
76

77
func CreateConfig() *common.Config {
14✔
78
        return &common.Config{
14✔
79
                CommonResponses: map[int]spec.Response{
14✔
80
                        401: {
14✔
81
                                ResponseProps: spec.ResponseProps{
14✔
82
                                        Description: "Unauthorized",
14✔
83
                                },
14✔
84
                        },
14✔
85
                },
14✔
86
                Info: &spec.Info{
14✔
87
                        InfoProps: spec.InfoProps{
14✔
88
                                Title:       "KubeVirt API",
14✔
89
                                Description: "This is KubeVirt API an add-on for Kubernetes.",
14✔
90
                                Contact: &spec.ContactInfo{
14✔
91
                                        Name:  "kubevirt-dev",
14✔
92
                                        Email: "kubevirt-dev@googlegroups.com",
14✔
93
                                        URL:   "https://github.com/kubevirt/kubevirt",
14✔
94
                                },
14✔
95
                                License: &spec.License{
14✔
96
                                        Name: "Apache 2.0",
14✔
97
                                        URL:  "https://www.apache.org/licenses/LICENSE-2.0",
14✔
98
                                },
14✔
99
                        },
14✔
100
                },
14✔
101
                SecurityDefinitions: &spec.SecurityDefinitions{
14✔
102
                        "BearerToken": &spec.SecurityScheme{
14✔
103
                                SecuritySchemeProps: spec.SecuritySchemeProps{
14✔
104
                                        Type:        "apiKey",
14✔
105
                                        Name:        "authorization",
14✔
106
                                        In:          "header",
14✔
107
                                        Description: "Bearer Token authentication",
14✔
108
                                },
14✔
109
                        },
14✔
110
                },
14✔
111
                GetDefinitions: func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
28✔
112
                        m := api.GetOpenAPIDefinitions(ref)
14✔
113
                        for k, v := range m {
11,158✔
114
                                if _, ok := m[k]; !ok {
11,144✔
115
                                        m[k] = v
×
116
                                }
×
117
                        }
118
                        return m
14✔
119
                },
120

121
                GetDefinitionName: func(name string) (string, spec.Extensions) {
34,664✔
122
                        if strings.Contains(name, "kubevirt.io") {
53,438✔
123
                                // keeping for validation
18,774✔
124
                                return name[strings.LastIndex(name, "/")+1:], nil
18,774✔
125
                        }
18,774✔
126
                        //adpting k8s style
127
                        return strings.ReplaceAll(name, "/", "."), nil
15,890✔
128
                },
129
        }
130
}
131

132
func CreateV3Config() *common.OpenAPIV3Config {
20✔
133
        return &common.OpenAPIV3Config{
20✔
134
                CommonResponses: map[int]*spec3.Response{
20✔
135
                        401: {
20✔
136
                                ResponseProps: spec3.ResponseProps{
20✔
137
                                        Description: "Unauthorized",
20✔
138
                                },
20✔
139
                        },
20✔
140
                },
20✔
141
                Info: &spec.Info{
20✔
142
                        InfoProps: spec.InfoProps{
20✔
143
                                Title:       "KubeVirt API",
20✔
144
                                Description: "This is KubeVirt API an add-on for Kubernetes.",
20✔
145
                                Contact: &spec.ContactInfo{
20✔
146
                                        Name:  "kubevirt-dev",
20✔
147
                                        Email: "kubevirt-dev@googlegroups.com",
20✔
148
                                        URL:   "https://github.com/kubevirt/kubevirt",
20✔
149
                                },
20✔
150
                                License: &spec.License{
20✔
151
                                        Name: "Apache 2.0",
20✔
152
                                        URL:  "https://www.apache.org/licenses/LICENSE-2.0",
20✔
153
                                },
20✔
154
                        },
20✔
155
                },
20✔
156
                SecuritySchemes: spec3.SecuritySchemes{
20✔
157
                        "BearerToken": &spec3.SecurityScheme{
20✔
158
                                SecuritySchemeProps: spec3.SecuritySchemeProps{
20✔
159
                                        Type:        "apiKey",
20✔
160
                                        Name:        "authorization",
20✔
161
                                        In:          "header",
20✔
162
                                        Description: "Bearer Token authentication",
20✔
163
                                },
20✔
164
                        },
20✔
165
                },
20✔
166
                GetDefinitions: func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
20✔
UNCOV
167
                        m := api.GetOpenAPIDefinitions(ref)
×
UNCOV
168
                        for k, v := range m {
×
UNCOV
169
                                if _, ok := m[k]; !ok {
×
UNCOV
170
                                        m[k] = v
×
UNCOV
171
                                }
×
172
                        }
UNCOV
173
                        return m
×
174
                },
175

176
                GetDefinitionName: func(name string) (string, spec.Extensions) {
27,900✔
177
                        if strings.Contains(name, "kubevirt.io") {
40,940✔
178
                                // keeping for validation
13,040✔
179
                                return name[strings.LastIndex(name, "/")+1:], nil
13,040✔
180
                        }
13,040✔
181
                        //adpting k8s style
182
                        return strings.ReplaceAll(name, "/", "."), nil
14,860✔
183
                },
184
        }
185
}
186

187
func LoadOpenAPISpec(webServices []*restful.WebService) *spec.Swagger {
14✔
188
        config := CreateConfig()
14✔
189
        openapispec, err := builder.BuildOpenAPISpecFromRoutes(restfuladapter.AdaptWebServices(webServices), config)
14✔
190
        if err != nil {
14✔
UNCOV
191
                panic(fmt.Errorf("Failed to build swagger: %s", err))
×
192
        }
193

194
        // creationTimestamp, lastProbeTime and lastTransitionTime are deserialized as "null"
195
        // Fix it here until
196
        // https://github.com/kubernetes/kubernetes/issues/66899 is ready
197
        // Otherwise CRDs can't use templates which contain metadata and controllers
198
        // can't set conditions without timestamps
199

200
        objectmeta := ""
14✔
201
        for k := range openapispec.Definitions {
2,633✔
202
                if strings.Contains(k, "v1.ObjectMeta") {
2,633✔
203
                        objectmeta = k
14✔
204
                        break
14✔
205
                }
206
        }
207

208
        const resourceQuantityDefinition = "k8s.io.apimachinery.pkg.api.resource.Quantity"
14✔
209

14✔
210
        quantity, exists := openapispec.Definitions[resourceQuantityDefinition]
14✔
211
        if exists {
28✔
212
                quantity.Type = spec.StringOrArray{"string", "integer", "number"}
14✔
213
                openapispec.Definitions[resourceQuantityDefinition] = quantity
14✔
214
        }
14✔
215

216
        objectMeta, exists := openapispec.Definitions[objectmeta]
14✔
217
        if exists {
28✔
218
                prop := objectMeta.Properties["creationTimestamp"]
14✔
219
                prop.Type = spec.StringOrArray{"string", "null"}
14✔
220
                // mask v1.Time as in validation v1.Time override sting,null type
14✔
221
                prop.Ref = spec.Ref{}
14✔
222
                objectMeta.Properties["creationTimestamp"] = prop
14✔
223
        }
14✔
224

225
        for k, s := range openapispec.Definitions {
5,516✔
226
                // allow nullable statuses
5,502✔
227
                if status, found := s.Properties["status"]; found {
5,838✔
228
                        if !status.Type.Contains("string") {
546✔
229
                                definitionName := strings.Split(status.Ref.GetPointer().String(), "/")[2]
210✔
230
                                object := openapispec.Definitions[definitionName]
210✔
231
                                object.Nullable = true
210✔
232
                                openapispec.Definitions[definitionName] = object
210✔
233
                        }
210✔
234
                }
235

236
                if strings.HasSuffix(k, "Condition") {
5,614✔
237
                        prop := s.Properties["lastProbeTime"]
112✔
238
                        prop.Type = spec.StringOrArray{"string", "null"}
112✔
239
                        prop.Ref = spec.Ref{}
112✔
240
                        s.Properties["lastProbeTime"] = prop
112✔
241

112✔
242
                        prop = s.Properties["lastTransitionTime"]
112✔
243
                        prop.Type = spec.StringOrArray{"string", "null"}
112✔
244
                        prop.Ref = spec.Ref{}
112✔
245
                        s.Properties["lastTransitionTime"] = prop
112✔
246
                }
112✔
247
                if strings.Contains(k, "v1.HTTPGetAction") {
5,516✔
248
                        prop := s.Properties["port"]
14✔
249
                        prop.Type = spec.StringOrArray{"string", "number"}
14✔
250
                        // As intstr.IntOrString, the ref for that must be masked
14✔
251
                        prop.Ref = spec.Ref{}
14✔
252
                        s.Properties["port"] = prop
14✔
253
                }
14✔
254
                if strings.Contains(k, "v1.TCPSocketAction") {
5,516✔
255
                        prop := s.Properties["port"]
14✔
256
                        prop.Type = spec.StringOrArray{"string", "number"}
14✔
257
                        // As intstr.IntOrString, the ref for that must be masked
14✔
258
                        prop.Ref = spec.Ref{}
14✔
259
                        s.Properties["port"] = prop
14✔
260
                }
14✔
261
                if strings.Contains(k, "v1.PersistentVolumeClaimSpec") {
5,516✔
262
                        for i, r := range s.Required {
14✔
UNCOV
263
                                if r == "dataSource" {
×
UNCOV
264
                                        s.Required = append(s.Required[:i], s.Required[i+1:]...)
×
UNCOV
265
                                        openapispec.Definitions[k] = s
×
266
                                        break
×
267
                                }
268
                        }
269
                }
270
        }
271

272
        return openapispec
14✔
273
}
274

275
func CreateOpenAPIValidator(webServices []*restful.WebService) *Validator {
14✔
276
        openapispec := LoadOpenAPISpec(webServices)
14✔
277
        data, err := json.Marshal(openapispec)
14✔
278
        if err != nil {
14✔
UNCOV
279
                log.Print(err)
×
UNCOV
280
                os.Exit(2)
×
UNCOV
281
        }
×
282

283
        specSchema := &openapi_spec.Schema{}
14✔
284
        err = json.Unmarshal(data, specSchema)
14✔
285
        if err != nil {
14✔
UNCOV
286
                panic(err)
×
287
        }
288

289
        // Make sure that no unknown fields are allowed in specs
290
        for k, v := range specSchema.Definitions {
5,516✔
291
                v.AdditionalProperties = &openapi_spec.SchemaOrBool{Allows: false}
5,502✔
292
                v.AdditionalItems = &openapi_spec.SchemaOrBool{Allows: false}
5,502✔
293
                specSchema.Definitions[k] = v
5,502✔
294
        }
5,502✔
295

296
        // Expand the specSchemes
297
        err = openapi_spec.ExpandSchema(specSchema, specSchema, nil)
14✔
298
        if err != nil {
14✔
UNCOV
299
                log.Print(err)
×
UNCOV
300
                os.Exit(2)
×
UNCOV
301
        }
×
302

303
        // Load spec once again for status. The status should accept unknown fields
304
        statusSchema := &openapi_spec.Schema{}
14✔
305
        err = json.Unmarshal(data, statusSchema)
14✔
306
        if err != nil {
14✔
UNCOV
307
                panic(err)
×
308
        }
309

310
        // Expand the statusSchemes
311
        err = openapi_spec.ExpandSchema(statusSchema, statusSchema, nil)
14✔
312
        if err != nil {
14✔
UNCOV
313
                log.Print(err)
×
UNCOV
314
                os.Exit(2)
×
UNCOV
315
        }
×
316

317
        return &Validator{
14✔
318
                specSchemes:   specSchema,
14✔
319
                statusSchemes: statusSchema,
14✔
320
                topLevelKeys: map[string]interface{}{
14✔
321
                        "kind":       nil,
14✔
322
                        "apiVersion": nil,
14✔
323
                        "spec":       nil,
14✔
324
                        "status":     nil,
14✔
325
                        "metadata":   nil,
14✔
326
                },
14✔
327
        }
14✔
328
}
329

330
func (v *Validator) Validate(gvk schema.GroupVersionKind, obj map[string]interface{}) []error {
418✔
331
        errs := []error{}
418✔
332
        for k := range obj {
1,844✔
333
                if _, exists := v.topLevelKeys[k]; !exists {
1,437✔
334
                        errs = append(errs, errors.PropertyNotAllowed("", "body", k))
11✔
335
                }
11✔
336
        }
337

338
        if _, exists := obj["spec"]; !exists {
420✔
339
                errs = append(errs, errors.Required("spec", "body"))
2✔
340
        }
2✔
341

342
        errs = append(errs, v.ValidateSpec(gvk, obj)...)
418✔
343
        errs = append(errs, v.ValidateStatus(gvk, obj)...)
418✔
344
        return errs
418✔
345
}
346

347
func (v *Validator) ValidateSpec(gvk schema.GroupVersionKind, obj map[string]interface{}) []error {
421✔
348
        schema := v.specSchemes.Definitions["v1."+gvk.Kind+"Spec"]
421✔
349
        result := openapi_validate.NewSchemaValidator(&schema, nil, "spec", strfmt.Default).Validate(obj["spec"])
421✔
350
        return result.Errors
421✔
351
}
421✔
352

353
func (v *Validator) ValidateStatus(gvk schema.GroupVersionKind, obj map[string]interface{}) []error {
421✔
354
        schema := v.statusSchemes.Definitions["v1."+gvk.Kind+"Status"]
421✔
355
        result := openapi_validate.NewSchemaValidator(&schema, nil, "status", strfmt.Default).Validate(obj["status"])
421✔
356
        return result.Errors
421✔
357
}
421✔
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