Skip to content

Commit d59b6c6

Browse files
authored
fix(vd,vi): fix issues with storage class condition display (#947)
1. The Start and StartImmediate methods of DiskService now take a *storagev1.StorageClass instead of a string as the storage class parameter. 2. The global services VirtualImageStorageClassService and VirtualDiskStorageClassService have been moved to the internal/service package of the corresponding resource. 3. All checks related to StorageClass are now performed exclusively in the StorageClassReady handlers. The LifeCycle handler (and all source handlers) now rely on the StorageClassReady condition and no longer perform redundant checks. 4. A new service, BaseStorageClassService, which integrates internal storage class services in order to use the base logic for working with StorageClass. 5. Refactored LifeCycle for vi similarly to how it’s done for vd. Signed-off-by: Isteb4k <dmitry.rakitin@flant.com>
1 parent ba1a3b4 commit d59b6c6

38 files changed

Lines changed: 558 additions & 682 deletions

api/core/v1alpha2/events.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,11 @@ const (
6464
// ReasonVMOPInProgress is event reason that the operation is in progress
6565
ReasonVMOPInProgress = "VirtualMachineOperationInProgress"
6666

67-
// ReasonVDStorageClassWasDeleted is event reason that VDStorageClass was deleted.
68-
ReasonVDStorageClassWasDeleted = "VirtualDiskStorageClassWasDeleted"
69-
// ReasonVDStorageClassNotFound is event reason that VDStorageClass not found.
70-
ReasonVDStorageClassNotFound = "VirtualDiskStorageClassNotFound"
71-
// ReasonVDSpecChanged is event reason that VDStorageClass is chanded.
72-
ReasonVDSpecChanged = "VirtualDiskSpecChanged"
67+
// ReasonVDSpecHasBeenChanged is event reason that spec of virtual disk has been changed.
68+
ReasonVDSpecHasBeenChanged = "VirtualDiskSpecHasBeenChanged"
69+
// ReasonVISpecHasBeenChanged is event reason that spec of virtual image has been changed.
70+
ReasonVISpecHasBeenChanged = "VirtualImageSpecHasBeenChanged"
71+
7372
// ReasonVDContainerRegistrySecretNotFound is event reason that VDContainerRegistrySecret not found.
7473
ReasonVDContainerRegistrySecretNotFound = "VirtualDiskContainerRegistrySecretNotFound"
7574

api/core/v1alpha2/vicondition/condition.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ const (
8787
QuotaExceeded ReadyReason = "QuotaExceeded"
8888
// ImagePullFailed indicates that there was an issue with importing from DVCR.
8989
ImagePullFailed ReadyReason = "ImagePullFailed"
90+
// DatasourceNotReady indicates that the datasource is not ready, which prevents the import process from starting.
91+
DatasourceNotReady ReadyReason = "DatasourceNotReady"
9092

9193
// Lost indicates that the underlying PersistentVolumeClaim has been lost and the `VirtualImage` can no longer be used.
9294
Lost ReadyReason = "PVCLost"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 service
18+
19+
import (
20+
"context"
21+
"sort"
22+
23+
corev1 "k8s.io/api/core/v1"
24+
storagev1 "k8s.io/api/storage/v1"
25+
"k8s.io/apimachinery/pkg/types"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
28+
"github.com/deckhouse/virtualization-controller/pkg/common/annotations"
29+
"github.com/deckhouse/virtualization-controller/pkg/common/object"
30+
"github.com/deckhouse/virtualization-controller/pkg/controller/supplements"
31+
)
32+
33+
type BaseStorageClassService struct {
34+
client client.Client
35+
}
36+
37+
func NewBaseStorageClassService(client client.Client) *BaseStorageClassService {
38+
return &BaseStorageClassService{client: client}
39+
}
40+
41+
func (s BaseStorageClassService) GetDefaultStorageClass(ctx context.Context) (*storagev1.StorageClass, error) {
42+
var scs storagev1.StorageClassList
43+
err := s.client.List(ctx, &scs, &client.ListOptions{})
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
var defaultClasses []*storagev1.StorageClass
49+
for idx := range scs.Items {
50+
if scs.Items[idx].Annotations[annotations.AnnDefaultStorageClass] == "true" {
51+
defaultClasses = append(defaultClasses, &scs.Items[idx])
52+
}
53+
}
54+
55+
if len(defaultClasses) == 0 {
56+
return nil, ErrDefaultStorageClassNotFound
57+
}
58+
59+
// Primary sort by creation timestamp, newest first.
60+
// Secondary sort by class name, ascending order.
61+
sort.Slice(defaultClasses, func(i, j int) bool {
62+
if defaultClasses[i].CreationTimestamp.UnixNano() == defaultClasses[j].CreationTimestamp.UnixNano() {
63+
return defaultClasses[i].Name < defaultClasses[j].Name
64+
}
65+
return defaultClasses[i].CreationTimestamp.UnixNano() > defaultClasses[j].CreationTimestamp.UnixNano()
66+
})
67+
68+
return defaultClasses[0], nil
69+
}
70+
71+
func (s BaseStorageClassService) GetStorageClass(ctx context.Context, scName string) (*storagev1.StorageClass, error) {
72+
return object.FetchObject(ctx, types.NamespacedName{Name: scName}, s.client, &storagev1.StorageClass{})
73+
}
74+
75+
func (s BaseStorageClassService) GetPersistentVolumeClaim(ctx context.Context, sup *supplements.Generator) (*corev1.PersistentVolumeClaim, error) {
76+
return object.FetchObject(ctx, sup.PersistentVolumeClaim(), s.client, &corev1.PersistentVolumeClaim{})
77+
}

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

Lines changed: 20 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ import (
2222
"errors"
2323
"fmt"
2424
"slices"
25-
"sort"
2625
"strconv"
2726
"strings"
2827

2928
vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
3029
corev1 "k8s.io/api/core/v1"
31-
storev1 "k8s.io/api/storage/v1"
30+
storagev1 "k8s.io/api/storage/v1"
3231
k8serrors "k8s.io/apimachinery/pkg/api/errors"
3332
"k8s.io/apimachinery/pkg/api/resource"
3433
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -73,12 +72,16 @@ func NewDiskService(
7372
func (s DiskService) Start(
7473
ctx context.Context,
7574
pvcSize resource.Quantity,
76-
storageClass *string,
75+
sc *storagev1.StorageClass,
7776
source *cdiv1.DataVolumeSource,
7877
obj ObjectKind,
7978
sup *supplements.Generator,
8079
opts ...Option,
8180
) error {
81+
if sc == nil {
82+
return errors.New("cannot create DataVolume: StorageClass must not be nil")
83+
}
84+
8285
dvBuilder := kvbuilder.NewDV(sup.DataVolume())
8386
dvBuilder.SetDataSource(source)
8487
dvBuilder.SetOwnerRef(obj, obj.GroupVersionKind())
@@ -95,17 +98,12 @@ func (s DiskService) Start(
9598
}
9699
}
97100

98-
sc, err := s.GetStorageClass(ctx, storageClass)
99-
if err != nil {
100-
return fmt.Errorf("get storage class: %w", err)
101-
}
102-
103101
volumeMode, accessMode, err := s.GetVolumeAndAccessModes(ctx, sc)
104102
if err != nil {
105103
return fmt.Errorf("get volume and access modes: %w", err)
106104
}
107105

108-
dvBuilder.SetPVC(storageClass, pvcSize, accessMode, volumeMode)
106+
dvBuilder.SetPVC(&sc.Name, pvcSize, accessMode, volumeMode)
109107

110108
if s.isImmediateBindingMode(sc) {
111109
dvBuilder.SetImmediate()
@@ -129,7 +127,7 @@ func (s DiskService) Start(
129127
return supplements.EnsureForDataVolume(ctx, s.client, sup, dvBuilder.GetResource(), s.dvcrSettings)
130128
}
131129

132-
func (s DiskService) GetVolumeAndAccessModes(ctx context.Context, sc *storev1.StorageClass) (corev1.PersistentVolumeMode, corev1.PersistentVolumeAccessMode, error) {
130+
func (s DiskService) GetVolumeAndAccessModes(ctx context.Context, sc *storagev1.StorageClass) (corev1.PersistentVolumeMode, corev1.PersistentVolumeAccessMode, error) {
133131
if sc == nil {
134132
return "", "", errors.New("storage class is nil")
135133
}
@@ -163,14 +161,13 @@ func (s DiskService) GetVolumeAndAccessModes(ctx context.Context, sc *storev1.St
163161
func (s DiskService) StartImmediate(
164162
ctx context.Context,
165163
pvcSize resource.Quantity,
166-
storageClass *string,
164+
sc *storagev1.StorageClass,
167165
source *cdiv1.DataVolumeSource,
168166
obj ObjectKind,
169167
sup *supplements.Generator,
170168
) error {
171-
sc, err := s.GetStorageClass(ctx, storageClass)
172-
if err != nil {
173-
return err
169+
if sc == nil {
170+
return errors.New("cannot create DataVolume: StorageClass must not be nil")
174171
}
175172

176173
dvBuilder := kvbuilder.NewDV(sup.DataVolume())
@@ -180,7 +177,7 @@ func (s DiskService) StartImmediate(
180177
dvBuilder.SetImmediate()
181178
dv := dvBuilder.GetResource()
182179

183-
err = s.client.Create(ctx, dv)
180+
err := s.client.Create(ctx, dv)
184181
if err != nil && !k8serrors.IsAlreadyExists(err) {
185182
return err
186183
}
@@ -455,7 +452,7 @@ func getAccessModeMax(modes []corev1.PersistentVolumeAccessMode) corev1.Persiste
455452
return m
456453
}
457454

458-
func (s DiskService) parseVolumeMode(sc *storev1.StorageClass) (corev1.PersistentVolumeMode, bool) {
455+
func (s DiskService) parseVolumeMode(sc *storagev1.StorageClass) (corev1.PersistentVolumeMode, bool) {
459456
if sc == nil {
460457
return "", false
461458
}
@@ -469,7 +466,7 @@ func (s DiskService) parseVolumeMode(sc *storev1.StorageClass) (corev1.Persisten
469466
}
470467
}
471468

472-
func (s DiskService) parseAccessMode(sc *storev1.StorageClass) (corev1.PersistentVolumeAccessMode, bool) {
469+
func (s DiskService) parseAccessMode(sc *storagev1.StorageClass) (corev1.PersistentVolumeAccessMode, bool) {
473470
if sc == nil {
474471
return "", false
475472
}
@@ -483,11 +480,11 @@ func (s DiskService) parseAccessMode(sc *storev1.StorageClass) (corev1.Persisten
483480
}
484481
}
485482

486-
func (s DiskService) isImmediateBindingMode(sc *storev1.StorageClass) bool {
483+
func (s DiskService) isImmediateBindingMode(sc *storagev1.StorageClass) bool {
487484
if sc == nil {
488485
return false
489486
}
490-
return sc.GetAnnotations()[annotations.AnnVirtualDiskBindingMode] == string(storev1.VolumeBindingImmediate)
487+
return sc.GetAnnotations()[annotations.AnnVirtualDiskBindingMode] == string(storagev1.VolumeBindingImmediate)
491488
}
492489

493490
func (s DiskService) parseStorageCapabilities(status cdiv1.StorageProfileStatus) StorageCapabilities {
@@ -521,6 +518,10 @@ func (s DiskService) parseStorageCapabilities(status cdiv1.StorageProfileStatus)
521518
return storageCapabilities[len(storageCapabilities)-1]
522519
}
523520

521+
func (s DiskService) GetStorageClass(ctx context.Context, scName string) (*storagev1.StorageClass, error) {
522+
return object.FetchObject(ctx, types.NamespacedName{Name: scName}, s.client, &storagev1.StorageClass{})
523+
}
524+
524525
func (s DiskService) GetDataVolume(ctx context.Context, sup *supplements.Generator) (*cdiv1.DataVolume, error) {
525526
return object.FetchObject(ctx, sup.DataVolume(), s.client, &cdiv1.DataVolume{})
526527
}
@@ -600,59 +601,6 @@ func (s DiskService) CheckImportProcess(ctx context.Context, dv *cdiv1.DataVolum
600601
return nil
601602
}
602603

603-
func (s DiskService) GetStorageClass(ctx context.Context, storageClassName *string) (*storev1.StorageClass, error) {
604-
if storageClassName == nil || *storageClassName == "" {
605-
return s.GetDefaultStorageClass(ctx)
606-
}
607-
return s.getStorageClass(ctx, *storageClassName)
608-
}
609-
610-
func (s DiskService) GetDefaultStorageClass(ctx context.Context) (*storev1.StorageClass, error) {
611-
var scs storev1.StorageClassList
612-
err := s.client.List(ctx, &scs, &client.ListOptions{})
613-
if err != nil {
614-
return nil, err
615-
}
616-
617-
var defaultClasses []*storev1.StorageClass
618-
for idx := range scs.Items {
619-
if scs.Items[idx].Annotations[annotations.AnnDefaultStorageClass] == "true" {
620-
defaultClasses = append(defaultClasses, &scs.Items[idx])
621-
}
622-
}
623-
624-
if len(defaultClasses) == 0 {
625-
return nil, ErrDefaultStorageClassNotFound
626-
}
627-
628-
// Primary sort by creation timestamp, newest first
629-
// Secondary sort by class name, ascending order
630-
sort.Slice(defaultClasses, func(i, j int) bool {
631-
if defaultClasses[i].CreationTimestamp.UnixNano() == defaultClasses[j].CreationTimestamp.UnixNano() {
632-
return defaultClasses[i].Name < defaultClasses[j].Name
633-
}
634-
return defaultClasses[i].CreationTimestamp.UnixNano() > defaultClasses[j].CreationTimestamp.UnixNano()
635-
})
636-
637-
return defaultClasses[0], nil
638-
}
639-
640-
func (s DiskService) getStorageClass(ctx context.Context, storageClassName string) (*storev1.StorageClass, error) {
641-
var sc storev1.StorageClass
642-
err := s.client.Get(ctx, types.NamespacedName{
643-
Name: storageClassName,
644-
}, &sc, &client.GetOptions{})
645-
if err != nil {
646-
if k8serrors.IsNotFound(err) {
647-
return nil, ErrStorageClassNotFound
648-
}
649-
650-
return nil, err
651-
}
652-
653-
return &sc, nil
654-
}
655-
656604
var ErrInsufficientPVCSize = errors.New("the specified pvc size is insufficient")
657605

658606
func GetValidatedPVCSize(pvcSize *resource.Quantity, requiredSize resource.Quantity) (resource.Quantity, error) {

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

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,11 @@ package service
1818

1919
import (
2020
"errors"
21-
"fmt"
2221
)
2322

2423
var (
25-
ErrStorageClassNotFound = errors.New("storage class not found")
2624
ErrStorageProfileNotFound = errors.New("storage profile not found")
2725
ErrDefaultStorageClassNotFound = errors.New("default storage class not found")
28-
ErrStorageClassNotAllowed = errors.New("storage class not allowed")
2926
ErrDataVolumeNotRunning = errors.New("pvc importer is not running")
3027
ErrDataVolumeProvisionerUnschedulable = errors.New("provisioner unschedulable")
3128
)
@@ -34,31 +31,3 @@ var (
3431
ErrIPAddressAlreadyExist = errors.New("the IP address is already allocated")
3532
ErrIPAddressOutOfRange = errors.New("the IP address is out of range")
3633
)
37-
38-
type VirtualDiskUsedByImageError struct {
39-
vdName string
40-
}
41-
42-
func (e VirtualDiskUsedByImageError) Error() string {
43-
return fmt.Sprintf("the virtual disk %q already used by creating image", e.vdName)
44-
}
45-
46-
func NewVirtualDiskUsedByImageError(name string) error {
47-
return VirtualDiskUsedByImageError{
48-
vdName: name,
49-
}
50-
}
51-
52-
type VirtualDiskUsedByVirtualMachineError struct {
53-
vdName string
54-
}
55-
56-
func (e VirtualDiskUsedByVirtualMachineError) Error() string {
57-
return fmt.Sprintf("the virtual disk %q already used by running virtual machine", e.vdName)
58-
}
59-
60-
func NewVirtualDiskUsedByVirtualMachineError(name string) error {
61-
return VirtualDiskUsedByVirtualMachineError{
62-
vdName: name,
63-
}
64-
}

0 commit comments

Comments
 (0)