Skip to content

Commit 5402672

Browse files
committed
Implement VolumeSnapshot IRI methods in volume poollet & broker
1 parent 311a254 commit 5402672

File tree

12 files changed

+1502
-0
lines changed

12 files changed

+1502
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package server
5+
6+
import (
7+
"fmt"
8+
9+
storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1"
10+
"github.com/ironcore-dev/ironcore/broker/volumebroker/apiutils"
11+
iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1"
12+
)
13+
14+
func (s *Server) convertAggregateIronCoreVolumeSnapshot(aggregate *AggregateIronCoreVolumeSnapshot) (*iri.VolumeSnapshot, error) {
15+
ironcoreVolumeSnapshot := aggregate.VolumeSnapshot
16+
ironcoreVolume := aggregate.Volume
17+
18+
metadata, err := apiutils.GetObjectMetadata(ironcoreVolumeSnapshot)
19+
if err != nil {
20+
return nil, fmt.Errorf("error getting object metadata: %w", err)
21+
}
22+
23+
var volumeID string
24+
if ironcoreVolume != nil {
25+
volumeID = ironcoreVolume.Status.VolumeID
26+
}
27+
28+
state, err := s.convertIronCoreVolumeSnapshotState(ironcoreVolumeSnapshot.Status.State)
29+
if err != nil {
30+
return nil, fmt.Errorf("error converting volume snapshot state: %w", err)
31+
}
32+
33+
iriVolumeSnapshot := &iri.VolumeSnapshot{
34+
Metadata: metadata,
35+
Spec: &iri.VolumeSnapshotSpec{
36+
VolumeId: volumeID,
37+
},
38+
Status: &iri.VolumeSnapshotStatus{
39+
State: state,
40+
},
41+
}
42+
43+
if ironcoreVolumeSnapshot.Status.Size != nil {
44+
iriVolumeSnapshot.Status.Size = ironcoreVolumeSnapshot.Status.Size.Value()
45+
}
46+
47+
return iriVolumeSnapshot, nil
48+
}
49+
50+
var ironcoreVolumeSnapshotStateToIRIState = map[storagev1alpha1.VolumeSnapshotState]iri.VolumeSnapshotState{
51+
storagev1alpha1.VolumeSnapshotStatePending: iri.VolumeSnapshotState_VOLUME_SNAPSHOT_PENDING,
52+
storagev1alpha1.VolumeSnapshotStateReady: iri.VolumeSnapshotState_VOLUME_SNAPSHOT_READY,
53+
storagev1alpha1.VolumeSnapshotStateFailed: iri.VolumeSnapshotState_VOLUME_SNAPSHOT_FAILED,
54+
}
55+
56+
func (s *Server) convertIronCoreVolumeSnapshotState(state storagev1alpha1.VolumeSnapshotState) (iri.VolumeSnapshotState, error) {
57+
if state, ok := ironcoreVolumeSnapshotStateToIRIState[state]; ok {
58+
return state, nil
59+
}
60+
return 0, fmt.Errorf("unknown ironcore volume snapshot state %q", state)
61+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package server
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/go-logr/logr"
11+
storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1"
12+
volumebrokerv1alpha1 "github.com/ironcore-dev/ironcore/broker/volumebroker/api/v1alpha1"
13+
"github.com/ironcore-dev/ironcore/broker/volumebroker/apiutils"
14+
iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1"
15+
utilsmaps "github.com/ironcore-dev/ironcore/utils/maps"
16+
"google.golang.org/grpc/codes"
17+
"google.golang.org/grpc/status"
18+
corev1 "k8s.io/api/core/v1"
19+
apierrors "k8s.io/apimachinery/pkg/api/errors"
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"sigs.k8s.io/controller-runtime/pkg/client"
22+
)
23+
24+
type AggregateIronCoreVolumeSnapshot struct {
25+
VolumeSnapshot *storagev1alpha1.VolumeSnapshot
26+
Volume *storagev1alpha1.Volume
27+
}
28+
29+
func (s *Server) getIronCoreVolumeSnapshotConfig(ctx context.Context, volumeSnapshot *iri.VolumeSnapshot) (*AggregateIronCoreVolumeSnapshot, error) {
30+
volumeID := volumeSnapshot.Spec.VolumeId
31+
if volumeID == "" {
32+
return nil, status.Errorf(codes.InvalidArgument, "volume ID is required")
33+
}
34+
35+
volume := &storagev1alpha1.Volume{}
36+
if err := s.getManagedAndCreated(ctx, volumeID, volume); err != nil {
37+
if !apierrors.IsNotFound(err) {
38+
return nil, fmt.Errorf("error getting volume %s: %w", volumeID, err)
39+
}
40+
return nil, status.Errorf(codes.NotFound, "volume with ID %s not found", volumeID)
41+
}
42+
43+
ironcoreVolumeSnapshot := &storagev1alpha1.VolumeSnapshot{
44+
ObjectMeta: metav1.ObjectMeta{
45+
Namespace: s.namespace,
46+
Name: s.idGen.Generate(),
47+
Labels: utilsmaps.AppendMap(volumeSnapshot.Metadata.Labels, map[string]string{
48+
volumebrokerv1alpha1.ManagerLabel: volumebrokerv1alpha1.VolumeBrokerManager,
49+
}),
50+
Annotations: volumeSnapshot.Metadata.Annotations,
51+
},
52+
Spec: storagev1alpha1.VolumeSnapshotSpec{
53+
VolumeRef: &corev1.LocalObjectReference{
54+
Name: volume.Name,
55+
},
56+
},
57+
}
58+
59+
if err := apiutils.SetObjectMetadata(ironcoreVolumeSnapshot, volumeSnapshot.Metadata); err != nil {
60+
return nil, err
61+
}
62+
63+
return &AggregateIronCoreVolumeSnapshot{
64+
VolumeSnapshot: ironcoreVolumeSnapshot,
65+
Volume: volume,
66+
}, nil
67+
}
68+
69+
func (s *Server) createIronCoreVolumeSnapshot(ctx context.Context, log logr.Logger, volumeSnapshot *AggregateIronCoreVolumeSnapshot) (retErr error) {
70+
c, cleanup := s.setupCleaner(ctx, log, &retErr)
71+
defer cleanup()
72+
73+
log.V(1).Info("Creating ironcore volume snapshot")
74+
if err := s.client.Create(ctx, volumeSnapshot.VolumeSnapshot); err != nil {
75+
return fmt.Errorf("error creating ironcore volume snapshot: %w", err)
76+
}
77+
c.Add(func(ctx context.Context) error {
78+
if err := s.client.Delete(ctx, volumeSnapshot.VolumeSnapshot); client.IgnoreNotFound(err) != nil {
79+
return fmt.Errorf("error deleting ironcore volume snapshot: %w", err)
80+
}
81+
return nil
82+
})
83+
84+
log.V(1).Info("Patching ironcore volume snapshot as created")
85+
if err := apiutils.PatchCreated(ctx, s.client, volumeSnapshot.VolumeSnapshot); err != nil {
86+
return fmt.Errorf("error patching ironcore volume snapshot as created: %w", err)
87+
}
88+
89+
// Reset cleaner since everything from now on operates on a consistent volume snapshot
90+
c.Reset()
91+
92+
return nil
93+
}
94+
95+
func (s *Server) CreateVolumeSnapshot(ctx context.Context, req *iri.CreateVolumeSnapshotRequest) (res *iri.CreateVolumeSnapshotResponse, retErr error) {
96+
log := s.loggerFrom(ctx)
97+
98+
log.V(1).Info("Getting volume snapshot configuration")
99+
cfg, err := s.getIronCoreVolumeSnapshotConfig(ctx, req.VolumeSnapshot)
100+
if err != nil {
101+
return nil, fmt.Errorf("error getting ironcore volume snapshot config: %w", err)
102+
}
103+
104+
if err := s.createIronCoreVolumeSnapshot(ctx, log, cfg); err != nil {
105+
return nil, fmt.Errorf("error creating ironcore volume snapshot: %w", err)
106+
}
107+
108+
v, err := s.convertAggregateIronCoreVolumeSnapshot(cfg)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
return &iri.CreateVolumeSnapshotResponse{
114+
VolumeSnapshot: v,
115+
}, nil
116+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package server_test
5+
6+
import (
7+
"context"
8+
9+
. "github.com/onsi/ginkgo/v2"
10+
. "github.com/onsi/gomega"
11+
"google.golang.org/grpc/codes"
12+
"google.golang.org/grpc/status"
13+
14+
corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1"
15+
storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1"
16+
volumebrokerv1alpha1 "github.com/ironcore-dev/ironcore/broker/volumebroker/api/v1alpha1"
17+
irimeta "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1"
18+
iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1"
19+
corev1 "k8s.io/api/core/v1"
20+
"k8s.io/apimachinery/pkg/api/resource"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
)
24+
25+
var _ = Describe("CreateVolumeSnapshot", func() {
26+
ns, srv := SetupTest()
27+
volumeClass := SetupVolumeClass()
28+
29+
var (
30+
volume *storagev1alpha1.Volume
31+
)
32+
33+
BeforeEach(func() {
34+
35+
volume = &storagev1alpha1.Volume{
36+
ObjectMeta: metav1.ObjectMeta{
37+
Namespace: ns.Name,
38+
Name: "test-volume",
39+
Labels: map[string]string{
40+
volumebrokerv1alpha1.ManagerLabel: volumebrokerv1alpha1.VolumeBrokerManager,
41+
volumebrokerv1alpha1.CreatedLabel: "true",
42+
},
43+
},
44+
Spec: storagev1alpha1.VolumeSpec{
45+
VolumeClassRef: &corev1.LocalObjectReference{
46+
Name: volumeClass.Name,
47+
},
48+
Resources: corev1alpha1.ResourceList{
49+
corev1alpha1.ResourceStorage: resource.MustParse("1Gi"),
50+
},
51+
},
52+
Status: storagev1alpha1.VolumeStatus{
53+
State: storagev1alpha1.VolumeStateAvailable,
54+
VolumeID: "test-volume",
55+
},
56+
}
57+
Expect(k8sClient.Create(context.Background(), volume)).To(Succeed())
58+
})
59+
60+
AfterEach(func() {
61+
Expect(k8sClient.Delete(context.Background(), volume)).To(Succeed())
62+
})
63+
64+
It("should create a volume snapshot", func(ctx SpecContext) {
65+
By("creating a volume snapshot")
66+
req := &iri.CreateVolumeSnapshotRequest{
67+
VolumeSnapshot: &iri.VolumeSnapshot{
68+
Metadata: &irimeta.ObjectMetadata{
69+
Labels: map[string]string{
70+
"test-label": "test-value",
71+
},
72+
},
73+
Spec: &iri.VolumeSnapshotSpec{
74+
VolumeId: volume.Status.VolumeID,
75+
},
76+
},
77+
}
78+
79+
res, err := srv.CreateVolumeSnapshot(ctx, req)
80+
Expect(err).NotTo(HaveOccurred())
81+
Expect(res).NotTo(BeNil())
82+
Expect(res.VolumeSnapshot).NotTo(BeNil())
83+
Expect(res.VolumeSnapshot.Spec.VolumeId).To(Equal(volume.Status.VolumeID))
84+
85+
By("getting the ironcore volume snapshot")
86+
var volumeSnapshotList storagev1alpha1.VolumeSnapshotList
87+
Expect(k8sClient.List(ctx, &volumeSnapshotList, client.InNamespace(ns.Name))).To(Succeed())
88+
Expect(volumeSnapshotList.Items).To(HaveLen(1))
89+
90+
volumeSnapshot := volumeSnapshotList.Items[0]
91+
Expect(volumeSnapshot.Spec.VolumeRef.Name).To(Equal(volume.Name))
92+
Expect(volumeSnapshot.Labels).To(HaveKeyWithValue(volumebrokerv1alpha1.ManagerLabel, volumebrokerv1alpha1.VolumeBrokerManager))
93+
Expect(volumeSnapshot.Labels).To(HaveKeyWithValue(volumebrokerv1alpha1.CreatedLabel, "true"))
94+
})
95+
96+
It("should return error if volume ID is empty", func(ctx SpecContext) {
97+
By("creating a volume snapshot with empty volume ID")
98+
req := &iri.CreateVolumeSnapshotRequest{
99+
VolumeSnapshot: &iri.VolumeSnapshot{
100+
Metadata: &irimeta.ObjectMetadata{},
101+
Spec: &iri.VolumeSnapshotSpec{
102+
VolumeId: "",
103+
},
104+
},
105+
}
106+
107+
res, err := srv.CreateVolumeSnapshot(ctx, req)
108+
Expect(err).To(HaveOccurred())
109+
Expect(res).To(BeNil())
110+
Expect(status.Code(err)).To(Equal(codes.InvalidArgument))
111+
})
112+
113+
It("should return error if volume is not found", func(ctx SpecContext) {
114+
By("creating a volume snapshot with non-existent volume ID")
115+
req := &iri.CreateVolumeSnapshotRequest{
116+
VolumeSnapshot: &iri.VolumeSnapshot{
117+
Metadata: &irimeta.ObjectMetadata{},
118+
Spec: &iri.VolumeSnapshotSpec{
119+
VolumeId: "non-existent-volume-id",
120+
},
121+
},
122+
}
123+
124+
res, err := srv.CreateVolumeSnapshot(ctx, req)
125+
Expect(err).To(HaveOccurred())
126+
Expect(res).To(BeNil())
127+
Expect(status.Code(err)).To(Equal(codes.NotFound))
128+
})
129+
130+
})
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package server
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1"
11+
"google.golang.org/grpc/codes"
12+
"google.golang.org/grpc/status"
13+
apierrors "k8s.io/apimachinery/pkg/api/errors"
14+
)
15+
16+
func (s *Server) DeleteVolumeSnapshot(ctx context.Context, req *iri.DeleteVolumeSnapshotRequest) (*iri.DeleteVolumeSnapshotResponse, error) {
17+
volumeSnapshotID := req.VolumeSnapshotId
18+
log := s.loggerFrom(ctx, "VolumeSnapshotID", volumeSnapshotID)
19+
20+
ironcoreVolumeSnapshot, err := s.getAggregateIronCoreVolumeSnapshot(ctx, req.VolumeSnapshotId)
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
log.V(1).Info("Deleting volume snapshot")
26+
if err := s.client.Delete(ctx, ironcoreVolumeSnapshot.VolumeSnapshot); err != nil {
27+
if !apierrors.IsNotFound(err) {
28+
return nil, fmt.Errorf("error deleting ironcore volume snapshot: %w", err)
29+
}
30+
return nil, status.Errorf(codes.NotFound, "volume snapshot %s not found", volumeSnapshotID)
31+
}
32+
33+
return &iri.DeleteVolumeSnapshotResponse{}, nil
34+
}

0 commit comments

Comments
 (0)