Skip to content

Commit d08337f

Browse files
authored
fix(vm): fix vm pod placement for local disks with hotplug volumes (#2363)
Signed-off-by: Valeriy Khorunzhin <valeriy.khorunzhin@flant.com>
1 parent 409364d commit d08337f

7 files changed

Lines changed: 491 additions & 41 deletions

File tree

images/virtualization-artifact/pkg/common/vd/vd.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package vd
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223
"log/slog"
2324

@@ -27,6 +28,7 @@ import (
2728
"sigs.k8s.io/controller-runtime/pkg/client"
2829

2930
"github.com/deckhouse/virtualization-controller/pkg/common/object"
31+
"github.com/deckhouse/virtualization-controller/pkg/controller/service"
3032
"github.com/deckhouse/virtualization-controller/pkg/featuregates"
3133
"github.com/deckhouse/virtualization/api/core/v1alpha2"
3234
)
@@ -75,6 +77,53 @@ func StorageClassChanged(vd *v1alpha2.VirtualDisk) bool {
7577
return *specSc != "" && statusSc != ""
7678
}
7779

80+
type VirtualDiskStorageClassResolver interface {
81+
GetModuleStorageClass(ctx context.Context) (*storagev1.StorageClass, error)
82+
GetDefaultStorageClass(ctx context.Context) (*storagev1.StorageClass, error)
83+
}
84+
85+
// ResolveStorageClassName resolves storage class name for a VirtualDisk
86+
// with the same precedence as VD handlers:
87+
// 1. vd.Status.StorageClassName
88+
// 2. vd.Spec.PersistentVolumeClaim.StorageClass
89+
// 3. module default storage class (if resolver is provided)
90+
// 4. cluster default storage class (if resolver is provided)
91+
func ResolveStorageClassName(ctx context.Context, vd *v1alpha2.VirtualDisk, resolver VirtualDiskStorageClassResolver) (string, error) {
92+
if vd == nil {
93+
return "", nil
94+
}
95+
96+
if vd.Status.StorageClassName != "" {
97+
return vd.Status.StorageClassName, nil
98+
}
99+
100+
if vd.Spec.PersistentVolumeClaim.StorageClass != nil && *vd.Spec.PersistentVolumeClaim.StorageClass != "" {
101+
return *vd.Spec.PersistentVolumeClaim.StorageClass, nil
102+
}
103+
104+
if resolver == nil {
105+
return "", nil
106+
}
107+
108+
moduleStorageClass, err := resolver.GetModuleStorageClass(ctx)
109+
if err != nil {
110+
return "", err
111+
}
112+
if moduleStorageClass != nil {
113+
return moduleStorageClass.Name, nil
114+
}
115+
116+
defaultStorageClass, err := resolver.GetDefaultStorageClass(ctx)
117+
if err != nil && !errors.Is(err, service.ErrDefaultStorageClassNotFound) {
118+
return "", err
119+
}
120+
if defaultStorageClass != nil {
121+
return defaultStorageClass.Name, nil
122+
}
123+
124+
return "", fmt.Errorf("storage class for VirtualDisk %q cannot be determined", vd.Name)
125+
}
126+
78127
func ValidateVirtualImageStorageClassProvisionerCompatibility(ctx context.Context, vd *v1alpha2.VirtualDisk, client client.Client) error {
79128
if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != v1alpha2.DataSourceTypeObjectRef {
80129
return nil

images/virtualization-artifact/pkg/controller/indexer/indexer.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const (
7171

7272
IndexFieldEventByInvolvedObjectName = "involvedObject.name"
7373
IndexFieldEventByInvolvedObjectKind = "involvedObject.kind"
74+
IndexFieldPVByStorageClass = "spec.storageClassName"
7475
IndexFieldUSBDeviceByName = "metadata.name"
7576
IndexFieldNodeUSBDeviceByAssignedNamespace = "spec.assignedNamespace"
7677

@@ -104,6 +105,7 @@ var IndexGetters = []IndexGetter{
104105
IndexVMIPLeaseByVMIP,
105106
IndexEventByInvolvedObjectName,
106107
IndexEventByInvolvedObjectKind,
108+
IndexPVByStorageClass,
107109
}
108110

109111
var IndexGettersUSB = []IndexGetter{
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Copyright 2026 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 indexer
18+
19+
import (
20+
corev1 "k8s.io/api/core/v1"
21+
"sigs.k8s.io/controller-runtime/pkg/client"
22+
)
23+
24+
func IndexPVByStorageClass() (obj client.Object, field string, extractValue client.IndexerFunc) {
25+
return &corev1.PersistentVolume{}, IndexFieldPVByStorageClass, func(object client.Object) []string {
26+
pv, ok := object.(*corev1.PersistentVolume)
27+
if !ok || pv == nil || pv.Spec.StorageClassName == "" {
28+
return nil
29+
}
30+
return []string{pv.Spec.StorageClassName}
31+
}
32+
}

images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,13 @@ package validator
1818

1919
import (
2020
"context"
21-
"errors"
22-
"fmt"
2321
"reflect"
2422

2523
"sigs.k8s.io/controller-runtime/pkg/client"
2624
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
2725

2826
commonvd "github.com/deckhouse/virtualization-controller/pkg/common/vd"
2927
"github.com/deckhouse/virtualization-controller/pkg/controller/conditions"
30-
"github.com/deckhouse/virtualization-controller/pkg/controller/service"
3128
intsvc "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/service"
3229
"github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/source"
3330
"github.com/deckhouse/virtualization/api/core/v1alpha2"
@@ -47,7 +44,7 @@ func NewVirtualImagePVCStorageClassValidator(client client.Client, scService *in
4744
}
4845

4946
func (v *VirtualImagePVCStorageClassValidator) ValidateCreate(ctx context.Context, vd *v1alpha2.VirtualDisk) (admission.Warnings, error) {
50-
scName, err := v.extractVDStorageClassName(ctx, vd)
47+
scName, err := commonvd.ResolveStorageClassName(ctx, vd, v.scService)
5148
if err != nil {
5249
return nil, err
5350
}
@@ -70,33 +67,3 @@ func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Contex
7067

7168
return nil, commonvd.ValidateVirtualImageStorageClassProvisionerCompatibility(ctx, newVD, v.client)
7269
}
73-
74-
func (v *VirtualImagePVCStorageClassValidator) extractVDStorageClassName(ctx context.Context, vd *v1alpha2.VirtualDisk) (string, error) {
75-
if vd.Status.StorageClassName != "" {
76-
return vd.Status.StorageClassName, nil
77-
}
78-
79-
if vd.Spec.PersistentVolumeClaim.StorageClass != nil {
80-
return *vd.Spec.PersistentVolumeClaim.StorageClass, nil
81-
}
82-
83-
moduleStorageClass, err := v.scService.GetModuleStorageClass(ctx)
84-
if err != nil {
85-
return "", err
86-
}
87-
88-
if moduleStorageClass != nil {
89-
return moduleStorageClass.Name, nil
90-
}
91-
92-
defaultStorageClass, err := v.scService.GetDefaultStorageClass(ctx)
93-
if err != nil && !errors.Is(err, service.ErrDefaultStorageClassNotFound) {
94-
return "", err
95-
}
96-
97-
if defaultStorageClass != nil {
98-
return defaultStorageClass.Name, nil
99-
}
100-
101-
return "", fmt.Errorf("storage class for VirtualDisk %q cannot be determined", vd.Name)
102-
}

0 commit comments

Comments
 (0)