Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/core/v1alpha2/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ const (
// ReasonVIStorageClassNotFound is event reason that VIStorageClass not found.
ReasonVIStorageClassNotFound = "VirtualImageStorageClassNotFound"

// ReasonMetadataSyncStarted is event reason that Metadata sync is started.
ReasonMetadataSyncStarted = "MetadataSyncStarted"
// ReasonDataSourceSyncStarted is event reason that DataSource sync is started.
ReasonDataSourceSyncStarted = "DataSourceImportStarted"
// ReasonDataSourceSyncInProgress is event reason that DataSource sync is in progress.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,13 @@ import (
"errors"
"fmt"

vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/deckhouse/virtualization-controller/pkg/common/object"
commonvdsnapshot "github.com/deckhouse/virtualization-controller/pkg/common/vdsnapshot"
"github.com/deckhouse/virtualization-controller/pkg/controller/service/restorer/common"
restorer "github.com/deckhouse/virtualization-controller/pkg/controller/service/restorer/restorers"
"github.com/deckhouse/virtualization/api/core/v1alpha2"
Expand Down Expand Up @@ -365,23 +363,6 @@ func getVirtualDisks(ctx context.Context, client client.Client, vmSnapshot *v1al
},
}

if vdSnapshot.Status.VolumeSnapshotName != "" {
vsKey := types.NamespacedName{
Namespace: vdSnapshot.Namespace,
Name: vdSnapshot.Status.VolumeSnapshotName,
}

vs, err := object.FetchObject(ctx, vsKey, client, &vsv1.VolumeSnapshot{})
if err != nil {
return nil, fmt.Errorf("failed to fetch the volume snapshot %q: %w", vsKey.Name, err)
}

err = commonvdsnapshot.AddOriginalMetadata(&vd, vs)
if err != nil {
return nil, fmt.Errorf("failed to add original metadata: %w", err)
}
}

vds = append(vds, &vd)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (ds ObjectRefVirtualDiskSnapshot) Sync(ctx context.Context, vd *v1alpha2.Vi
return steptaker.NewStepTakers[*v1alpha2.VirtualDisk](
step.NewReadyStep(ds.diskService, pvc, cb),
step.NewTerminatingStep(pvc),
step.NewAddOriginalMetadataStep(ds.recorder, ds.client, cb),
step.NewCreatePVCFromVDSnapshotStep(pvc, ds.recorder, ds.client, cb),
step.NewWaitForPVCStep(pvc, ds.client, cb),
).Run(ctx, vd)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package source

import (
"context"
"fmt"
"log/slog"
"testing"

Expand All @@ -34,6 +35,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"

"github.com/deckhouse/virtualization-controller/pkg/common/annotations"
"github.com/deckhouse/virtualization-controller/pkg/controller/conditions"
"github.com/deckhouse/virtualization-controller/pkg/controller/supplements"
"github.com/deckhouse/virtualization-controller/pkg/eventrecord"
Expand Down Expand Up @@ -185,7 +187,7 @@ var _ = Describe("ObjectRef VirtualDiskSnapshot", func() {
It("waits for the first consumer", func() {
pvc.Status.Phase = corev1.ClaimPending
sc.VolumeBindingMode = ptr.To(storagev1.VolumeBindingWaitForFirstConsumer)
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(pvc, sc).Build()
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(pvc, sc, vdSnapshot, vs).Build()

syncer := NewObjectRefVirtualDiskSnapshot(recorder, svc, client)

Expand All @@ -200,7 +202,7 @@ var _ = Describe("ObjectRef VirtualDiskSnapshot", func() {
It("is in provisioning", func() {
pvc.Status.Phase = corev1.ClaimPending
sc.VolumeBindingMode = ptr.To(storagev1.VolumeBindingImmediate)
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(pvc, sc).Build()
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(pvc, sc, vdSnapshot, vs).Build()

syncer := NewObjectRefVirtualDiskSnapshot(recorder, svc, client)

Expand Down Expand Up @@ -273,6 +275,30 @@ var _ = Describe("ObjectRef VirtualDiskSnapshot", func() {
Expect(vd.Status.Target.PersistentVolumeClaim).NotTo(BeEmpty())
})
})

Context("Virtual disk has annotations and labels", func() {
It("checks that the restored virtual disk has its original metadata", func() {
key := "key"
value := "value"
originalMetadata := fmt.Sprintf("{\"%s\":\"%s\"}", key, value)

vs.Annotations = map[string]string{
annotations.AnnVirtualDiskOriginalAnnotations: originalMetadata,
annotations.AnnVirtualDiskOriginalLabels: originalMetadata,
}

client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vdSnapshot, vs).Build()

syncer := NewObjectRefVirtualDiskSnapshot(recorder, svc, client)

res, err := syncer.Sync(ctx, vd)
Expect(err).ToNot(HaveOccurred())
Expect(res.IsZero()).To(BeTrue())

Expect(vd.Annotations).To(HaveKeyWithValue(key, value))
Expect(vd.Labels).To(HaveKeyWithValue(key, value))
})
})
})

func ExpectStats(vd *v1alpha2.VirtualDisk) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
Copyright 2025 Flant JSC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package step

import (
"context"
"encoding/json"
"fmt"

vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
corev1 "k8s.io/api/core/v1"
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/annotations"
"github.com/deckhouse/virtualization-controller/pkg/common/object"
"github.com/deckhouse/virtualization-controller/pkg/controller/conditions"
"github.com/deckhouse/virtualization-controller/pkg/controller/service"
"github.com/deckhouse/virtualization-controller/pkg/eventrecord"
"github.com/deckhouse/virtualization/api/core/v1alpha2"
"github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition"
)

const lastAppliedConfigAnnotation = "kubectl.kubernetes.io/last-applied-configuration"

type AddOriginalMetadataStep struct {
recorder eventrecord.EventRecorderLogger
client client.Client
cb *conditions.ConditionBuilder
}

func NewAddOriginalMetadataStep(
recorder eventrecord.EventRecorderLogger,
client client.Client,
cb *conditions.ConditionBuilder,
) *AddOriginalMetadataStep {
return &AddOriginalMetadataStep{
recorder: recorder,
client: client,
cb: cb,
}
}

func (s AddOriginalMetadataStep) Take(ctx context.Context, vd *v1alpha2.VirtualDisk) (*reconcile.Result, error) {
vdSnapshot, err := object.FetchObject(ctx, types.NamespacedName{Name: vd.Spec.DataSource.ObjectRef.Name, Namespace: vd.Namespace}, s.client, &v1alpha2.VirtualDiskSnapshot{})
if err != nil {
wrappedErr := fmt.Errorf("failed to fetch the virtual disk snapshot: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to create the new variable

err = fmt.Errorf("failed to fetch the virtual disk snapshot: %w", err)

vd.Status.Phase = v1alpha2.DiskFailed
s.cb.
Status(metav1.ConditionFalse).
Reason(vdcondition.ProvisioningFailed).
Message(service.CapitalizeFirstLetter(wrappedErr.Error() + "."))
return nil, wrappedErr
}

if vdSnapshot == nil {
vd.Status.Phase = v1alpha2.DiskPending
s.cb.
Status(metav1.ConditionFalse).
Reason(vdcondition.ProvisioningNotStarted).
Message(fmt.Sprintf("VirtualDiskSnapshot %q not found.", vd.Spec.DataSource.ObjectRef.Name))
return &reconcile.Result{}, nil
}

vs, err := object.FetchObject(ctx, types.NamespacedName{Name: vdSnapshot.Status.VolumeSnapshotName, Namespace: vdSnapshot.Namespace}, s.client, &vsv1.VolumeSnapshot{})
if err != nil {
wrappedErr := fmt.Errorf("failed to fetch the volume snapshot: %w", err)
vd.Status.Phase = v1alpha2.DiskFailed
s.cb.
Status(metav1.ConditionFalse).
Reason(vdcondition.ProvisioningFailed).
Message(service.CapitalizeFirstLetter(wrappedErr.Error() + "."))
return nil, wrappedErr
}

if vdSnapshot.Status.Phase != v1alpha2.VirtualDiskSnapshotPhaseReady || vs == nil || vs.Status == nil || vs.Status.ReadyToUse == nil || !*vs.Status.ReadyToUse {
vd.Status.Phase = v1alpha2.DiskPending
s.cb.
Status(metav1.ConditionFalse).
Reason(vdcondition.ProvisioningNotStarted).
Message(fmt.Sprintf("VirtualDiskSnapshot %q is not ready to use.", vdSnapshot.Name))
return &reconcile.Result{}, nil
}

areAnnotationsAdded, err := setOriginalAnnotations(vd, vs)
if err != nil {
wrappedErr := fmt.Errorf("failed to set original annotations: %w", err)
vd.Status.Phase = v1alpha2.DiskFailed
s.cb.
Status(metav1.ConditionFalse).
Reason(vdcondition.ProvisioningFailed).
Message(service.CapitalizeFirstLetter(wrappedErr.Error() + "."))
return nil, wrappedErr
}

areLabelsAdded, err := setOriginalLabels(vd, vs)
if err != nil {
wrappedErr := fmt.Errorf("failed to set original labels: %w", err)
vd.Status.Phase = v1alpha2.DiskFailed
s.cb.
Status(metav1.ConditionFalse).
Reason(vdcondition.ProvisioningFailed).
Message(service.CapitalizeFirstLetter(wrappedErr.Error() + "."))
return nil, wrappedErr
}

// Ensure that new metadata is applied correctly because a conflict error can occur
// when updating the virtual disk resource. Therefore, this step should finish
// with reconciliation if the metadata has changed.
if areAnnotationsAdded || areLabelsAdded {
s.recorder.Event(
vd,
corev1.EventTypeNormal,
v1alpha2.ReasonMetadataSyncStarted,
"The original metadata sync has started",
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set condition here as well

return &reconcile.Result{}, nil
}

return nil, nil
}

func setOriginalAnnotations(vd *v1alpha2.VirtualDisk, vs *vsv1.VolumeSnapshot) (bool, error) {
var originalAnnotationsMap map[string]string
if vs.Annotations[annotations.AnnVirtualDiskOriginalAnnotations] != "" {
err := json.Unmarshal([]byte(vs.Annotations[annotations.AnnVirtualDiskOriginalAnnotations]), &originalAnnotationsMap)
if err != nil {
return false, fmt.Errorf("failed to unmarshal the original annotations: %w", err)
}
}

if vd.Annotations == nil {
vd.Annotations = make(map[string]string)
}

var areAnnotationsAdded bool
for key, originalvalue := range originalAnnotationsMap {
if currentValue, exists := vd.Annotations[key]; !exists || (key != lastAppliedConfigAnnotation && currentValue != originalvalue) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don’t need to set the lastAppliedConfigAnnotation. Right now you are setting it.

vd.Annotations[key] = originalvalue
areAnnotationsAdded = true
}
}

return areAnnotationsAdded, nil
}

func setOriginalLabels(vd *v1alpha2.VirtualDisk, vs *vsv1.VolumeSnapshot) (bool, error) {
var originalLabelsMap map[string]string
if vs.Annotations[annotations.AnnVirtualDiskOriginalLabels] != "" {
err := json.Unmarshal([]byte(vs.Annotations[annotations.AnnVirtualDiskOriginalLabels]), &originalLabelsMap)
if err != nil {
return false, fmt.Errorf("failed to unmarshal the original labels: %w", err)
}
}

if vd.Labels == nil {
vd.Labels = make(map[string]string)
}

var areLabelsAdded bool
for key, originalvalue := range originalLabelsMap {
if currentValue, exists := vd.Labels[key]; !exists || currentValue != originalvalue {
vd.Labels[key] = originalvalue
areLabelsAdded = true
}
}

return areLabelsAdded, nil
}
Loading
Loading