diff --git a/images/virtualization-artifact/pkg/controller/indexer/indexer.go b/images/virtualization-artifact/pkg/controller/indexer/indexer.go index af409e42ec..d95cf19af8 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/indexer.go @@ -41,8 +41,9 @@ const ( IndexFieldVIByVDSnapshot = "vi,spec.DataSource.ObjectRef.Name,.Kind=VirtualDiskSnapshot" IndexFieldCVIByVDSnapshot = "cvi,spec.DataSource.ObjectRef.Name,.Kind=VirtualDiskSnapshot" - IndexFieldVDByStorageClass = "vd.spec.PersistentVolumeClaim.StorageClass" - IndexFieldVIByStorageClass = "vi.spec.PersistentVolumeClaim.StorageClass" + IndexFieldVDByStorageClass = "vd.spec.PersistentVolumeClaim.StorageClass" + IndexFieldVIByStorageClass = "vi.spec.PersistentVolumeClaim.StorageClass" + IndexFieldVIByNotReadyStorageClass = "vi,status.conditions[].type,StorageClassReady" IndexFieldVMSnapshotByVM = "spec.virtualMachineName" IndexFieldVMSnapshotByVDSnapshot = "status.virtualDiskSnapshotNames" @@ -72,6 +73,7 @@ func IndexALL(ctx context.Context, mgr manager.Manager) error { IndexVDByStorageClass, IndexVIByVDSnapshot, IndexVIByStorageClass, + IndexVIByNotReadyStorageClass, IndexCVIByVDSnapshot, IndexVMIPByAddress, IndexVMBDAByVM, diff --git a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go index 0415d33538..48507c7c6b 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go @@ -19,10 +19,12 @@ package indexer import ( "context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) func IndexVIByVDSnapshot(ctx context.Context, mgr manager.Manager) error { @@ -61,3 +63,20 @@ func IndexVIByStorageClass(ctx context.Context, mgr manager.Manager) error { } }) } + +func IndexVIByNotReadyStorageClass(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByNotReadyStorageClass, func(object client.Object) []string { + vi, ok := object.(*virtv2.VirtualImage) + if !ok || vi == nil { + return nil + } + + for _, condition := range vi.Status.Conditions { + if condition.Type == string(vicondition.StorageClassReadyType) && condition.Status == metav1.ConditionTrue { + return []string{condition.Type} + } + } + + return nil + }) +} diff --git a/images/virtualization-artifact/pkg/controller/service/disk_service.go b/images/virtualization-artifact/pkg/controller/service/disk_service.go index 1b6524d107..821b3e4952 100644 --- a/images/virtualization-artifact/pkg/controller/service/disk_service.go +++ b/images/virtualization-artifact/pkg/controller/service/disk_service.go @@ -44,6 +44,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/common/pointer" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/kvbuilder" + mcapi "github.com/deckhouse/virtualization-controller/pkg/controller/moduleconfig/api" "github.com/deckhouse/virtualization-controller/pkg/controller/supplements" "github.com/deckhouse/virtualization-controller/pkg/dvcr" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -608,8 +609,32 @@ func (s DiskService) GetStorageClass(ctx context.Context, storageClassName *stri } func (s DiskService) GetDefaultStorageClass(ctx context.Context) (*storev1.StorageClass, error) { + var ( + moduleConfigViDefaultStorageClass string + moduleConfig mcapi.ModuleConfig + moduleConfigName = "virtualization" + ) + err := s.client.Get(ctx, types.NamespacedName{Name: moduleConfigName}, &moduleConfig, &client.GetOptions{}) + if err != nil { + return nil, err + } + + if virtualImages, ok := moduleConfig.Spec.Settings["virtualImages"].(map[string]interface{}); ok { + if defaultClass, ok := virtualImages["defaultStorageClassName"].(string); ok { + moduleConfigViDefaultStorageClass = defaultClass + } + } + + if moduleConfigViDefaultStorageClass != "" { + moduleConfigViDefaultStorageClassObj, err := s.getStorageClass(ctx, moduleConfigViDefaultStorageClass) + if err != nil { + return nil, err + } + return moduleConfigViDefaultStorageClassObj, nil + } + var scs storev1.StorageClassList - err := s.client.List(ctx, &scs, &client.ListOptions{}) + err = s.client.List(ctx, &scs, &client.ListOptions{}) if err != nil { return nil, err } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/moduleconfig_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/moduleconfig_watcher.go new file mode 100644 index 0000000000..231754f4c4 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/moduleconfig_watcher.go @@ -0,0 +1,107 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package watcher + +import ( + "context" + "fmt" + "log/slog" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + mcapi "github.com/deckhouse/virtualization-controller/pkg/controller/moduleconfig/api" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" +) + +type ModuleConfigWatcher struct { + client client.Client + logger *slog.Logger +} + +func NewModuleConfigWatcher(client client.Client) *StorageClassWatcher { + return &StorageClassWatcher{ + client: client, + logger: slog.Default().With("watcher", "moduleconfig"), + } +} + +func (w ModuleConfigWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &mcapi.ModuleConfig{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(event event.CreateEvent) bool { return true }, + DeleteFunc: func(event event.DeleteEvent) bool { return true }, + UpdateFunc: func(event event.UpdateEvent) bool { + oldMc, oldOk := event.ObjectOld.(*mcapi.ModuleConfig) + newMc, newOk := event.ObjectNew.(*mcapi.ModuleConfig) + if !oldOk || !newOk { + return false + } + var ( + oldViDefaultSc string + newViDefaultSc string + ) + if virtualImages, ok := oldMc.Spec.Settings["virtualImages"].(map[string]interface{}); ok { + if defaultClass, ok := virtualImages["defaultStorageClassName"].(string); ok { + oldViDefaultSc = defaultClass + } + } + if virtualImages, ok := newMc.Spec.Settings["virtualImages"].(map[string]interface{}); ok { + if defaultClass, ok := virtualImages["defaultStorageClassName"].(string); ok { + oldViDefaultSc = defaultClass + } + } + return oldViDefaultSc != newViDefaultSc + }, + }, + ) +} + +func (w ModuleConfigWatcher) enqueueRequests(ctx context.Context, object client.Object) []reconcile.Request { + var vis virtv2.VirtualImageList + err := w.client.List(ctx, &vis, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(indexer.IndexFieldVIByNotReadyStorageClass, string(vicondition.StorageClassReadyType)), + }) + if err != nil { + w.logger.Error(fmt.Sprintf("failed to list virtual images: %s", err)) + return []reconcile.Request{} + } + + var requests []reconcile.Request + for _, vi := range vis.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vi.Name, + Namespace: vi.Namespace, + }, + }) + } + + return requests +} diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go index 7623d3ba56..aa913dba30 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go @@ -225,6 +225,7 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewStorageClassWatcher(mgr.GetClient()), watcher.NewVirtualMachineWatcher(mgr.GetClient()), watcher.NewVirtualDiskSnapshotWatcher(mgr.GetClient()), + watcher.NewModuleConfigWatcher(mgr.GetClient()), } { err := w.Watch(mgr, ctr) if err != nil { diff --git a/templates/virtualization-controller/rbac-for-us.yaml b/templates/virtualization-controller/rbac-for-us.yaml index 449592c40f..17765ec60a 100644 --- a/templates/virtualization-controller/rbac-for-us.yaml +++ b/templates/virtualization-controller/rbac-for-us.yaml @@ -225,6 +225,14 @@ rules: verbs: - patch - update +- apiGroups: + - deckhouse.io + resources: + - moduleconfigs + verbs: + - get + - list + - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding