diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go index 297dcb35c..7ffb166b6 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go @@ -38,13 +38,13 @@ func NewDeletionHandler(sources *source.Sources) *DeletionHandler { func (h DeletionHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { if cvi.DeletionTimestamp != nil { - requeue, err := h.sources.CleanUp(ctx, cvi) + result, err := h.sources.CleanUp(ctx, cvi) if err != nil { return reconcile.Result{}, err } - if requeue { - return reconcile.Result{Requeue: true}, nil + if !result.IsZero() { + return result, nil } controllerutil.RemoveFinalizer(cvi, virtv2.FinalizerCVICleanup) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go index 4cc3b7b98..10dea1b14 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go @@ -93,10 +93,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtual return reconcile.Result{}, fmt.Errorf("data source runner not found for type: %s", cvi.Spec.DataSource.Type) } - requeue, err := ds.Sync(ctx, cvi) + result, err := ds.Sync(ctx, cvi) if err != nil { return reconcile.Result{}, err } - return reconcile.Result{Requeue: requeue}, nil + return result, nil } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/data_source_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/data_source_test.go deleted file mode 100644 index 4501d181c..000000000 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/data_source_test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2024 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 source - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestDataSources(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "DataSources") -} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/http.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/http.go index 51101ed12..9f992dfa3 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/http.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/http.go @@ -19,8 +19,10 @@ package source import ( "context" "errors" + "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" "github.com/deckhouse/virtualization-controller/pkg/controller/common" @@ -54,7 +56,7 @@ func NewHTTPDataSource( } } -func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "http") condition, _ := service.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) @@ -63,7 +65,7 @@ func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualIma supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -79,7 +81,7 @@ func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualIma // Unprotect import time supplements to delete them later. err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUp(ctx, cvi, ds) @@ -92,15 +94,24 @@ func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualIma envSettings := ds.getEnvSettings(cvi, supgen) err = ds.importerService.Start(ctx, envSettings, cvi, supgen, datasource.NewCABundleForCVMI(cvi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &cvi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &cvi.Status.Phase, err, cvi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &cvi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + cvi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = cvicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", cvi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -111,9 +122,9 @@ func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualIma condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -140,20 +151,20 @@ func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualIma condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningNotStarted condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil case errors.Is(err, service.ErrProvisioningFailed): condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } condition.Status = metav1.ConditionFalse @@ -168,18 +179,18 @@ func (ds HTTPDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualIma log.Info("Provisioning...", "progress", cvi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds HTTPDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds HTTPDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) requeue, err := ds.importerService.CleanUp(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return requeue, nil + return reconcile.Result{Requeue: requeue}, nil } func (ds HTTPDataSource) Validate(_ context.Context, _ *virtv2.ClusterVirtualImage) error { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/http_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/http_test.go deleted file mode 100644 index 915cd2a5a..000000000 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/http_test.go +++ /dev/null @@ -1,188 +0,0 @@ -/* -Copyright 2024 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 source - -// func TestHTTPDataSource_Run(t *testing.T) { -// expectedStatus := getExpectedStatus() -// stat := getStatMock(expectedStatus) -// -// t.Run("to provisioning phase (no importer pod)", func(t *testing.T) { -// var cvi virtv2.ClusterVirtualImage -// cvi.Spec.DataSource.Type = virtv2.DataSourceTypeHTTP -// cvi.Spec.DataSource.HTTP = &virtv2.DataSourceHTTP{ -// URL: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img", -// } -// -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return nil, nil -// }, -// StartFunc: func(ctx context.Context, settings *importer.Settings, obj service.ObjectKind, sup *supplements.Generator, caBundle *datasource.CABundle) error { -// return nil -// }, -// } -// -// ds := NewHTTPDataSource(stat, &impt, &dvcr.Settings{}, "") -// requeue, err := ds.Sync(context.Background(), &cvi) -// require.NoError(t, err) -// require.True(t, requeue) -// -// require.Equal(t, virtv2.ImageProvisioning, cvi.Status.Phase) -// require.Equal(t, expectedStatus.Progress, cvi.Status.Progress) -// require.Equal(t, expectedStatus.DownloadSpeed, cvi.Status.DownloadSpeed) -// require.NotEmpty(t, cvi.Status.Target) -// }) -// -// t.Run("to provisioning phase (with importer pod)", func(t *testing.T) { -// var cvi virtv2.ClusterVirtualImage -// -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{ -// Phase: corev1.PodRunning, -// }, -// }, nil -// }, -// } -// -// ds := NewHTTPDataSource(stat, &impt, &dvcr.Settings{}, "") -// requeue, err := ds.Sync(context.Background(), &cvi) -// require.NoError(t, err) -// require.True(t, requeue) -// -// require.Equal(t, virtv2.ImageProvisioning, cvi.Status.Phase) -// require.Equal(t, expectedStatus.Progress, cvi.Status.Progress) -// require.Equal(t, expectedStatus.DownloadSpeed, cvi.Status.DownloadSpeed) -// require.NotEmpty(t, cvi.Status.Target) -// }) -// -// t.Run("to ready", func(t *testing.T) { -// var cvi virtv2.ClusterVirtualImage -// -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{ -// Phase: corev1.PodSucceeded, -// }, -// }, nil -// }, -// } -// -// ds := NewHTTPDataSource(stat, &impt, &dvcr.Settings{}, "") -// requeue, err := ds.Sync(context.Background(), &cvi) -// require.NoError(t, err) -// require.True(t, requeue) -// -// require.Equal(t, virtv2.ImageReady, cvi.Status.Phase) -// require.Equal(t, expectedStatus.Format, cvi.Status.Format) -// require.Equal(t, expectedStatus.CDROM, cvi.Status.CDROM) -// require.Equal(t, expectedStatus.Size, cvi.Status.Size) -// require.Equal(t, expectedStatus.Progress, cvi.Status.Progress) -// require.Equal(t, expectedStatus.DownloadSpeed, cvi.Status.DownloadSpeed) -// require.NotEmpty(t, cvi.Status.Target) -// }) -// -// t.Run("clean up", func(t *testing.T) { -// var cvi virtv2.ClusterVirtualImage -// cvi.Status = expectedStatus -// cvi.Status.Phase = virtv2.ImageReady -// -// impt := ImporterMock{ -// CleanUpFunc: func(ctx context.Context, sup *supplements.Generator) (bool, error) { -// return false, nil -// }, -// } -// -// ds := NewHTTPDataSource(stat, &impt, &dvcr.Settings{}, "") -// requeue, err := ds.Sync(context.Background(), &cvi) -// require.NoError(t, err) -// require.False(t, requeue) -// -// require.Equal(t, virtv2.ImageReady, cvi.Status.Phase) -// require.Equal(t, expectedStatus.Format, cvi.Status.Format) -// require.Equal(t, expectedStatus.CDROM, cvi.Status.CDROM) -// require.Equal(t, expectedStatus.Size, cvi.Status.Size) -// require.Equal(t, expectedStatus.Progress, cvi.Status.Progress) -// require.Equal(t, expectedStatus.DownloadSpeed, cvi.Status.DownloadSpeed) -// require.NotEmpty(t, cvi.Status.Target) -// }) -// } - -// func TestHTTPDataSource_CleanUp(t *testing.T) { -// t.Run("clean up", func(t *testing.T) { -// var cvi virtv2.ClusterVirtualImage -// -// impt := ImporterMock{ -// CleanUpFunc: func(ctx context.Context, sup *supplements.Generator) (bool, error) { -// return false, nil -// }, -// } -// -// ds := NewHTTPDataSource(nil, &impt, &dvcr.Settings{}, "") -// requeue, err := ds.CleanUp(context.Background(), &cvi) -// require.NoError(t, err) -// require.False(t, requeue) -// }) -// } - -// func getExpectedStatus() virtv2.ClusterVirtualImageStatus { -// return virtv2.ClusterVirtualImageStatus{ -// ImageStatus: virtv2.ImageStatus{ -// Phase: virtv2.ImagePending, -// DownloadSpeed: virtv2.ImageStatusSpeed{ -// Avg: "000", -// AvgBytes: "111", -// Current: "222", -// CurrentBytes: "333", -// }, -// Size: virtv2.ImageStatusSize{ -// Stored: "AAA", -// StoredBytes: "BBB", -// Unpacked: "CCC", -// UnpackedBytes: "DDD", -// }, -// Format: "qcow2", -// CDROM: true, -// Target: virtv2.ImageStatusTarget{ -// RegistryURL: "dvcr.d8-virtualization.svc/cvi/cvi-example", -// }, -// Progress: "15%", -// }, -// } -// } - -// func getStatMock(expectedStatus virtv2.ClusterVirtualImageStatus) *StatMock { -// return &StatMock{ -// GetCDROMFunc: func(pod *corev1.Pod) bool { -// return expectedStatus.CDROM -// }, -// GetDownloadSpeedFunc: func(ownerUID types.UID, pod *corev1.Pod) virtv2.ImageStatusSpeed { -// return expectedStatus.DownloadSpeed -// }, -// GetFormatFunc: func(pod *corev1.Pod) string { -// return expectedStatus.Format -// }, -// GetProgressFunc: func(ownerUID types.UID, pod *corev1.Pod, prevProgress string, opts ...service.GetProgressOption) string { -// return expectedStatus.Progress -// }, -// GetSizeFunc: func(pod *corev1.Pod) virtv2.ImageStatusSize { -// return expectedStatus.Size -// }, -// } -// } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go index 8ee282ae8..36ba7f0ae 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go @@ -24,6 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" "github.com/deckhouse/virtualization-controller/pkg/controller" @@ -68,7 +69,7 @@ func NewObjectRefDataSource( } } -func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "objectref") condition, _ := service.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) @@ -79,11 +80,11 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu viKey := types.NamespacedName{Name: cvi.Spec.DataSource.ObjectRef.Name, Namespace: cvi.Spec.DataSource.ObjectRef.Namespace} vi, err := helper.FetchObject(ctx, viKey, ds.client, &virtv2.VirtualImage{}) if err != nil { - return false, fmt.Errorf("unable to get VI %s: %w", viKey, err) + return reconcile.Result{}, fmt.Errorf("unable to get VI %s: %w", viKey, err) } if vi == nil { - return false, fmt.Errorf("VI object ref source %s is nil", cvi.Spec.DataSource.ObjectRef.Name) + return reconcile.Result{}, fmt.Errorf("VI object ref source %s is nil", cvi.Spec.DataSource.ObjectRef.Name) } if vi.Spec.Storage == virtv2.StorageKubernetes { @@ -93,11 +94,11 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu vdKey := types.NamespacedName{Name: cvi.Spec.DataSource.ObjectRef.Name, Namespace: cvi.Spec.DataSource.ObjectRef.Namespace} vd, err := helper.FetchObject(ctx, vdKey, ds.client, &virtv2.VirtualDisk{}) if err != nil { - return false, fmt.Errorf("unable to get VD %s: %w", vdKey, err) + return reconcile.Result{}, fmt.Errorf("unable to get VD %s: %w", vdKey, err) } if vd == nil { - return false, fmt.Errorf("VD object ref source %s is nil", cvi.Spec.DataSource.ObjectRef.Name) + return reconcile.Result{}, fmt.Errorf("VD object ref source %s is nil", cvi.Spec.DataSource.ObjectRef.Name) } return ds.vdSyncer.Sync(ctx, cvi, vd, &condition) @@ -106,7 +107,7 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -122,7 +123,7 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu // Unprotect import time supplements to delete them later. err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUp(ctx, cvi, ds) @@ -134,25 +135,34 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu var dvcrDataSource controller.DVCRDataSource dvcrDataSource, err = controller.NewDVCRDataSourcesForCVMI(ctx, cvi.Spec.DataSource, ds.client) if err != nil { - return false, err + return reconcile.Result{}, err } var envSettings *importer.Settings envSettings, err = ds.getEnvSettings(cvi, supgen, dvcrDataSource) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.importerService.Start(ctx, envSettings, cvi, supgen, datasource.NewCABundleForCVMI(cvi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &cvi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &cvi.Status.Phase, err, cvi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &cvi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + cvi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = cvicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Ready", "progress", cvi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -163,9 +173,9 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -178,14 +188,14 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu var dvcrDataSource controller.DVCRDataSource dvcrDataSource, err = controller.NewDVCRDataSourcesForCVMI(ctx, cvi.Spec.DataSource, ds.client) if err != nil { - return false, err + return reconcile.Result{}, err } if !dvcrDataSource.IsReady() { condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = "Failed to get stats from non-ready datasource: waiting for the DataSource to be ready." - return false, nil + return reconcile.Result{}, nil } cvi.Status.Size = dvcrDataSource.GetSize() @@ -205,14 +215,14 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningNotStarted condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil case errors.Is(err, service.ErrProvisioningFailed): condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -226,28 +236,28 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtu log.Info("Ready", "progress", cvi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds ObjectRefDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { - viRefRequeue, err := ds.viOnPvcSyncer.CleanUp(ctx, cvi) +func (ds ObjectRefDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { + viRefResult, err := ds.viOnPvcSyncer.CleanUp(ctx, cvi) if err != nil { - return false, err + return reconcile.Result{}, err } - vdRefRequeue, err := ds.vdSyncer.CleanUp(ctx, cvi) + vdRefResult, err := ds.vdSyncer.CleanUp(ctx, cvi) if err != nil { - return false, err + return reconcile.Result{}, err } supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) objRefRequeue, err := ds.importerService.CleanUp(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return objRefRequeue || vdRefRequeue || viRefRequeue, nil + return service.MergeResults(viRefResult, vdRefResult, reconcile.Result{Requeue: objRefRequeue}), nil } func (ds ObjectRefDataSource) Validate(ctx context.Context, cvi *virtv2.ClusterVirtualImage) error { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_test.go deleted file mode 100644 index 40ecb2f00..000000000 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_test.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright 2024 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 source - -// var _ = Describe("ObjectRefDataSource Run", func() { -// expectedStatus := getExpectedStatus() -// stat := getStatMock(expectedStatus) -// -// var cvi *virtv2.ClusterVirtualImage -// -// BeforeEach(func() { -// cvi = &virtv2.ClusterVirtualImage{} -// cvi.Spec.DataSource.Type = virtv2.DataSourceTypeObjectRef -// cvi.Spec.DataSource.ObjectRef = &virtv2.ClusterVirtualImageObjectRef{ -// Kind: virtv2.ClusterVirtualImageObjectRefKindClusterVirtualImage, -// } -// }) -// -// It("To provisioning phase (no importer pod)", func() { -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return nil, nil -// }, -// StartFunc: func(ctx context.Context, settings *importer.Settings, obj service.ObjectKind, sup *supplements.Generator, caBundle *datasource.CABundle) error { -// return nil -// }, -// } -// -// ds := NewObjectRefDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageProvisioning)) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("To provisioning phase (with importer pod)", func() { -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{ -// Phase: corev1.PodRunning, -// }, -// }, nil -// }, -// } -// -// ds := NewObjectRefDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageProvisioning)) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("To ready phase", func() { -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{ -// Phase: corev1.PodSucceeded, -// }, -// }, nil -// }, -// } -// -// ds := NewObjectRefDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageReady)) -// Expect(cvi.Status.Size).To(Equal(expectedStatus.Size)) -// Expect(cvi.Status.CDROM).To(Equal(expectedStatus.CDROM)) -// Expect(cvi.Status.Format).To(Equal(expectedStatus.Format)) -// Expect(cvi.Status.Progress).To(Equal("100%")) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("Clean up", func() { -// cvi.Status.Phase = virtv2.ImageReady -// impt := ImporterMock{ -// CleanUpFunc: func(ctx context.Context, sup *supplements.Generator) (bool, error) { -// return true, nil -// }, -// } -// -// ds := NewObjectRefDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// }) -// }) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go index a92662033..606efd0f7 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go @@ -24,6 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" cc "github.com/deckhouse/virtualization-controller/pkg/controller/common" @@ -34,6 +35,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) @@ -55,13 +57,13 @@ func NewObjectRefVirtualDisk(importerService Importer, client client.Client, con } } -func (ds ObjectRefVirtualDisk) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage, vdRef *virtv2.VirtualDisk, condition *metav1.Condition) (bool, error) { +func (ds ObjectRefVirtualDisk) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage, vdRef *virtv2.VirtualDisk, condition *metav1.Condition) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "objectref") supgen := supplements.NewGenerator(cc.CVIShortName, cvi.Name, vdRef.Namespace, cvi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -76,7 +78,7 @@ func (ds ObjectRefVirtualDisk) Sync(ctx context.Context, cvi *virtv2.ClusterVirt err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUp(ctx, cvi, ds) @@ -93,16 +95,24 @@ func (ds ObjectRefVirtualDisk) Sync(ctx context.Context, cvi *virtv2.ClusterVirt ownerRef := metav1.NewControllerRef(cvi, cvi.GroupVersionKind()) podSettings := ds.importerService.GetPodSettingsWithPVC(ownerRef, supgen, vdRef.Status.Target.PersistentVolumeClaim, vdRef.Namespace) err = ds.importerService.StartWithPodSetting(ctx, envSettings, supgen, datasource.NewCABundleForCVMI(cvi.Spec.DataSource), podSettings) - - var requeue bool - requeue, err = setPhaseConditionForImporterStart(condition, &cvi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case cc.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(condition, &cvi.Status.Phase, err, cvi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(condition, &cvi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + cvi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = cvicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", cvi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case cc.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -113,9 +123,9 @@ func (ds ObjectRefVirtualDisk) Sync(ctx context.Context, cvi *virtv2.ClusterVirt condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -141,20 +151,20 @@ func (ds ObjectRefVirtualDisk) Sync(ctx context.Context, cvi *virtv2.ClusterVirt condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningNotStarted condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil case errors.Is(err, service.ErrProvisioningFailed): condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } condition.Status = metav1.ConditionFalse @@ -168,16 +178,16 @@ func (ds ObjectRefVirtualDisk) Sync(ctx context.Context, cvi *virtv2.ClusterVirt log.Info("Provisioning...", "progress", cvi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds ObjectRefVirtualDisk) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds ObjectRefVirtualDisk) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { importerRequeue, err := ds.importerService.DeletePod(ctx, cvi, controllerName) if err != nil { - return false, err + return reconcile.Result{}, err } - return importerRequeue, nil + return reconcile.Result{Requeue: importerRequeue}, nil } func (ds ObjectRefVirtualDisk) getEnvSettings(cvi *virtv2.ClusterVirtualImage, sup *supplements.Generator) *importer.Settings { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vi_on_pvc.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vi_on_pvc.go index 21ecb5a9a..bfea22122 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vi_on_pvc.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vi_on_pvc.go @@ -19,8 +19,10 @@ package source import ( "context" "errors" + "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" "github.com/deckhouse/virtualization-controller/pkg/controller/common" @@ -49,13 +51,13 @@ func NewObjectRefVirtualImageOnPvc(importerService Importer, dvcrSettings *dvcr. } } -func (ds ObjectRefVirtualImageOnPvc) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage, viRef *virtv2.VirtualImage, condition *metav1.Condition) (bool, error) { +func (ds ObjectRefVirtualImageOnPvc) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage, viRef *virtv2.VirtualImage, condition *metav1.Condition) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "objectref") supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, viRef.Namespace, cvi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -70,7 +72,7 @@ func (ds ObjectRefVirtualImageOnPvc) Sync(ctx context.Context, cvi *virtv2.Clust err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUp(ctx, cvi, ds) @@ -87,16 +89,24 @@ func (ds ObjectRefVirtualImageOnPvc) Sync(ctx context.Context, cvi *virtv2.Clust ownerRef := metav1.NewControllerRef(cvi, cvi.GroupVersionKind()) podSettings := ds.importerService.GetPodSettingsWithPVC(ownerRef, supgen, viRef.Status.Target.PersistentVolumeClaim, viRef.Namespace) err = ds.importerService.StartWithPodSetting(ctx, envSettings, supgen, datasource.NewCABundleForCVMI(cvi.Spec.DataSource), podSettings) - - var requeue bool - requeue, err = setPhaseConditionForImporterStart(condition, &cvi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(condition, &cvi.Status.Phase, err, cvi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(condition, &cvi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + cvi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = cvicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", cvi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -107,9 +117,9 @@ func (ds ObjectRefVirtualImageOnPvc) Sync(ctx context.Context, cvi *virtv2.Clust condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -135,20 +145,20 @@ func (ds ObjectRefVirtualImageOnPvc) Sync(ctx context.Context, cvi *virtv2.Clust condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningNotStarted condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil case errors.Is(err, service.ErrProvisioningFailed): condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } condition.Status = metav1.ConditionFalse @@ -162,16 +172,16 @@ func (ds ObjectRefVirtualImageOnPvc) Sync(ctx context.Context, cvi *virtv2.Clust log.Info("Provisioning...", "progress", cvi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds ObjectRefVirtualImageOnPvc) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds ObjectRefVirtualImageOnPvc) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { importerRequeue, err := ds.importerService.DeletePod(ctx, cvi, controllerName) if err != nil { - return false, err + return reconcile.Result{}, err } - return importerRequeue, nil + return reconcile.Result{Requeue: importerRequeue}, nil } func (ds ObjectRefVirtualImageOnPvc) getEnvSettings(cvi *virtv2.ClusterVirtualImage, sup *supplements.Generator) *importer.Settings { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/registry.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/registry.go index 88859339c..77d16f42c 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/registry.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/registry.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" "github.com/deckhouse/virtualization-controller/pkg/controller/common" @@ -62,7 +63,7 @@ func NewRegistryDataSource( } } -func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "registry") condition, _ := service.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) @@ -71,7 +72,7 @@ func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtua supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -87,7 +88,7 @@ func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtua // Unprotect import time supplements to delete them later. err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUp(ctx, cvi, ds) @@ -100,15 +101,24 @@ func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtua envSettings := ds.getEnvSettings(cvi, supgen) err = ds.importerService.Start(ctx, envSettings, cvi, supgen, datasource.NewCABundleForCVMI(cvi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &cvi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &cvi.Status.Phase, err, cvi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &cvi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + cvi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = cvicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", cvi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -119,9 +129,9 @@ func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtua condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -147,14 +157,14 @@ func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtua condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningNotStarted condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil case errors.Is(err, service.ErrProvisioningFailed): condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -169,18 +179,18 @@ func (ds RegistryDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtua log.Info("Provisioning...", "progress", cvi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds RegistryDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds RegistryDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) requeue, err := ds.importerService.CleanUp(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return requeue, nil + return reconcile.Result{Requeue: requeue}, nil } func (ds RegistryDataSource) Validate(ctx context.Context, cvi *virtv2.ClusterVirtualImage) error { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/registry_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/registry_test.go deleted file mode 100644 index 70204b407..000000000 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/registry_test.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright 2024 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 source - -// var _ = Describe("RegistryDataSource Run", func() { -// expectedStatus := getExpectedStatus() -// stat := getStatMock(expectedStatus) -// -// var cvi *virtv2.ClusterVirtualImage -// -// BeforeEach(func() { -// cvi = &virtv2.ClusterVirtualImage{} -// cvi.Spec.DataSource.Type = virtv2.DataSourceTypeContainerImage -// cvi.Spec.DataSource.ContainerImage = &virtv2.DataSourceContainerRegistry{ -// Image: "registry.hub.example.com/example/example:latest", -// } -// }) -// -// It("To provisioning phase (no importer pod)", func() { -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return nil, nil -// }, -// StartFunc: func(ctx context.Context, settings *importer.Settings, obj service.ObjectKind, sup *supplements.Generator, caBundle *datasource.CABundle) error { -// return nil -// }, -// } -// -// ds := NewRegistryDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageProvisioning)) -// Expect(cvi.Status.Progress).To(Equal("0%")) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("To provisioning phase (with importer pod)", func() { -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{ -// Phase: corev1.PodRunning, -// }, -// }, nil -// }, -// } -// -// ds := NewRegistryDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageProvisioning)) -// Expect(cvi.Status.Progress).To(Equal("0%")) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("To ready phase", func() { -// impt := ImporterMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{ -// Phase: corev1.PodSucceeded, -// }, -// }, nil -// }, -// } -// -// ds := NewRegistryDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageReady)) -// Expect(cvi.Status.Size).To(Equal(expectedStatus.Size)) -// Expect(cvi.Status.CDROM).To(Equal(expectedStatus.CDROM)) -// Expect(cvi.Status.Format).To(Equal(expectedStatus.Format)) -// Expect(cvi.Status.Progress).To(Equal("100%")) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("Clean up", func() { -// cvi.Status.Phase = virtv2.ImageReady -// impt := ImporterMock{ -// CleanUpFunc: func(ctx context.Context, sup *supplements.Generator) (bool, error) { -// return true, nil -// }, -// } -// -// ds := NewRegistryDataSource(stat, &impt, &dvcr.Settings{}, nil, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// }) -// }) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/sources.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/sources.go index cd3a98687..992c9351d 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/sources.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/sources.go @@ -19,17 +19,21 @@ package source import ( "context" "fmt" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" cc "github.com/deckhouse/virtualization-controller/pkg/controller/common" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type Handler interface { - Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) - CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) + Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) + CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) Validate(ctx context.Context, cvi *virtv2.ClusterVirtualImage) error } @@ -56,64 +60,56 @@ func (s Sources) Changed(_ context.Context, cvi *virtv2.ClusterVirtualImage) boo return cvi.Generation != cvi.Status.ObservedGeneration } -func (s Sources) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { - var requeue bool +func (s Sources) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { + var mergedResult reconcile.Result for _, source := range s.sources { - ok, err := source.CleanUp(ctx, cvi) + result, err := source.CleanUp(ctx, cvi) if err != nil { - return false, err + return reconcile.Result{}, err } - requeue = requeue || ok + mergedResult = service.MergeResults(mergedResult, result) } - return requeue, nil + return mergedResult, nil } type Cleaner interface { - CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) + CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) } -func CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage, c Cleaner) (bool, error) { +func CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage, c Cleaner) (reconcile.Result, error) { if cc.ShouldCleanupSubResources(cvi) { return c.CleanUp(ctx, cvi) } - return false, nil + return reconcile.Result{}, nil } func isDiskProvisioningFinished(c metav1.Condition) bool { return c.Reason == cvicondition.Ready } -func setPhaseConditionForImporterStart(ready *metav1.Condition, phase *virtv2.ImagePhase, err error) (bool, error) { - return setPhaseConditionForPodStart(ready, phase, err, virtv2.ImageProvisioning, cvicondition.Provisioning) -} +const retryPeriod = 1 -func setPhaseConditionForUploaderStart(ready *metav1.Condition, phase *virtv2.ImagePhase, err error) (bool, error) { - return setPhaseConditionForPodStart(ready, phase, err, virtv2.ImagePending, cvicondition.WaitForUserUpload) -} +func setQuotaExceededPhaseCondition(condition *metav1.Condition, phase *virtv2.ImagePhase, err error, creationTimestamp metav1.Time) reconcile.Result { + *phase = virtv2.ImageFailed + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.ProvisioningFailed -func setPhaseConditionForPodStart(ready *metav1.Condition, phase *virtv2.ImagePhase, err error, okPhase virtv2.ImagePhase, okReason cvicondition.ReadyReason) (bool, error) { - switch { - case err == nil: - *phase = okPhase - ready.Status = metav1.ConditionFalse - ready.Reason = okReason - ready.Message = "DVCR Provisioner not found: create the new one." - return true, nil - case cc.ErrQuotaExceeded(err): - *phase = virtv2.ImageFailed - ready.Status = metav1.ConditionFalse - ready.Reason = cvicondition.ProvisioningFailed - ready.Message = fmt.Sprintf("Quota exceeded: please configure the `importerResourceRequirements` field in the virtualization module configuration; %s.", err) - return false, nil - default: - *phase = virtv2.ImageFailed - ready.Status = metav1.ConditionFalse - ready.Reason = cvicondition.ProvisioningFailed - ready.Message = fmt.Sprintf("Unexpected error: %s.", err) - return false, err + if creationTimestamp.Add(30 * time.Minute).After(time.Now()) { + condition.Message = fmt.Sprintf("Quota exceeded: %s; Please configure quotas or try recreating the resource later.", err) + return reconcile.Result{} } + + condition.Message = fmt.Sprintf("Quota exceeded: %s; Retry in %d minute.", err, retryPeriod) + return reconcile.Result{RequeueAfter: retryPeriod * time.Minute} +} + +func setPhaseConditionToFailed(ready *metav1.Condition, phase *virtv2.ImagePhase, err error) { + *phase = virtv2.ImageFailed + ready.Status = metav1.ConditionFalse + ready.Reason = cvicondition.ProvisioningFailed + ready.Message = service.CapitalizeFirstLetter(err.Error()) } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/upload.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/upload.go index 8b92008d3..4b6183704 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/upload.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/upload.go @@ -22,6 +22,7 @@ import ( "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" "github.com/deckhouse/virtualization-controller/pkg/controller/common" @@ -55,7 +56,7 @@ func NewUploadDataSource( } } -func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "upload") condition, _ := service.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) @@ -64,15 +65,15 @@ func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualI supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) pod, err := ds.uploaderService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } svc, err := ds.uploaderService.GetService(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } ing, err := ds.uploaderService.GetIngress(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -88,7 +89,7 @@ func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualI // Unprotect upload time supplements to delete them later. err = ds.uploaderService.Unprotect(ctx, pod, svc, ing) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUp(ctx, cvi, ds) @@ -99,15 +100,24 @@ func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualI case pod == nil || svc == nil || ing == nil: envSettings := ds.getEnvSettings(cvi, supgen) err = ds.uploaderService.Start(ctx, envSettings, cvi, supgen, datasource.NewCABundleForCVMI(cvi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForUploaderStart(&condition, &cvi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &cvi.Status.Phase, err, cvi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &cvi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + cvi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = cvicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create uploader pod...", "progress", cvi.Status.Progress, "pod.phase", nil) - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -118,9 +128,9 @@ func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualI condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -147,14 +157,14 @@ func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualI condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningNotStarted condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil case errors.Is(err, service.ErrProvisioningFailed): condition.Status = metav1.ConditionFalse condition.Reason = cvicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -169,7 +179,7 @@ func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualI err = ds.uploaderService.Protect(ctx, pod, svc, ing) if err != nil { - return false, err + return reconcile.Result{}, err } log.Info("Provisioning...", "progress", cvi.Status.Progress, "pod.phase", pod.Status.Phase) @@ -196,18 +206,18 @@ func (ds UploadDataSource) Sync(ctx context.Context, cvi *virtv2.ClusterVirtualI log.Info("Waiting for the uploader to be ready to process the user's upload", "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds UploadDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (bool, error) { +func (ds UploadDataSource) CleanUp(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.CVIShortName, cvi.Name, ds.controllerNamespace, cvi.UID) requeue, err := ds.uploaderService.CleanUp(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return requeue, nil + return reconcile.Result{Requeue: requeue}, nil } func (ds UploadDataSource) Validate(_ context.Context, _ *virtv2.ClusterVirtualImage) error { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/upload_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/upload_test.go deleted file mode 100644 index 1c0b1d367..000000000 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/upload_test.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -Copyright 2024 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 source - -// var _ = Describe("UploadDataSource Run", func() { -// expectedStatus := getExpectedStatus() -// stat := getStatMock(expectedStatus) -// -// var uplr *UploaderMock -// -// var cvi *virtv2.ClusterVirtualImage -// -// BeforeEach(func() { -// cvi = &virtv2.ClusterVirtualImage{} -// cvi.Spec.DataSource.Type = virtv2.DataSourceTypeUpload -// uplr = &UploaderMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{}, nil -// }, -// GetServiceFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Service, error) { -// return &corev1.Service{}, nil -// }, -// GetIngressFunc: func(ctx context.Context, sup *supplements.Generator) (*netv1.Ingress, error) { -// return &netv1.Ingress{}, nil -// }, -// } -// }) -// -// It("To pending phase (no importer pod)", func() { -// uplr = &UploaderMock{ -// GetPodFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return nil, nil -// }, -// GetServiceFunc: func(ctx context.Context, sup *supplements.Generator) (*corev1.Service, error) { -// return nil, nil -// }, -// GetIngressFunc: func(ctx context.Context, sup *supplements.Generator) (*netv1.Ingress, error) { -// return nil, nil -// }, -// StartFunc: func(ctx context.Context, settings *uploader.Settings, obj service.ObjectKind, sup *supplements.Generator, caBundle *datasource.CABundle) error { -// return nil -// }, -// } -// -// ds := NewUploadDataSource(stat, uplr, &dvcr.Settings{}, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImagePending)) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("To wait wo user upload phase", func() { -// uplr.GetPodFunc = func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{Phase: corev1.PodRunning}, -// }, nil -// } -// -// stat.IsUploadStartedFunc = func(ownerUID types.UID, pod *corev1.Pod) bool { -// return false -// } -// -// ds := NewUploadDataSource(stat, uplr, &dvcr.Settings{}, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageWaitForUserUpload)) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("To provisioning phase", func() { -// uplr.GetPodFunc = func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{Phase: corev1.PodRunning}, -// }, nil -// } -// -// stat.IsUploadStartedFunc = func(ownerUID types.UID, pod *corev1.Pod) bool { -// return true -// } -// -// ds := NewUploadDataSource(stat, uplr, &dvcr.Settings{}, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageProvisioning)) -// Expect(cvi.Status.Progress).To(Equal(expectedStatus.Progress)) -// Expect(cvi.Status.DownloadSpeed).To(Equal(expectedStatus.DownloadSpeed)) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("To ready phase", func() { -// uplr.GetPodFunc = func(ctx context.Context, sup *supplements.Generator) (*corev1.Pod, error) { -// return &corev1.Pod{ -// Status: corev1.PodStatus{Phase: corev1.PodSucceeded}, -// }, nil -// } -// -// ds := NewUploadDataSource(stat, uplr, &dvcr.Settings{}, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// Expect(cvi.Status.Phase).To(Equal(virtv2.ImageReady)) -// Expect(cvi.Status.Size).To(Equal(expectedStatus.Size)) -// Expect(cvi.Status.CDROM).To(Equal(expectedStatus.CDROM)) -// Expect(cvi.Status.Format).To(Equal(expectedStatus.Format)) -// Expect(cvi.Status.Progress).To(Equal("100%")) -// Expect(cvi.Status.Target.RegistryURL).NotTo(BeEmpty()) -// }) -// -// It("Clean up", func() { -// cvi.Status.Phase = virtv2.ImageReady -// uplr := UploaderMock{ -// CleanUpFunc: func(ctx context.Context, sup *supplements.Generator) (bool, error) { -// return true, nil -// }, -// } -// -// ds := NewUploadDataSource(stat, &uplr, &dvcr.Settings{}, "") -// -// requeue, err := ds.Sync(context.Background(), cvi) -// Expect(err).To(BeNil()) -// Expect(requeue).To(BeTrue()) -// }) -// }) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go index db5a3765b..4b1004d2c 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go @@ -93,19 +93,19 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) ( return reconcile.Result{}, fmt.Errorf("data source runner not found for type: %s", vi.Spec.DataSource.Type) } - var requeue bool + var result reconcile.Result var err error if vi.Spec.Storage == virtv2.StorageKubernetes { - requeue, err = ds.StoreToPVC(ctx, vi) + result, err = ds.StoreToPVC(ctx, vi) } else { - requeue, err = ds.StoreToDVCR(ctx, vi) + result, err = ds.StoreToDVCR(ctx, vi) } if err != nil { return reconcile.Result{}, err } - return reconcile.Result{Requeue: requeue}, nil + return result, nil } func (h LifeCycleHandler) Name() string { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go index 5470e2382..a22e2dafc 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" @@ -63,7 +64,7 @@ func NewHTTPDataSource( } } -func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "http") condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -72,7 +73,7 @@ func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImag supgen := supplements.NewGenerator(cc.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -87,10 +88,10 @@ func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImag err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } - return CleanUp(ctx, vi, ds) + return CleanUpSupplements(ctx, vi, ds) case cc.IsTerminating(pod): vi.Status.Phase = virtv2.ImagePending @@ -101,15 +102,24 @@ func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImag envSettings := ds.getEnvSettings(vi, supgen) err = ds.importerService.Start(ctx, envSettings, vi, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case cc.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", vi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case cc.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -120,9 +130,9 @@ func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImag condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -142,12 +152,12 @@ func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImag default: err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(&condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(&condition, vi, err) } err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } condition.Status = metav1.ConditionFalse @@ -162,10 +172,10 @@ func (ds HTTPDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImag log.Info("Provisioning...", "progress", vi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "http") condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -174,15 +184,15 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage supgen := supplements.NewGenerator(cc.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } dv, err := ds.diskService.GetDataVolume(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } pvc, err := ds.diskService.GetPersistentVolumeClaim(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -194,18 +204,18 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } // Unprotect import time supplements to delete them later. err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.Unprotect(ctx, dv) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUpSupplements(ctx, vi, ds) @@ -216,26 +226,35 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage envSettings := ds.getEnvSettings(vi, supgen) err = ds.importerService.Start(ctx, envSettings, vi, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case cc.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", vi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case !cc.IsPodComplete(pod): log.Info("Provisioning to DVCR is in progress", "podPhase", pod.Status.Phase) err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(&condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(&condition, vi, err) } err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } vi.Status.Phase = virtv2.ImageProvisioning @@ -257,9 +276,9 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -272,16 +291,16 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage setPhaseConditionToFailed(&condition, &vi.Status.Phase, err) if errors.Is(err, service.ErrInsufficientPVCSize) { - return false, nil + return reconcile.Result{}, nil } - return false, err + return reconcile.Result{}, err } source := ds.getSource(supgen, ds.statService.GetDVCRImageName(pod)) err = ds.diskService.StartImmediate(ctx, diskSize, ptr.To(ds.storageClassForPVC), source, vi, supgen) if updated, err := setPhaseConditionFromStorageError(err, vi, &condition); err != nil || updated { - return false, err + return reconcile.Result{}, err } vi.Status.Phase = virtv2.ImageProvisioning @@ -289,13 +308,13 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage condition.Reason = vicondition.Provisioning condition.Message = "PVC Provisioner not found: create the new one." - return true, nil + return reconcile.Result{Requeue: true}, nil case pvc == nil: vi.Status.Phase = virtv2.ImageProvisioning condition.Status = metav1.ConditionFalse condition.Reason = vicondition.Provisioning condition.Message = "PVC not found: waiting for creation." - return true, nil + return reconcile.Result{Requeue: true}, nil case ds.diskService.IsImportDone(dv, pvc): log.Info("Import has completed", "dvProgress", dv.Status.Progress, "dvPhase", dv.Status.Phase, "pvcPhase", pvc.Status.Phase) @@ -316,18 +335,18 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage err = ds.diskService.Protect(ctx, vi, dv, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = setPhaseConditionForPVCProvisioningImage(ctx, dv, vi, pvc, &condition, ds.diskService) if err != nil { - return false, err + return reconcile.Result{}, err } - return false, nil + return reconcile.Result{}, nil } - return true, nil + return reconcile.Result{Requeue: true}, nil } func (ds HTTPDataSource) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { @@ -346,20 +365,20 @@ func (ds HTTPDataSource) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) ( return importerRequeue || diskRequeue, nil } -func (ds HTTPDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds HTTPDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(cc.VIShortName, vi.Name, vi.Namespace, vi.UID) importerRequeue, err := ds.importerService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } diskRequeue, err := ds.diskService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return importerRequeue || diskRequeue, nil + return reconcile.Result{Requeue: importerRequeue || diskRequeue}, nil } func (ds HTTPDataSource) Validate(_ context.Context, _ *virtv2.VirtualImage) error { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go index 3def184b6..a840af752 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go @@ -27,6 +27,7 @@ import ( "k8s.io/utils/ptr" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" cc "github.com/deckhouse/virtualization-controller/pkg/common" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" @@ -77,7 +78,7 @@ func NewObjectRefDataSource( } } -func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, objectRefDataSource) condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -88,11 +89,11 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual viKey := types.NamespacedName{Name: vi.Spec.DataSource.ObjectRef.Name, Namespace: vi.Namespace} viRef, err := helper.FetchObject(ctx, viKey, ds.client, &virtv2.VirtualImage{}) if err != nil { - return false, fmt.Errorf("unable to get VI %s: %w", viKey, err) + return reconcile.Result{}, fmt.Errorf("unable to get VI %s: %w", viKey, err) } if viRef == nil { - return false, fmt.Errorf("VI object ref %s is nil", viKey) + return reconcile.Result{}, fmt.Errorf("VI object ref %s is nil", viKey) } if viRef.Spec.Storage == virtv2.StorageKubernetes { @@ -102,11 +103,11 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual viKey := types.NamespacedName{Name: vi.Spec.DataSource.ObjectRef.Name, Namespace: vi.Namespace} vd, err := helper.FetchObject(ctx, viKey, ds.client, &virtv2.VirtualDisk{}) if err != nil { - return false, fmt.Errorf("unable to get VI %s: %w", viKey, err) + return reconcile.Result{}, fmt.Errorf("unable to get VI %s: %w", viKey, err) } if vd == nil { - return false, fmt.Errorf("VD object ref %s is nil", viKey) + return reconcile.Result{}, fmt.Errorf("VD object ref %s is nil", viKey) } return ds.vdSyncer.StoreToPVC(ctx, vi, vd, &condition) @@ -115,11 +116,11 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) dv, err := ds.diskService.GetDataVolume(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } pvc, err := ds.diskService.GetPersistentVolumeClaim(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -131,12 +132,12 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.Unprotect(ctx, dv) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUpSupplements(ctx, vi, ds) @@ -147,14 +148,14 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual var dvcrDataSource controller.DVCRDataSource dvcrDataSource, err = controller.NewDVCRDataSourcesForVMI(ctx, vi.Spec.DataSource, vi, ds.client) if err != nil { - return false, err + return reconcile.Result{}, err } if !dvcrDataSource.IsReady() { condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = "Failed to get stats from non-ready datasource: waiting for the DataSource to be ready." - return false, nil + return reconcile.Result{}, nil } vi.Status.Progress = "0%" @@ -166,21 +167,21 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual setPhaseConditionToFailed(&condition, &vi.Status.Phase, err) if errors.Is(err, service.ErrInsufficientPVCSize) { - return false, nil + return reconcile.Result{}, nil } - return false, err + return reconcile.Result{}, err } var source *cdiv1.DataVolumeSource source, err = ds.getSource(supgen, dvcrDataSource) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.StartImmediate(ctx, diskSize, ptr.To(ds.storageClassForPVC), source, vi, supgen) if updated, err := setPhaseConditionFromStorageError(err, vi, &condition); err != nil || updated { - return false, err + return reconcile.Result{}, err } vi.Status.Phase = virtv2.ImageProvisioning @@ -188,13 +189,13 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual condition.Reason = vicondition.Provisioning condition.Message = "PVC Provisioner not found: create the new one." - return true, nil + return reconcile.Result{Requeue: true}, nil case pvc == nil: vi.Status.Phase = virtv2.ImageProvisioning condition.Status = metav1.ConditionFalse condition.Reason = vicondition.Provisioning condition.Message = "PVC not found: waiting for creation." - return true, nil + return reconcile.Result{Requeue: true}, nil case ds.diskService.IsImportDone(dv, pvc): log.Info("Import has completed", "dvProgress", dv.Status.Progress, "dvPhase", dv.Status.Phase, "pvcPhase", pvc.Status.Phase) @@ -206,7 +207,7 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual var dvcrDataSource controller.DVCRDataSource dvcrDataSource, err = controller.NewDVCRDataSourcesForVMI(ctx, vi.Spec.DataSource, vi, ds.client) if err != nil { - return false, err + return reconcile.Result{}, err } vi.Status.Size = dvcrDataSource.GetSize() @@ -222,21 +223,21 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual err = ds.diskService.Protect(ctx, vi, dv, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = setPhaseConditionForPVCProvisioningImage(ctx, dv, vi, pvc, &condition, ds.diskService) if err != nil { - return false, err + return reconcile.Result{}, err } - return false, nil + return reconcile.Result{}, nil } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "objectref") condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -247,11 +248,11 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua viKey := types.NamespacedName{Name: vi.Spec.DataSource.ObjectRef.Name, Namespace: vi.Namespace} viRef, err := helper.FetchObject(ctx, viKey, ds.client, &virtv2.VirtualImage{}) if err != nil { - return false, fmt.Errorf("unable to get VI %s: %w", viKey, err) + return reconcile.Result{}, fmt.Errorf("unable to get VI %s: %w", viKey, err) } if viRef == nil { - return false, fmt.Errorf("VI object ref source %s is nil", vi.Spec.DataSource.ObjectRef.Name) + return reconcile.Result{}, fmt.Errorf("VI object ref source %s is nil", vi.Spec.DataSource.ObjectRef.Name) } if viRef.Spec.Storage == virtv2.StorageKubernetes { @@ -261,11 +262,11 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua viKey := types.NamespacedName{Name: vi.Spec.DataSource.ObjectRef.Name, Namespace: vi.Namespace} vd, err := helper.FetchObject(ctx, viKey, ds.client, &virtv2.VirtualDisk{}) if err != nil { - return false, fmt.Errorf("unable to get VD %s: %w", viKey, err) + return reconcile.Result{}, fmt.Errorf("unable to get VD %s: %w", viKey, err) } if vd == nil { - return false, fmt.Errorf("VD object ref %s is nil", viKey) + return reconcile.Result{}, fmt.Errorf("VD object ref %s is nil", viKey) } return ds.vdSyncer.StoreToDVCR(ctx, vi, vd, &condition) @@ -274,7 +275,7 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -289,10 +290,10 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } - return CleanUp(ctx, vi, ds) + return CleanUpSupplements(ctx, vi, ds) case common.IsTerminating(pod): vi.Status.Phase = virtv2.ImagePending @@ -303,27 +304,36 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua var dvcrDataSource controller.DVCRDataSource dvcrDataSource, err = controller.NewDVCRDataSourcesForVMI(ctx, vi.Spec.DataSource, vi, ds.client) if err != nil { - return false, err + return reconcile.Result{}, err } + vi.Status.SourceUID = util.GetPointer(dvcrDataSource.GetUID()) + var envSettings *importer.Settings envSettings, err = ds.getEnvSettings(vi, supgen, dvcrDataSource) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.importerService.Start(ctx, envSettings, vi, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } - vi.Status.SourceUID = util.GetPointer(dvcrDataSource.GetUID()) + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." log.Info("Ready", "progress", vi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -334,22 +344,22 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } var dvcrDataSource controller.DVCRDataSource dvcrDataSource, err = controller.NewDVCRDataSourcesForVMI(ctx, vi.Spec.DataSource, vi, ds.client) if err != nil { - return false, err + return reconcile.Result{}, err } if !dvcrDataSource.IsReady() { condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = "Failed to get stats from non-ready datasource: waiting for the DataSource to be ready." - return false, nil + return reconcile.Result{}, nil } condition.Status = metav1.ConditionTrue @@ -367,7 +377,7 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua default: err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(&condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(&condition, vi, err) } condition.Status = metav1.ConditionFalse @@ -380,7 +390,7 @@ func (ds ObjectRefDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtua log.Info("Ready", "progress", vi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } func (ds ObjectRefDataSource) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { @@ -468,20 +478,20 @@ func (ds ObjectRefDataSource) getEnvSettings(vi *virtv2.VirtualImage, sup *suppl return &settings, nil } -func (ds ObjectRefDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds ObjectRefDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) importerRequeue, err := ds.importerService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } diskRequeue, err := ds.diskService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return importerRequeue || diskRequeue, nil + return reconcile.Result{Requeue: importerRequeue || diskRequeue}, nil } func (ds ObjectRefDataSource) getPVCSize(dvcrDataSource controller.DVCRDataSource) (resource.Quantity, error) { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go index 445764bdf..7dc5ea460 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go @@ -27,6 +27,7 @@ import ( "k8s.io/utils/ptr" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" "github.com/deckhouse/virtualization-controller/pkg/controller/common" @@ -62,13 +63,13 @@ func NewObjectRefVirtualDisk(importerService Importer, client client.Client, dis } } -func (ds ObjectRefVirtualDisk) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage, vdRef *virtv2.VirtualDisk, condition *metav1.Condition) (bool, error) { +func (ds ObjectRefVirtualDisk) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage, vdRef *virtv2.VirtualDisk, condition *metav1.Condition) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "objectref") supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vdRef.Namespace, vi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -83,10 +84,10 @@ func (ds ObjectRefVirtualDisk) StoreToDVCR(ctx context.Context, vi *virtv2.Virtu err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } - return CleanUp(ctx, vi, ds) + return CleanUpSupplements(ctx, vi, ds) case common.IsTerminating(pod): vi.Status.Phase = virtv2.ImagePending @@ -100,16 +101,24 @@ func (ds ObjectRefVirtualDisk) StoreToDVCR(ctx context.Context, vi *virtv2.Virtu ownerRef := metav1.NewControllerRef(vi, vi.GroupVersionKind()) podSettings := ds.importerService.GetPodSettingsWithPVC(ownerRef, supgen, vdRef.Status.Target.PersistentVolumeClaim, vdRef.Namespace) err = ds.importerService.StartWithPodSetting(ctx, envSettings, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource), podSettings) - - var requeue bool - requeue, err = setPhaseConditionForImporterStart(condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", vi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -120,9 +129,9 @@ func (ds ObjectRefVirtualDisk) StoreToDVCR(ctx context.Context, vi *virtv2.Virtu condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -148,20 +157,20 @@ func (ds ObjectRefVirtualDisk) StoreToDVCR(ctx context.Context, vi *virtv2.Virtu condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningNotStarted condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil case errors.Is(err, service.ErrProvisioningFailed): condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } condition.Status = metav1.ConditionFalse @@ -175,21 +184,21 @@ func (ds ObjectRefVirtualDisk) StoreToDVCR(ctx context.Context, vi *virtv2.Virtu log.Info("Provisioning...", "progress", vi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds ObjectRefVirtualDisk) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage, vdRef *virtv2.VirtualDisk, condition *metav1.Condition) (bool, error) { +func (ds ObjectRefVirtualDisk) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage, vdRef *virtv2.VirtualDisk, condition *metav1.Condition) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, objectRefDataSource) supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) dv, err := ds.diskService.GetDataVolume(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } pvc, err := ds.diskService.GetPersistentVolumeClaim(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -201,12 +210,12 @@ func (ds ObjectRefVirtualDisk) StoreToPVC(ctx context.Context, vi *virtv2.Virtua // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.Unprotect(ctx, dv) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUpSupplements(ctx, vi, ds) @@ -227,12 +236,12 @@ func (ds ObjectRefVirtualDisk) StoreToPVC(ctx context.Context, vi *virtv2.Virtua size, err := resource.ParseQuantity(vdRef.Status.Capacity) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.StartImmediate(ctx, size, ptr.To(ds.storageClassForPVC), source, vi, supgen) if updated, err := setPhaseConditionFromStorageError(err, vi, condition); err != nil || updated { - return false, err + return reconcile.Result{}, err } vi.Status.Phase = virtv2.ImageProvisioning @@ -240,13 +249,13 @@ func (ds ObjectRefVirtualDisk) StoreToPVC(ctx context.Context, vi *virtv2.Virtua condition.Reason = vicondition.Provisioning condition.Message = "PVC Provisioner not found: create the new one." - return true, nil + return reconcile.Result{Requeue: true}, nil case pvc == nil: vi.Status.Phase = virtv2.ImageProvisioning condition.Status = metav1.ConditionFalse condition.Reason = vicondition.Provisioning condition.Message = "PVC not found: waiting for creation." - return true, nil + return reconcile.Result{Requeue: true}, nil case ds.diskService.IsImportDone(dv, pvc): log.Info("Import has completed", "dvProgress", dv.Status.Progress, "dvPhase", dv.Status.Phase, "pvcPhase", pvc.Status.Phase) @@ -271,34 +280,34 @@ func (ds ObjectRefVirtualDisk) StoreToPVC(ctx context.Context, vi *virtv2.Virtua err = ds.diskService.Protect(ctx, vi, dv, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = setPhaseConditionForPVCProvisioningImage(ctx, dv, vi, pvc, condition, ds.diskService) if err != nil { - return false, err + return reconcile.Result{}, err } - return false, nil + return reconcile.Result{}, nil } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds ObjectRefVirtualDisk) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds ObjectRefVirtualDisk) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) importerRequeue, err := ds.importerService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } diskRequeue, err := ds.diskService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return importerRequeue || diskRequeue, nil + return reconcile.Result{Requeue: importerRequeue || diskRequeue}, nil } func (ds ObjectRefVirtualDisk) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go index 2fec74617..cecbc6f15 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go @@ -26,6 +26,7 @@ import ( "k8s.io/utils/ptr" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" "github.com/deckhouse/virtualization-controller/pkg/controller/common" @@ -66,13 +67,13 @@ func NewObjectRefDataVirtualImageOnPVC( } } -func (ds ObjectRefDataVirtualImageOnPVC) StoreToDVCR(ctx context.Context, vi, viRef *virtv2.VirtualImage, condition *metav1.Condition) (bool, error) { +func (ds ObjectRefDataVirtualImageOnPVC) StoreToDVCR(ctx context.Context, vi, viRef *virtv2.VirtualImage, condition *metav1.Condition) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "objectref") supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -87,10 +88,10 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToDVCR(ctx context.Context, vi, vi err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } - return CleanUp(ctx, vi, ds) + return CleanUpSupplements(ctx, vi, ds) case common.IsTerminating(pod): vi.Status.Phase = virtv2.ImagePending @@ -104,16 +105,24 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToDVCR(ctx context.Context, vi, vi ownerRef := metav1.NewControllerRef(vi, vi.GroupVersionKind()) podSettings := ds.importerService.GetPodSettingsWithPVC(ownerRef, supgen, viRef.Status.Target.PersistentVolumeClaim, viRef.Namespace) err = ds.importerService.StartWithPodSetting(ctx, envSettings, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource), podSettings) - - var requeue bool - requeue, err = setPhaseConditionForImporterStart(condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create importer pod...", "progress", vi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -124,9 +133,9 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToDVCR(ctx context.Context, vi, vi condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -145,12 +154,12 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToDVCR(ctx context.Context, vi, vi default: err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(condition, vi, err) } err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } condition.Status = metav1.ConditionFalse @@ -164,20 +173,20 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToDVCR(ctx context.Context, vi, vi log.Info("Provisioning...", "progress", vi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viRef *virtv2.VirtualImage, condition *metav1.Condition) (bool, error) { +func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viRef *virtv2.VirtualImage, condition *metav1.Condition) (reconcile.Result, error) { log, _ := logger.GetDataSourceContext(ctx, objectRefDataSource) supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) dv, err := ds.diskService.GetDataVolume(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } pvc, err := ds.diskService.GetPersistentVolumeClaim(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -189,12 +198,12 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viR // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.Unprotect(ctx, dv) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUpSupplements(ctx, vi, ds) @@ -211,10 +220,10 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viR setPhaseConditionToFailed(condition, &vi.Status.Phase, err) if errors.Is(err, service.ErrInsufficientPVCSize) { - return false, nil + return reconcile.Result{}, nil } - return false, err + return reconcile.Result{}, err } source := &cdiv1.DataVolumeSource{ @@ -226,7 +235,7 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viR err = ds.diskService.StartImmediate(ctx, size, ptr.To(ds.storageClassForPVC), source, vi, supgen) if updated, err := setPhaseConditionFromStorageError(err, vi, condition); err != nil || updated { - return false, err + return reconcile.Result{}, err } vi.Status.Phase = virtv2.ImageProvisioning @@ -234,13 +243,13 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viR condition.Reason = vicondition.Provisioning condition.Message = "PVC Provisioner not found: create the new one." - return true, nil + return reconcile.Result{Requeue: true}, nil case pvc == nil: vi.Status.Phase = virtv2.ImageProvisioning condition.Status = metav1.ConditionFalse condition.Reason = vicondition.Provisioning condition.Message = "PVC not found: waiting for creation." - return true, nil + return reconcile.Result{Requeue: true}, nil case ds.diskService.IsImportDone(dv, pvc): log.Info("Import has completed", "dvProgress", dv.Status.Progress, "dvPhase", dv.Status.Phase, "pvcPhase", pvc.Status.Phase) @@ -261,18 +270,18 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viR err = ds.diskService.Protect(ctx, vi, dv, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = setPhaseConditionForPVCProvisioningImage(ctx, dv, vi, pvc, condition, ds.diskService) if err != nil { - return false, err + return reconcile.Result{}, err } - return false, nil + return reconcile.Result{}, nil } - return true, nil + return reconcile.Result{Requeue: true}, nil } func (ds ObjectRefDataVirtualImageOnPVC) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { @@ -304,20 +313,20 @@ func (ds ObjectRefDataVirtualImageOnPVC) getEnvSettings(vi *virtv2.VirtualImage, return &settings } -func (ds ObjectRefDataVirtualImageOnPVC) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds ObjectRefDataVirtualImageOnPVC) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) importerRequeue, err := ds.importerService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } diskRequeue, err := ds.diskService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return importerRequeue || diskRequeue, nil + return reconcile.Result{Requeue: importerRequeue || diskRequeue}, nil } func (ds ObjectRefDataVirtualImageOnPVC) getPVCSize(refSize virtv2.ImageStatusSize) (resource.Quantity, error) { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go index e9a37bf94..ee338f64a 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go @@ -28,6 +28,7 @@ import ( "k8s.io/utils/ptr" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" cc "github.com/deckhouse/virtualization-controller/pkg/common" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" @@ -71,7 +72,7 @@ func NewRegistryDataSource( } } -func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, registryDataSource) condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -80,15 +81,15 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } dv, err := ds.diskService.GetDataVolume(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } pvc, err := ds.diskService.GetPersistentVolumeClaim(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -100,18 +101,18 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } // Unprotect import time supplements to delete them later. err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.Unprotect(ctx, dv) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUpSupplements(ctx, vi, ds) @@ -120,23 +121,32 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI case pod == nil: log.Info("Start import to DVCR") + vi.Status.Progress = "0%" + envSettings := ds.getEnvSettings(vi, supgen) err = ds.importerService.Start(ctx, envSettings, vi, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } - vi.Status.Progress = "0%" + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." - return requeue, nil + return reconcile.Result{Requeue: true}, nil case !common.IsPodComplete(pod): log.Info("Provisioning to DVCR is in progress", "podPhase", pod.Status.Phase) err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(&condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(&condition, vi, err) } vi.Status.Phase = virtv2.ImageProvisioning @@ -148,7 +158,7 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI err = ds.importerService.Protect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } case dv == nil: log.Info("Start import to PVC") @@ -162,9 +172,9 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -176,17 +186,17 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI setPhaseConditionToFailed(&condition, &vi.Status.Phase, err) if errors.Is(err, service.ErrInsufficientPVCSize) { - return false, nil + return reconcile.Result{}, nil } - return false, err + return reconcile.Result{}, err } source := ds.getSource(supgen, ds.statService.GetDVCRImageName(pod)) err = ds.diskService.StartImmediate(ctx, diskSize, ptr.To(ds.storageClassForPVC), source, vi, supgen) if updated, err := setPhaseConditionFromStorageError(err, vi, &condition); err != nil || updated { - return false, err + return reconcile.Result{}, err } vi.Status.Phase = virtv2.ImageProvisioning @@ -194,13 +204,13 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI condition.Reason = vicondition.Provisioning condition.Message = "PVC Provisioner not found: create the new one." - return true, nil + return reconcile.Result{Requeue: true}, nil case pvc == nil: vi.Status.Phase = virtv2.ImageProvisioning condition.Status = metav1.ConditionFalse condition.Reason = vicondition.Provisioning condition.Message = "PVC not found: waiting for creation." - return true, nil + return reconcile.Result{Requeue: true}, nil case ds.diskService.IsImportDone(dv, pvc): log.Info("Import has completed", "dvProgress", dv.Status.Progress, "dvPhase", dv.Status.Phase, "pvcPhase", pvc.Status.Phase) @@ -221,21 +231,21 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI err = ds.diskService.Protect(ctx, vi, dv, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = setPhaseConditionForPVCProvisioningImage(ctx, dv, vi, pvc, &condition, ds.diskService) if err != nil { - return false, err + return reconcile.Result{}, err } - return false, nil + return reconcile.Result{}, nil } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds RegistryDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds RegistryDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "registry") condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -244,7 +254,7 @@ func (ds RegistryDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtual supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.importerService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -259,28 +269,37 @@ func (ds RegistryDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtual err = ds.importerService.Unprotect(ctx, pod) if err != nil { - return false, err + return reconcile.Result{}, err } - return CleanUp(ctx, vi, ds) + return CleanUpSupplements(ctx, vi, ds) case common.IsTerminating(pod): vi.Status.Phase = virtv2.ImagePending log.Info("Cleaning up...") case pod == nil: + vi.Status.Progress = "0%" + envSettings := ds.getEnvSettings(vi, supgen) err = ds.importerService.Start(ctx, envSettings, vi, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForImporterStart(&condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } - vi.Status.Progress = "0%" + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." log.Info("Create importer pod...", "progress", vi.Status.Progress, "pod.phase", "nil") - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -291,9 +310,9 @@ func (ds RegistryDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtual condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -312,7 +331,7 @@ func (ds RegistryDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtual default: err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(&condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(&condition, vi, err) } condition.Status = metav1.ConditionFalse @@ -326,7 +345,7 @@ func (ds RegistryDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.Virtual log.Info("Provisioning...", "progress", vi.Status.Progress, "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } func (ds RegistryDataSource) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { @@ -378,20 +397,20 @@ func (ds RegistryDataSource) getEnvSettings(vi *virtv2.VirtualImage, supgen *sup return &settings } -func (ds RegistryDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds RegistryDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) importerRequeue, err := ds.importerService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } diskRequeue, err := ds.diskService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return importerRequeue || diskRequeue, nil + return reconcile.Result{Requeue: importerRequeue || diskRequeue}, nil } func (ds RegistryDataSource) getPVCSize(pod *corev1.Pod) (resource.Quantity, error) { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go index b4865e82d..3a7f2bad2 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go @@ -20,22 +20,23 @@ import ( "context" "errors" "fmt" + "time" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" cc "github.com/deckhouse/virtualization-controller/pkg/controller/common" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/supplements" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type Handler interface { - StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) - StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) + StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) + StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) Validate(ctx context.Context, vi *virtv2.VirtualImage) error } @@ -80,7 +81,7 @@ func (s Sources) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, er type Cleaner interface { CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) - CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) + CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) } func CleanUp(ctx context.Context, vi *virtv2.VirtualImage, c Cleaner) (bool, error) { @@ -91,49 +92,18 @@ func CleanUp(ctx context.Context, vi *virtv2.VirtualImage, c Cleaner) (bool, err return false, nil } -func CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage, c Cleaner) (bool, error) { +func CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage, c Cleaner) (reconcile.Result, error) { if cc.ShouldCleanupSubResources(vi) { return c.CleanUpSupplements(ctx, vi) } - return false, nil + return reconcile.Result{}, nil } func isDiskProvisioningFinished(c metav1.Condition) bool { return c.Reason == vicondition.Ready } -func setPhaseConditionForImporterStart(ready *metav1.Condition, phase *virtv2.ImagePhase, err error) (bool, error) { - return setPhaseConditionForPodStart(ready, phase, err, virtv2.ImageProvisioning, vicondition.Provisioning) -} - -func setPhaseConditionForUploaderStart(ready *metav1.Condition, phase *virtv2.ImagePhase, err error) (bool, error) { - return setPhaseConditionForPodStart(ready, phase, err, virtv2.ImagePending, vicondition.WaitForUserUpload) -} - -func setPhaseConditionForPodStart(ready *metav1.Condition, phase *virtv2.ImagePhase, err error, okPhase virtv2.ImagePhase, okReason vicondition.ReadyReason) (bool, error) { - switch { - case err == nil: - *phase = okPhase - ready.Status = metav1.ConditionFalse - ready.Reason = okReason - ready.Message = "DVCR Provisioner not found: create the new one." - return true, nil - case cc.ErrQuotaExceeded(err): - *phase = virtv2.ImageFailed - ready.Status = metav1.ConditionFalse - ready.Reason = cvicondition.ProvisioningFailed - ready.Message = fmt.Sprintf("Quota exceeded: please configure the `importerResourceRequirements` field in the virtualization module configuration; %s.", err) - return false, nil - default: - *phase = virtv2.ImageFailed - ready.Status = metav1.ConditionFalse - ready.Reason = cvicondition.ProvisioningFailed - ready.Message = fmt.Sprintf("Unexpected error: %s.", err) - return false, err - } -} - type CheckImportProcess interface { CheckImportProcess(ctx context.Context, dv *cdiv1.DataVolume, pvc *corev1.PersistentVolumeClaim) error } @@ -257,3 +227,19 @@ func setPhaseConditionFromStorageError(err error, vi *virtv2.VirtualImage, condi return false, err } } + +const retryPeriod = 1 + +func setQuotaExceededPhaseCondition(condition *metav1.Condition, phase *virtv2.ImagePhase, err error, creationTimestamp metav1.Time) reconcile.Result { + *phase = virtv2.ImageFailed + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.ProvisioningFailed + + if creationTimestamp.Add(30 * time.Minute).After(time.Now()) { + condition.Message = fmt.Sprintf("Quota exceeded: %s; Please configure quotas or try recreating the resource later.", err) + return reconcile.Result{} + } + + condition.Message = fmt.Sprintf("Quota exceeded: %s; Retry in %d minute.", err, retryPeriod) + return reconcile.Result{RequeueAfter: retryPeriod * time.Minute} +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go index a36529838..480841f4d 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" cc "github.com/deckhouse/virtualization-controller/pkg/common" "github.com/deckhouse/virtualization-controller/pkg/common/datasource" @@ -65,7 +66,7 @@ func NewUploadDataSource( } } -func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, uploadDataSource) condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -74,23 +75,23 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.uploaderService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } svc, err := ds.uploaderService.GetService(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } ing, err := ds.uploaderService.GetIngress(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } dv, err := ds.diskService.GetDataVolume(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } pvc, err := ds.diskService.GetPersistentVolumeClaim(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -102,18 +103,18 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } // Unprotect upload time supplements to delete them later. err = ds.uploaderService.Unprotect(ctx, pod, svc, ing) if err != nil { - return false, err + return reconcile.Result{}, err } err = ds.diskService.Unprotect(ctx, dv) if err != nil { - return false, err + return reconcile.Result{}, err } return CleanUpSupplements(ctx, vi, ds) @@ -122,23 +123,32 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma case pod == nil || svc == nil || ing == nil: log.Info("Start import to DVCR") + vi.Status.Progress = "0%" + envSettings := ds.getEnvSettings(vi, supgen) err = ds.uploaderService.Start(ctx, envSettings, vi, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForUploaderStart(&condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } - vi.Status.Progress = "0%" + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." - return requeue, nil + return reconcile.Result{Requeue: true}, nil case !common.IsPodComplete(pod): log.Info("Provisioning to DVCR is in progress", "podPhase", pod.Status.Phase) err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(&condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(&condition, vi, err) } if !ds.statService.IsUploadStarted(vi.GetUID(), pod) { @@ -164,7 +174,7 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma condition.Message = fmt.Sprintf("Waiting for the uploader %q to be ready to process the user's upload.", pod.Name) } - return true, nil + return reconcile.Result{Requeue: true}, nil } vi.Status.Phase = virtv2.ImageProvisioning @@ -177,7 +187,7 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma err = ds.uploaderService.Protect(ctx, pod, svc, ing) if err != nil { - return false, err + return reconcile.Result{}, err } case dv == nil: log.Info("Start import to PVC") @@ -191,9 +201,9 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -206,17 +216,17 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma setPhaseConditionToFailed(&condition, &vi.Status.Phase, err) if errors.Is(err, service.ErrInsufficientPVCSize) { - return false, nil + return reconcile.Result{}, nil } - return false, err + return reconcile.Result{}, err } source := ds.getSource(supgen, ds.statService.GetDVCRImageName(pod)) err = ds.diskService.StartImmediate(ctx, diskSize, ptr.To(ds.storageClassForPVC), source, vi, supgen) if updated, err := setPhaseConditionFromStorageError(err, vi, &condition); err != nil || updated { - return false, err + return reconcile.Result{}, err } vi.Status.Phase = virtv2.ImageProvisioning @@ -224,13 +234,13 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma condition.Reason = vicondition.Provisioning condition.Message = "PVC Provisioner not found: create the new one." - return true, nil + return reconcile.Result{Requeue: true}, nil case pvc == nil: vi.Status.Phase = virtv2.ImageProvisioning condition.Status = metav1.ConditionFalse condition.Reason = vicondition.Provisioning condition.Message = "PVC not found: waiting for creation." - return true, nil + return reconcile.Result{Requeue: true}, nil case ds.diskService.IsImportDone(dv, pvc): log.Info("Import has completed", "dvProgress", dv.Status.Progress, "dvPhase", dv.Status.Phase, "pvcPhase", pvc.Status.Phase) @@ -253,21 +263,21 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma err = ds.diskService.Protect(ctx, vi, dv, pvc) if err != nil { - return false, err + return reconcile.Result{}, err } err = setPhaseConditionForPVCProvisioningImage(ctx, dv, vi, pvc, &condition, ds.diskService) if err != nil { - return false, err + return reconcile.Result{}, err } - return false, nil + return reconcile.Result{}, nil } - return true, nil + return reconcile.Result{Requeue: true}, nil } -func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { log, ctx := logger.GetDataSourceContext(ctx, "upload") condition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) @@ -276,15 +286,15 @@ func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualIm supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) pod, err := ds.uploaderService.GetPod(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } svc, err := ds.uploaderService.GetService(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } ing, err := ds.uploaderService.GetIngress(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } switch { @@ -299,10 +309,10 @@ func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualIm err = ds.uploaderService.Unprotect(ctx, pod, svc, ing) if err != nil { - return false, err + return reconcile.Result{}, err } - return CleanUp(ctx, vi, ds) + return CleanUpSupplements(ctx, vi, ds) case common.AnyTerminating(pod, svc, ing): vi.Status.Phase = virtv2.ImagePending @@ -310,15 +320,24 @@ func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualIm case pod == nil || svc == nil || ing == nil: envSettings := ds.getEnvSettings(vi, supgen) err = ds.uploaderService.Start(ctx, envSettings, vi, supgen, datasource.NewCABundleForVMI(vi.Spec.DataSource)) - var requeue bool - requeue, err = setPhaseConditionForUploaderStart(&condition, &vi.Status.Phase, err) - if err != nil { - return false, err + switch { + case err == nil: + // OK. + case common.ErrQuotaExceeded(err): + return setQuotaExceededPhaseCondition(&condition, &vi.Status.Phase, err, vi.CreationTimestamp), nil + default: + setPhaseConditionToFailed(&condition, &vi.Status.Phase, fmt.Errorf("unexpected error: %w", err)) + return reconcile.Result{}, err } + vi.Status.Phase = virtv2.ImageProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vicondition.Provisioning + condition.Message = "DVCR Provisioner not found: create the new one." + log.Info("Create uploader pod...", "progress", vi.Status.Progress, "pod.phase", nil) - return requeue, nil + return reconcile.Result{Requeue: true}, nil case common.IsPodComplete(pod): err = ds.statService.CheckPod(pod) if err != nil { @@ -329,9 +348,9 @@ func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualIm condition.Status = metav1.ConditionFalse condition.Reason = vicondition.ProvisioningFailed condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") - return false, nil + return reconcile.Result{}, nil default: - return false, err + return reconcile.Result{}, err } } @@ -351,7 +370,7 @@ func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualIm case ds.statService.IsUploadStarted(vi.GetUID(), pod): err = ds.statService.CheckPod(pod) if err != nil { - return false, setPhaseConditionFromPodError(&condition, vi, err) + return reconcile.Result{}, setPhaseConditionFromPodError(&condition, vi, err) } condition.Status = metav1.ConditionFalse @@ -365,7 +384,7 @@ func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualIm err = ds.uploaderService.Protect(ctx, pod, svc, ing) if err != nil { - return false, err + return reconcile.Result{}, err } log.Info("Provisioning...", "pod.phase", pod.Status.Phase) @@ -392,7 +411,7 @@ func (ds UploadDataSource) StoreToDVCR(ctx context.Context, vi *virtv2.VirtualIm log.Info("Waiting for the uploader to be ready to process the user's upload", "pod.phase", pod.Status.Phase) } - return true, nil + return reconcile.Result{Requeue: true}, nil } func (ds UploadDataSource) CleanUp(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { @@ -428,20 +447,20 @@ func (ds UploadDataSource) getEnvSettings(vi *virtv2.VirtualImage, supgen *suppl return &settings } -func (ds UploadDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (bool, error) { +func (ds UploadDataSource) CleanUpSupplements(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { supgen := supplements.NewGenerator(common.VIShortName, vi.Name, vi.Namespace, vi.UID) uploaderRequeue, err := ds.uploaderService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } diskRequeue, err := ds.diskService.CleanUpSupplements(ctx, supgen) if err != nil { - return false, err + return reconcile.Result{}, err } - return uploaderRequeue || diskRequeue, nil + return reconcile.Result{Requeue: uploaderRequeue || diskRequeue}, nil } func (ds UploadDataSource) getPVCSize(pod *corev1.Pod) (resource.Quantity, error) {