Skip to content

Commit 3787799

Browse files
author
Roman Sysoev
committed
fix(vi): validate storage class
Currently, only storage classes with Block volume mode and RWX access mode can be used. Signed-off-by: Roman Sysoev <[email protected]>
1 parent 49fee43 commit 3787799

File tree

10 files changed

+487
-10
lines changed

10 files changed

+487
-10
lines changed

images/virtualization-artifact/pkg/controller/moduleconfig/moduleconfig_webhook.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func NewModuleConfigValidator(client client.Client) *validator.Validator[*mcapi.
4646

4747
cidrs := newCIDRsValidator(client)
4848
reduceCIDRs := newRemoveCIDRsValidator(client)
49+
// viStorageClasses := newViStorageClassValidator(client)
4950

5051
return validator.NewValidator[*mcapi.ModuleConfig](logger).
5152
WithPredicate(&validator.Predicate[*mcapi.ModuleConfig]{
@@ -54,5 +55,6 @@ func NewModuleConfigValidator(client client.Client) *validator.Validator[*mcapi.
5455
oldMC.GetGeneration() != newMC.GetGeneration()
5556
},
5657
}).
58+
// WithUpdateValidators(cidrs, reduceCIDRs, viStorageClasses)
5759
WithUpdateValidators(cidrs, reduceCIDRs)
5860
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
Copyright 2025 Flant JSC
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+
17+
package moduleconfig
18+
19+
// import (
20+
// "context"
21+
// "fmt"
22+
// "slices"
23+
24+
// corev1 "k8s.io/api/core/v1"
25+
// cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
26+
// "sigs.k8s.io/controller-runtime/pkg/client"
27+
// "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
28+
29+
// mcapi "github.com/deckhouse/virtualization-controller/pkg/controller/moduleconfig/api"
30+
// )
31+
32+
// type viStorageClassValidator struct {
33+
// client client.Client
34+
// }
35+
36+
// func newViStorageClassValidator(client client.Client) *viStorageClassValidator {
37+
// return &viStorageClassValidator{
38+
// client: client,
39+
// }
40+
// }
41+
42+
// func (v viStorageClassValidator) ValidateUpdate(ctx context.Context, _, newMC *mcapi.ModuleConfig) (admission.Warnings, error) {
43+
// warnings := make([]string, 0)
44+
45+
// viScSettings := parseViStorageClass(newMC.Spec.Settings)
46+
// if viScSettings.DefaultStorageClassName != "" {
47+
// scWarnings, err := v.validateStorageClass(ctx, viScSettings.DefaultStorageClassName)
48+
// if err != nil {
49+
// return warnings, err
50+
// }
51+
// if len(scWarnings) != 0 {
52+
// warnings = append(warnings, scWarnings...)
53+
// }
54+
// }
55+
56+
// if len(viScSettings.AllowedStorageClassSelector.MatchNames) != 0 {
57+
// for _, sc := range viScSettings.AllowedStorageClassSelector.MatchNames {
58+
// scWarnings, err := v.validateStorageClass(ctx, sc)
59+
// if err != nil {
60+
// return warnings, err
61+
// }
62+
// if len(scWarnings) != 0 {
63+
// warnings = append(warnings, scWarnings...)
64+
// }
65+
// }
66+
// }
67+
68+
// return admission.Warnings{}, nil
69+
// }
70+
71+
// func (v viStorageClassValidator) validateStorageClass(ctx context.Context, scName string) (admission.Warnings, error) {
72+
// scProfile := &cdiv1.StorageProfile{}
73+
// err := v.client.Get(ctx, client.ObjectKey{Name: scName}, scProfile, &client.GetOptions{})
74+
// if err != nil {
75+
// return admission.Warnings{}, fmt.Errorf("failed to obtain the `StorageProfile` %s: %w", scName, err)
76+
// }
77+
// if len(scProfile.Status.ClaimPropertySets) == 0 {
78+
// return admission.Warnings{}, fmt.Errorf("failed to validate the `PersistentVolumeMode` of the `StorageProfile`: %s", scName)
79+
// }
80+
81+
// var valid bool
82+
// for _, cps := range scProfile.Spec.ClaimPropertySets {
83+
// if slices.Contains(cps.AccessModes, corev1.ReadWriteMany) && *cps.VolumeMode == corev1.PersistentVolumeBlock {
84+
// valid = true
85+
// }
86+
// }
87+
88+
// if !valid {
89+
// return admission.Warnings{}, fmt.Errorf("the storage class %q is not supported in the current version due to known compatibility issues with virtual images; please choose a different storage class", scName)
90+
// }
91+
92+
// return admission.Warnings{}, nil
93+
// }
94+
95+
// type viStorageClassSettings struct {
96+
// DefaultStorageClassName string
97+
// AllowedStorageClassSelector AllowedStorageClassSelector
98+
// }
99+
100+
// type AllowedStorageClassSelector struct {
101+
// MatchNames []string
102+
// }
103+
104+
// func parseViStorageClass(settings mcapi.SettingsValues) *viStorageClassSettings {
105+
// viScSettings := &viStorageClassSettings{}
106+
// if virtualImages, ok := settings["virtualImages"].(map[string]interface{}); ok {
107+
// if defaultClass, ok := virtualImages["defaultStorageClassName"].(string); ok {
108+
// viScSettings.DefaultStorageClassName = defaultClass
109+
// }
110+
111+
// if allowedSelector, ok := virtualImages["allowedStorageClassSelector"].(map[string]interface{}); ok {
112+
// if matchNames, ok := allowedSelector["matchNames"].([]interface{}); ok {
113+
// var matchNameStrings []string
114+
// for _, name := range matchNames {
115+
// if strName, ok := name.(string); ok {
116+
// matchNameStrings = append(matchNameStrings, strName)
117+
// }
118+
// }
119+
// viScSettings.AllowedStorageClassSelector.MatchNames = matchNameStrings
120+
// }
121+
// }
122+
// }
123+
124+
// return viScSettings
125+
// }

images/virtualization-artifact/pkg/controller/service/base_storage_class_service.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
corev1 "k8s.io/api/core/v1"
2424
storagev1 "k8s.io/api/storage/v1"
2525
"k8s.io/apimachinery/pkg/types"
26+
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
2728

2829
"github.com/deckhouse/virtualization-controller/pkg/common/annotations"
@@ -79,3 +80,7 @@ func (s BaseStorageClassService) GetPersistentVolumeClaim(ctx context.Context, s
7980
func (s BaseStorageClassService) IsStorageClassDeprecated(sc *storagev1.StorageClass) bool {
8081
return sc != nil && sc.Labels["module"] == "local-path-provisioner"
8182
}
83+
84+
func (s BaseStorageClassService) GetStorageProfile(ctx context.Context, name string) (*cdiv1.StorageProfile, error) {
85+
return object.FetchObject(ctx, types.NamespacedName{Name: name}, s.client, &cdiv1.StorageProfile{})
86+
}

images/virtualization-artifact/pkg/controller/vi/internal/interfaces.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
corev1 "k8s.io/api/core/v1"
2323
storagev1 "k8s.io/api/storage/v1"
24+
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
2425

2526
"github.com/deckhouse/virtualization-controller/pkg/controller/supplements"
2627
"github.com/deckhouse/virtualization-controller/pkg/controller/vi/internal/source"
@@ -45,6 +46,8 @@ type StorageClassService interface {
4546
GetModuleStorageClass(ctx context.Context) (*storagev1.StorageClass, error)
4647
GetDefaultStorageClass(ctx context.Context) (*storagev1.StorageClass, error)
4748
GetStorageClass(ctx context.Context, sc string) (*storagev1.StorageClass, error)
49+
GetStorageProfile(ctx context.Context, name string) (*cdiv1.StorageProfile, error)
4850
GetPersistentVolumeClaim(ctx context.Context, sup *supplements.Generator) (*corev1.PersistentVolumeClaim, error)
4951
IsStorageClassDeprecated(sc *storagev1.StorageClass) bool
52+
ValidateClaimPropertySets(sp *cdiv1.StorageProfile) error
5053
}

images/virtualization-artifact/pkg/controller/vi/internal/mock.go

Lines changed: 101 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

images/virtualization-artifact/pkg/controller/vi/internal/service/vi_storage_class_service.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ package service
1919
import (
2020
"context"
2121
"errors"
22+
"fmt"
2223
"slices"
2324

25+
corev1 "k8s.io/api/core/v1"
2426
storev1 "k8s.io/api/storage/v1"
27+
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
2528

2629
"github.com/deckhouse/virtualization-controller/pkg/config"
2730
"github.com/deckhouse/virtualization-controller/pkg/controller/service"
@@ -117,3 +120,17 @@ func (svc *VirtualImageStorageClassService) IsStorageClassAllowed(scName string)
117120
func (svc *VirtualImageStorageClassService) GetModuleStorageClass(ctx context.Context) (*storev1.StorageClass, error) {
118121
return svc.GetStorageClass(ctx, svc.storageClassSettings.DefaultStorageClassName)
119122
}
123+
124+
func (svc *VirtualImageStorageClassService) ValidateClaimPropertySets(sp *cdiv1.StorageProfile) error {
125+
if sp == nil {
126+
return fmt.Errorf("the storage profile cannot be nil; please report a bug")
127+
}
128+
129+
for _, cps := range sp.Status.ClaimPropertySets {
130+
if slices.Contains(cps.AccessModes, corev1.ReadWriteMany) && *cps.VolumeMode == corev1.PersistentVolumeBlock {
131+
return nil
132+
}
133+
}
134+
135+
return fmt.Errorf("the storage class %q is not supported in the current version due to known compatibility issues with virtual images; please choose a different storage class", sp.Name)
136+
}

0 commit comments

Comments
 (0)