Skip to content

Commit 58f20f5

Browse files
authored
Merge pull request #110 from jingxu97/snapshot
Add VolumeSnapshot Support
2 parents a1d6f88 + 14ef841 commit 58f20f5

File tree

16 files changed

+2516
-1560
lines changed

16 files changed

+2516
-1560
lines changed

Gopkg.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
non-go = true
7777

7878
[[constraint]]
79-
branch = "v0.3.0"
79+
branch = "master"
8080
name = "github.com/kubernetes-csi/csi-test"
8181

8282
[[constraint]]

pkg/common/utils.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ const (
2828
// Volume ID Expected Format
2929
// "projects/{projectName}/zones/{zoneName}/disks/{diskName}"
3030
// "projects/{projectName}/regions/{regionName}/disks/{diskName}"
31-
volIDToplogyKey = 2
32-
volIDToplogyValue = 3
33-
volIDDiskNameValue = 5
34-
volIDTotalElements = 6
31+
volIDToplogyKey = 2
32+
volIDToplogyValue = 3
33+
volIDDiskNameValue = 5
34+
volIDTotalElements = 6
35+
snapshotTotalElements = 5
36+
snapshotTopologyKey = 2
3537

3638
// Node ID Expected Format
3739
// "{zoneName}/{instanceName}"
@@ -64,6 +66,18 @@ func VolumeIDToKey(id string) (*meta.Key, error) {
6466
}
6567
}
6668

69+
func SnapshotIDToKey(id string) (string, error) {
70+
splitId := strings.Split(id, "/")
71+
if len(splitId) != snapshotTotalElements {
72+
return "", fmt.Errorf("failed to get id components. Expected projects/{project}/global/snapshot/{name}. Got: %s", id)
73+
}
74+
if splitId[snapshotTopologyKey] == "global" {
75+
return splitId[snapshotTotalElements-1], nil
76+
} else {
77+
return "", fmt.Errorf("could not get id components, expected global, got: %v", splitId[snapshotTopologyKey])
78+
}
79+
}
80+
6781
func NodeIDToZoneAndName(id string) (string, string, error) {
6882
splitId := strings.Split(id, "/")
6983
if len(splitId) != nodeIDTotalElements {

pkg/gce-cloud-provider/compute/fake-gce.go

Lines changed: 182 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package gcecloudprovider
1616

1717
import (
1818
"fmt"
19+
"strconv"
1920
"strings"
2021

2122
csi "github.com/container-storage-interface/spec/lib/go/csi/v0"
@@ -24,16 +25,26 @@ import (
2425
computebeta "google.golang.org/api/compute/v0.beta"
2526
compute "google.golang.org/api/compute/v1"
2627
"google.golang.org/api/googleapi"
28+
"google.golang.org/grpc/codes"
29+
"google.golang.org/grpc/status"
2730
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
2831
"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
2932
)
3033

34+
const (
35+
DiskSizeGb = 10
36+
Timestamp = "2018-09-05T15:17:08.270-07:00"
37+
BasePath = "https://www.googleapis.com/compute/v1/projects/"
38+
snapshotURITemplateGlobal = "%s/global/snapshots/%s" //{gce.projectID}/global/snapshots/{snapshot.Name}"
39+
)
40+
3141
type FakeCloudProvider struct {
3242
project string
3343
zone string
3444

3545
disks map[string]*CloudDisk
3646
instances map[string]*compute.Instance
47+
snapshots map[string]*compute.Snapshot
3748
}
3849

3950
var _ GCECompute = &FakeCloudProvider{}
@@ -44,6 +55,7 @@ func FakeCreateCloudProvider(project, zone string) (*FakeCloudProvider, error) {
4455
zone: zone,
4556
disks: map[string]*CloudDisk{},
4657
instances: map[string]*compute.Instance{},
58+
snapshots: map[string]*compute.Snapshot{},
4759
}, nil
4860

4961
}
@@ -52,6 +64,66 @@ func (cloud *FakeCloudProvider) ListZones(ctx context.Context, region string) ([
5264
return []string{cloud.zone, "country-region-fakesecondzone"}, nil
5365
}
5466

67+
func (cloud *FakeCloudProvider) ListSnapshots(ctx context.Context, filter string, maxEntries int64, pageToken string) ([]*compute.Snapshot, string, error) {
68+
var sourceDisk string
69+
snapshots := []*compute.Snapshot{}
70+
if len(filter) > 0 {
71+
filterSplits := strings.Fields(filter)
72+
if len(filterSplits) != 3 || filterSplits[0] != "sourceDisk" {
73+
return nil, "", invalidError()
74+
}
75+
sourceDisk = filterSplits[2]
76+
}
77+
for _, snapshot := range cloud.snapshots {
78+
if len(sourceDisk) > 0 {
79+
if snapshot.SourceDisk == sourceDisk {
80+
continue
81+
}
82+
}
83+
snapshots = append(snapshots, snapshot)
84+
}
85+
86+
var (
87+
ulenSnapshots = len(snapshots)
88+
startingToken int
89+
)
90+
91+
if len(pageToken) > 0 {
92+
i, err := strconv.ParseUint(pageToken, 10, 32)
93+
if err != nil {
94+
return nil, "", invalidError()
95+
}
96+
startingToken = int(i)
97+
}
98+
99+
if startingToken > ulenSnapshots {
100+
return nil, "", invalidError()
101+
}
102+
103+
// Discern the number of remaining entries.
104+
rem := ulenSnapshots - startingToken
105+
106+
// If maxEntries is 0 or greater than the number of remaining entries then
107+
// set maxEntries to the number of remaining entries.
108+
max := int(maxEntries)
109+
if max == 0 || max > rem {
110+
max = rem
111+
}
112+
113+
results := []*compute.Snapshot{}
114+
j := startingToken
115+
for i := 0; i < max; i++ {
116+
results = append(results, snapshots[j])
117+
j++
118+
}
119+
120+
var nextToken string
121+
if j < ulenSnapshots {
122+
nextToken = fmt.Sprintf("%d", j)
123+
}
124+
return results, nextToken, nil
125+
}
126+
55127
// Disk Methods
56128
func (cloud *FakeCloudProvider) GetDisk(ctx context.Context, volKey *meta.Key) (*CloudDisk, error) {
57129
disk, ok := cloud.disks[volKey.Name]
@@ -128,7 +200,7 @@ func (cloud *FakeCloudProvider) DeleteDisk(ctx context.Context, volKey *meta.Key
128200
}
129201

130202
func (cloud *FakeCloudProvider) AttachDisk(ctx context.Context, disk *CloudDisk, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string) error {
131-
source := cloud.GetDiskSourceURI(disk, volKey)
203+
source := cloud.GetDiskSourceURI(volKey)
132204

133205
attachedDiskV1 := &compute.AttachedDisk{
134206
DeviceName: disk.GetName(),
@@ -162,10 +234,6 @@ func (cloud *FakeCloudProvider) DetachDisk(ctx context.Context, volKey *meta.Key
162234
return nil
163235
}
164236

165-
func (cloud *FakeCloudProvider) GetDiskSourceURI(disk *CloudDisk, volKey *meta.Key) string {
166-
return ""
167-
}
168-
169237
func (cloud *FakeCloudProvider) GetDiskTypeURI(volKey *meta.Key, diskType string) string {
170238
switch volKey.Type() {
171239
case meta.Zonal:
@@ -208,6 +276,105 @@ func (cloud *FakeCloudProvider) GetInstanceOrError(ctx context.Context, instance
208276
return instance, nil
209277
}
210278

279+
// Snapshot Methods
280+
func (cloud *FakeCloudProvider) GetSnapshot(ctx context.Context, snapshotName string) (*compute.Snapshot, error) {
281+
snapshot, ok := cloud.snapshots[snapshotName]
282+
if !ok {
283+
return nil, notFoundError()
284+
}
285+
snapshot.Status = "READY"
286+
return snapshot, nil
287+
}
288+
289+
func (cloud *FakeCloudProvider) CreateSnapshot(ctx context.Context, volKey *meta.Key, snapshotName string) (*compute.Snapshot, error) {
290+
if snapshot, ok := cloud.snapshots[snapshotName]; ok {
291+
return snapshot, nil
292+
}
293+
294+
var snapshotToCreate *compute.Snapshot
295+
switch volKey.Type() {
296+
case meta.Zonal:
297+
snapshotToCreateGA := &compute.Snapshot{
298+
Name: snapshotName,
299+
DiskSizeGb: int64(DiskSizeGb),
300+
CreationTimestamp: Timestamp,
301+
Status: "UPLOADING",
302+
SelfLink: cloud.getGlobalSnapshotURI(snapshotName),
303+
SourceDisk: cloud.getZonalDiskSourceURI(volKey.Name, volKey.Zone),
304+
}
305+
snapshotToCreate = snapshotToCreateGA
306+
case meta.Regional:
307+
snapshotToCreateBeta := &compute.Snapshot{
308+
Name: volKey.Name,
309+
DiskSizeGb: int64(DiskSizeGb),
310+
CreationTimestamp: Timestamp,
311+
Status: "UPLOADING",
312+
SelfLink: cloud.getGlobalSnapshotURI(snapshotName),
313+
SourceDisk: cloud.getRegionalDiskSourceURI(volKey.Name, volKey.Region),
314+
}
315+
snapshotToCreate = snapshotToCreateBeta
316+
default:
317+
return nil, fmt.Errorf("could not create snapshot, disk key was neither zonal nor regional, instead got: %v", volKey.String())
318+
}
319+
320+
cloud.snapshots[snapshotName] = snapshotToCreate
321+
return snapshotToCreate, nil
322+
}
323+
324+
// Snapshot Methods
325+
func (cloud *FakeCloudProvider) DeleteSnapshot(ctx context.Context, snapshotName string) error {
326+
delete(cloud.snapshots, snapshotName)
327+
return nil
328+
}
329+
330+
func (cloud *FakeCloudProvider) ValidateExistingSnapshot(resp *compute.Snapshot, volKey *meta.Key) error {
331+
if resp == nil {
332+
return fmt.Errorf("disk does not exist")
333+
}
334+
335+
diskSource := cloud.GetDiskSourceURI(volKey)
336+
if resp.SourceDisk != diskSource {
337+
return status.Error(codes.AlreadyExists, fmt.Sprintf("snapshot already exists with same name but with a different disk source %s, expected disk source %s", diskSource, resp.SourceDisk))
338+
}
339+
// Snapshot exists with matching source disk.
340+
glog.V(4).Infof("Compatible snapshot already exists. Reusing existing.")
341+
return nil
342+
}
343+
344+
func (cloud *FakeCloudProvider) GetDiskSourceURI(volKey *meta.Key) string {
345+
switch volKey.Type() {
346+
case Zonal:
347+
return cloud.getZonalDiskSourceURI(volKey.Name, volKey.Zone)
348+
case Regional:
349+
return cloud.getRegionalDiskSourceURI(volKey.Name, volKey.Region)
350+
default:
351+
return ""
352+
}
353+
}
354+
355+
func (cloud *FakeCloudProvider) getZonalDiskSourceURI(diskName, zone string) string {
356+
return BasePath + fmt.Sprintf(
357+
diskSourceURITemplateSingleZone,
358+
cloud.project,
359+
zone,
360+
diskName)
361+
}
362+
363+
func (cloud *FakeCloudProvider) getRegionalDiskSourceURI(diskName, region string) string {
364+
return BasePath + fmt.Sprintf(
365+
diskSourceURITemplateRegional,
366+
cloud.project,
367+
region,
368+
diskName)
369+
}
370+
371+
func (cloud *FakeCloudProvider) getGlobalSnapshotURI(snapshotName string) string {
372+
return BasePath + fmt.Sprintf(
373+
snapshotURITemplateGlobal,
374+
cloud.project,
375+
snapshotName)
376+
}
377+
211378
func notFoundError() *googleapi.Error {
212379
return &googleapi.Error{
213380
Errors: []googleapi.ErrorItem{
@@ -217,3 +384,13 @@ func notFoundError() *googleapi.Error {
217384
},
218385
}
219386
}
387+
388+
func invalidError() *googleapi.Error {
389+
return &googleapi.Error{
390+
Errors: []googleapi.ErrorItem{
391+
{
392+
Reason: "invalid",
393+
},
394+
},
395+
}
396+
}

0 commit comments

Comments
 (0)