Skip to content

Commit 2caefc6

Browse files
authored
Merge pull request #399 from nixpanic/GroupControllerServer
Add GroupControllerServer interface for VolumeGroupSnapshot API
2 parents 9774cb0 + 7d3bdf0 commit 2caefc6

File tree

7 files changed

+471
-37
lines changed

7 files changed

+471
-37
lines changed

pkg/hostpath/controllerserver.go

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434

3535
"github.com/container-storage-interface/spec/lib/go/csi"
3636
"k8s.io/klog/v2"
37-
utilexec "k8s.io/utils/exec"
3837

3938
"github.com/kubernetes-csi/csi-driver-host-path/pkg/state"
4039
)
@@ -543,21 +542,10 @@ func (hp *hostPath) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotR
543542

544543
snapshotID := uuid.NewUUID().String()
545544
creationTime := ptypes.TimestampNow()
546-
volPath := hostPathVolume.VolPath
547545
file := hp.getSnapshotPath(snapshotID)
548546

549-
var cmd []string
550-
if hostPathVolume.VolAccessType == state.BlockAccess {
551-
glog.V(4).Infof("Creating snapshot of Raw Block Mode Volume")
552-
cmd = []string{"cp", volPath, file}
553-
} else {
554-
glog.V(4).Infof("Creating snapshot of Filsystem Mode Volume")
555-
cmd = []string{"tar", "czf", file, "-C", volPath, "."}
556-
}
557-
executor := utilexec.New()
558-
out, err := executor.Command(cmd[0], cmd[1:]...).CombinedOutput()
559-
if err != nil {
560-
return nil, fmt.Errorf("failed create snapshot: %w: %s", err, out)
547+
if err := hp.createSnapshotFromVolume(hostPathVolume, file); err != nil {
548+
return nil, err
561549
}
562550

563551
glog.V(4).Infof("create volume snapshot %s", file)
@@ -601,6 +589,11 @@ func (hp *hostPath) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotR
601589
hp.mutex.Lock()
602590
defer hp.mutex.Unlock()
603591

592+
// If the snapshot has a GroupSnapshotID, deletion is not allowed and should return InvalidArgument.
593+
if snapshot, err := hp.state.GetSnapshotByID(snapshotID); err != nil && snapshot.GroupSnapshotID != "" {
594+
return nil, status.Errorf(codes.InvalidArgument, "Snapshot with ID %s is part of groupsnapshot %s", snapshotID, snapshot.GroupSnapshotID)
595+
}
596+
604597
glog.V(4).Infof("deleting snapshot %s", snapshotID)
605598
path := hp.getSnapshotPath(snapshotID)
606599
os.RemoveAll(path)
@@ -651,11 +644,12 @@ func (hp *hostPath) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsReq
651644

652645
for _, snap := range hpSnapshots {
653646
snapshot := csi.Snapshot{
654-
SnapshotId: snap.Id,
655-
SourceVolumeId: snap.VolID,
656-
CreationTime: snap.CreationTime,
657-
SizeBytes: snap.SizeBytes,
658-
ReadyToUse: snap.ReadyToUse,
647+
SnapshotId: snap.Id,
648+
SourceVolumeId: snap.VolID,
649+
CreationTime: snap.CreationTime,
650+
SizeBytes: snap.SizeBytes,
651+
ReadyToUse: snap.ReadyToUse,
652+
GroupSnapshotId: snap.GroupSnapshotID,
659653
}
660654
snapshots = append(snapshots, snapshot)
661655
}
@@ -767,11 +761,12 @@ func convertSnapshot(snap state.Snapshot) *csi.ListSnapshotsResponse {
767761
entries := []*csi.ListSnapshotsResponse_Entry{
768762
{
769763
Snapshot: &csi.Snapshot{
770-
SnapshotId: snap.Id,
771-
SourceVolumeId: snap.VolID,
772-
CreationTime: snap.CreationTime,
773-
SizeBytes: snap.SizeBytes,
774-
ReadyToUse: snap.ReadyToUse,
764+
SnapshotId: snap.Id,
765+
SourceVolumeId: snap.VolID,
766+
CreationTime: snap.CreationTime,
767+
SizeBytes: snap.SizeBytes,
768+
ReadyToUse: snap.ReadyToUse,
769+
GroupSnapshotId: snap.GroupSnapshotID,
775770
},
776771
},
777772
}

pkg/hostpath/groupcontrollerserver.go

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package hostpath
18+
19+
import (
20+
"os"
21+
22+
"github.com/container-storage-interface/spec/lib/go/csi"
23+
"github.com/golang/glog"
24+
"github.com/golang/protobuf/ptypes"
25+
"github.com/pborman/uuid"
26+
"golang.org/x/net/context"
27+
"google.golang.org/grpc/codes"
28+
"google.golang.org/grpc/status"
29+
30+
"github.com/kubernetes-csi/csi-driver-host-path/pkg/state"
31+
)
32+
33+
func (hp *hostPath) GroupControllerGetCapabilities(context.Context, *csi.GroupControllerGetCapabilitiesRequest) (*csi.GroupControllerGetCapabilitiesResponse, error) {
34+
return &csi.GroupControllerGetCapabilitiesResponse{
35+
Capabilities: []*csi.GroupControllerServiceCapability{{
36+
Type: &csi.GroupControllerServiceCapability_Rpc{
37+
Rpc: &csi.GroupControllerServiceCapability_RPC{
38+
Type: csi.GroupControllerServiceCapability_RPC_CREATE_DELETE_GET_VOLUME_GROUP_SNAPSHOT,
39+
},
40+
},
41+
}},
42+
}, nil
43+
}
44+
45+
func (hp *hostPath) CreateVolumeGroupSnapshot(ctx context.Context, req *csi.CreateVolumeGroupSnapshotRequest) (*csi.CreateVolumeGroupSnapshotResponse, error) {
46+
if err := hp.validateGroupControllerServiceRequest(csi.GroupControllerServiceCapability_RPC_CREATE_DELETE_GET_VOLUME_GROUP_SNAPSHOT); err != nil {
47+
glog.V(3).Infof("invalid create volume group snapshot req: %v", req)
48+
return nil, err
49+
}
50+
51+
if len(req.GetName()) == 0 {
52+
return nil, status.Error(codes.InvalidArgument, "Name missing in request")
53+
}
54+
// Check arguments
55+
if len(req.GetSourceVolumeIds()) == 0 {
56+
return nil, status.Error(codes.InvalidArgument, "SourceVolumeIds missing in request")
57+
}
58+
59+
// Lock before acting on global state. A production-quality
60+
// driver might use more fine-grained locking.
61+
hp.mutex.Lock()
62+
defer hp.mutex.Unlock()
63+
64+
// Need to check for already existing groupsnapshot name, and if found check for the
65+
// requested sourceVolumeIds and sourceVolumeIds of groupsnapshot that has been created.
66+
if exGS, err := hp.state.GetGroupSnapshotByName(req.GetName()); err == nil {
67+
// Since err is nil, it means the groupsnapshot with the same name already exists. Need
68+
// to check if the sourceVolumeIds of existing groupsnapshot is the same as in new request.
69+
70+
if !exGS.MatchesSourceVolumeIDs(req.GetSourceVolumeIds()) {
71+
return nil, status.Errorf(codes.AlreadyExists, "group snapshot with the same name: %s but with different SourceVolumeIds already exist", req.GetName())
72+
}
73+
74+
// same groupsnapshot has been created.
75+
snapshots := make([]*csi.Snapshot, len(exGS.SnapshotIDs))
76+
readyToUse := true
77+
78+
for i, snapshotID := range exGS.SnapshotIDs {
79+
snapshot, err := hp.state.GetSnapshotByID(snapshotID)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
snapshots[i] = &csi.Snapshot{
85+
SizeBytes: snapshot.SizeBytes,
86+
CreationTime: snapshot.CreationTime,
87+
ReadyToUse: snapshot.ReadyToUse,
88+
GroupSnapshotId: snapshot.GroupSnapshotID,
89+
}
90+
91+
readyToUse = readyToUse && snapshot.ReadyToUse
92+
}
93+
94+
return &csi.CreateVolumeGroupSnapshotResponse{
95+
GroupSnapshot: &csi.VolumeGroupSnapshot{
96+
GroupSnapshotId: exGS.Id,
97+
Snapshots: snapshots,
98+
CreationTime: exGS.CreationTime,
99+
ReadyToUse: readyToUse,
100+
},
101+
}, nil
102+
}
103+
104+
groupSnapshot := state.GroupSnapshot{
105+
Name: req.GetName(),
106+
Id: uuid.NewUUID().String(),
107+
CreationTime: ptypes.TimestampNow(),
108+
SnapshotIDs: make([]string, len(req.GetSourceVolumeIds())),
109+
SourceVolumeIDs: make([]string, len(req.GetSourceVolumeIds())),
110+
ReadyToUse: true,
111+
}
112+
113+
copy(groupSnapshot.SourceVolumeIDs, req.GetSourceVolumeIds())
114+
115+
snapshots := make([]*csi.Snapshot, len(req.GetSourceVolumeIds()))
116+
117+
// TODO: defer a cleanup function to remove snapshots in case of a failure
118+
119+
for i, volumeID := range req.GetSourceVolumeIds() {
120+
hostPathVolume, err := hp.state.GetVolumeByID(volumeID)
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
snapshotID := uuid.NewUUID().String()
126+
file := hp.getSnapshotPath(snapshotID)
127+
128+
if err := hp.createSnapshotFromVolume(hostPathVolume, file); err != nil {
129+
return nil, err
130+
}
131+
132+
glog.V(4).Infof("create volume snapshot %s", file)
133+
snapshot := state.Snapshot{}
134+
snapshot.Name = req.GetName() + "-" + volumeID
135+
snapshot.Id = snapshotID
136+
snapshot.VolID = volumeID
137+
snapshot.Path = file
138+
snapshot.CreationTime = groupSnapshot.CreationTime
139+
snapshot.SizeBytes = hostPathVolume.VolSize
140+
snapshot.ReadyToUse = true
141+
snapshot.GroupSnapshotID = groupSnapshot.Id
142+
143+
hp.state.UpdateSnapshot(snapshot)
144+
145+
groupSnapshot.SnapshotIDs[i] = snapshotID
146+
147+
snapshots[i] = &csi.Snapshot{
148+
SizeBytes: hostPathVolume.VolSize,
149+
SnapshotId: snapshotID,
150+
SourceVolumeId: volumeID,
151+
CreationTime: groupSnapshot.CreationTime,
152+
ReadyToUse: true,
153+
GroupSnapshotId: groupSnapshot.Id,
154+
}
155+
}
156+
157+
if err := hp.state.UpdateGroupSnapshot(groupSnapshot); err != nil {
158+
return nil, err
159+
}
160+
161+
return &csi.CreateVolumeGroupSnapshotResponse{
162+
GroupSnapshot: &csi.VolumeGroupSnapshot{
163+
GroupSnapshotId: groupSnapshot.Id,
164+
Snapshots: snapshots,
165+
CreationTime: groupSnapshot.CreationTime,
166+
ReadyToUse: groupSnapshot.ReadyToUse,
167+
},
168+
}, nil
169+
}
170+
171+
func (hp *hostPath) DeleteVolumeGroupSnapshot(ctx context.Context, req *csi.DeleteVolumeGroupSnapshotRequest) (*csi.DeleteVolumeGroupSnapshotResponse, error) {
172+
if err := hp.validateGroupControllerServiceRequest(csi.GroupControllerServiceCapability_RPC_CREATE_DELETE_GET_VOLUME_GROUP_SNAPSHOT); err != nil {
173+
glog.V(3).Infof("invalid delete volume group snapshot req: %v", req)
174+
return nil, err
175+
}
176+
177+
// Check arguments
178+
if len(req.GetGroupSnapshotId()) == 0 {
179+
return nil, status.Error(codes.InvalidArgument, "GroupSnapshot ID missing in request")
180+
}
181+
182+
groupSnapshotID := req.GetGroupSnapshotId()
183+
184+
// Lock before acting on global state. A production-quality
185+
// driver might use more fine-grained locking.
186+
hp.mutex.Lock()
187+
defer hp.mutex.Unlock()
188+
189+
groupSnapshot, err := hp.state.GetGroupSnapshotByID(groupSnapshotID)
190+
if err != nil {
191+
// ok if NotFound, the VolumeGroupSnapshot was deleted already
192+
if status.Code(err) == codes.NotFound {
193+
return &csi.DeleteVolumeGroupSnapshotResponse{}, nil
194+
}
195+
196+
return nil, err
197+
}
198+
199+
for _, snapshotID := range groupSnapshot.SnapshotIDs {
200+
glog.V(4).Infof("deleting snapshot %s", snapshotID)
201+
path := hp.getSnapshotPath(snapshotID)
202+
os.RemoveAll(path)
203+
204+
if err := hp.state.DeleteSnapshot(snapshotID); err != nil {
205+
return nil, err
206+
}
207+
}
208+
209+
glog.V(4).Infof("deleting groupsnapshot %s", groupSnapshotID)
210+
if err := hp.state.DeleteGroupSnapshot(groupSnapshotID); err != nil {
211+
return nil, err
212+
}
213+
214+
return &csi.DeleteVolumeGroupSnapshotResponse{}, nil
215+
}
216+
217+
func (hp *hostPath) GetVolumeGroupSnapshot(ctx context.Context, req *csi.GetVolumeGroupSnapshotRequest) (*csi.GetVolumeGroupSnapshotResponse, error) {
218+
if err := hp.validateGroupControllerServiceRequest(csi.GroupControllerServiceCapability_RPC_CREATE_DELETE_GET_VOLUME_GROUP_SNAPSHOT); err != nil {
219+
glog.V(3).Infof("invalid get volume group snapshot req: %v", req)
220+
return nil, err
221+
}
222+
223+
groupSnapshotID := req.GetGroupSnapshotId()
224+
225+
// Check arguments
226+
if len(groupSnapshotID) == 0 {
227+
return nil, status.Error(codes.InvalidArgument, "GroupSnapshot ID missing in request")
228+
}
229+
230+
// Lock before acting on global state. A production-quality
231+
// driver might use more fine-grained locking.
232+
hp.mutex.Lock()
233+
defer hp.mutex.Unlock()
234+
235+
groupSnapshot, err := hp.state.GetGroupSnapshotByID(groupSnapshotID)
236+
if err != nil {
237+
return nil, err
238+
}
239+
240+
if !groupSnapshot.MatchesSourceVolumeIDs(req.GetSnapshotIds()) {
241+
return nil, status.Error(codes.InvalidArgument, "Snapshot IDs do not match the GroupSnapshot IDs")
242+
}
243+
244+
snapshots := make([]*csi.Snapshot, len(groupSnapshot.SnapshotIDs))
245+
for i, snapshotID := range groupSnapshot.SnapshotIDs {
246+
snapshot, err := hp.state.GetSnapshotByID(snapshotID)
247+
if err != nil {
248+
return nil, err
249+
}
250+
251+
snapshots[i] = &csi.Snapshot{
252+
SizeBytes: snapshot.SizeBytes,
253+
SnapshotId: snapshotID,
254+
SourceVolumeId: snapshot.VolID,
255+
CreationTime: snapshot.CreationTime,
256+
ReadyToUse: snapshot.ReadyToUse,
257+
GroupSnapshotId: snapshot.GroupSnapshotID,
258+
}
259+
}
260+
261+
return &csi.GetVolumeGroupSnapshotResponse{
262+
GroupSnapshot: &csi.VolumeGroupSnapshot{
263+
GroupSnapshotId: groupSnapshotID,
264+
Snapshots: snapshots,
265+
CreationTime: groupSnapshot.CreationTime,
266+
ReadyToUse: groupSnapshot.ReadyToUse,
267+
},
268+
}, nil
269+
}
270+
271+
func (hp *hostPath) validateGroupControllerServiceRequest(c csi.GroupControllerServiceCapability_RPC_Type) error {
272+
if c == csi.GroupControllerServiceCapability_RPC_UNKNOWN {
273+
return nil
274+
}
275+
276+
if c == csi.GroupControllerServiceCapability_RPC_CREATE_DELETE_GET_VOLUME_GROUP_SNAPSHOT {
277+
return nil
278+
}
279+
280+
return status.Errorf(codes.InvalidArgument, "unsupported capability %s", c)
281+
}

pkg/hostpath/hostpath.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func NewHostPathDriver(cfg Config) (*hostPath, error) {
123123
func (hp *hostPath) Run() error {
124124
s := NewNonBlockingGRPCServer()
125125
// hp itself implements ControllerServer, NodeServer, and IdentityServer.
126-
s.Start(hp.config.Endpoint, hp, hp, hp)
126+
s.Start(hp.config.Endpoint, hp, hp, hp, hp)
127127
s.Wait()
128128

129129
return nil
@@ -377,3 +377,21 @@ func (hp *hostPath) getAttachCount() int64 {
377377
}
378378
return count
379379
}
380+
381+
func (hp *hostPath) createSnapshotFromVolume(vol state.Volume, file string) error {
382+
var cmd []string
383+
if vol.VolAccessType == state.BlockAccess {
384+
glog.V(4).Infof("Creating snapshot of Raw Block Mode Volume")
385+
cmd = []string{"cp", vol.VolPath, file}
386+
} else {
387+
glog.V(4).Infof("Creating snapshot of Filsystem Mode Volume")
388+
cmd = []string{"tar", "czf", file, "-C", vol.VolPath, "."}
389+
}
390+
executor := utilexec.New()
391+
out, err := executor.Command(cmd[0], cmd[1:]...).CombinedOutput()
392+
if err != nil {
393+
return fmt.Errorf("failed create snapshot: %w: %s", err, out)
394+
}
395+
396+
return nil
397+
}

0 commit comments

Comments
 (0)