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: 1 addition & 1 deletion cloudscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func NewClient(httpClient *http.Client) *Client {
client: c,
path: floatingIPsBasePath,
}
c.Volumes = GenericServiceOperations[Volume, VolumeRequest, VolumeRequest]{
c.Volumes = GenericServiceOperations[Volume, VolumeCreateRequest, VolumeUpdateRequest]{
client: c,
path: volumeBasePath,
}
Expand Down
23 changes: 23 additions & 0 deletions test/integration/cloudscale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestMain(m *testing.M) {
foundResource := false
foundResource = foundResource || DeleteRemainingServer()
foundResource = foundResource || DeleteRemainingServerGroups()
foundResource = foundResource || DeleteRemainingVolumeSnapshots()
foundResource = foundResource || DeleteRemainingVolumes()
foundResource = foundResource || DeleteRemainingSubnets()
foundResource = foundResource || DeleteRemainingNetworks()
Expand Down Expand Up @@ -106,6 +107,28 @@ func DeleteRemainingServerGroups() bool {
return foundResource
}

func DeleteRemainingVolumeSnapshots() bool {
foundResource := false

snapshots, err := client.VolumeSnapshots.List(context.Background())
if err != nil {
log.Fatalf("VolumeSnapshots.List returned error %s\n", err)
}

for _, snapshot := range snapshots {
if strings.HasPrefix(snapshot.Name, testRunPrefix) {
foundResource = true
log.Printf("Found not deleted snapshot: %s (%s)\n", snapshot.Name, snapshot.UUID)
err = client.VolumeSnapshots.Delete(context.Background(), snapshot.UUID)
if err != nil {
log.Fatalf("VolumeSnapshots.Delete returned error %s\n", err)
}
}
}

return foundResource
}

func DeleteRemainingVolumes() bool {
foundResource := false

Expand Down
109 changes: 106 additions & 3 deletions test/integration/tags_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestIntegrationTags_Server(t *testing.T) {
func TestIntegrationTags_Volume(t *testing.T) {
integrationTest(t)

createRequest := cloudscale.VolumeRequest{
createRequest := cloudscale.VolumeCreateRequest{
Name: testRunPrefix,
SizeGB: 3,
}
Expand All @@ -129,7 +129,7 @@ func TestIntegrationTags_Volume(t *testing.T) {
t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags)
}

updateRequest := cloudscale.VolumeRequest{}
updateRequest := cloudscale.VolumeUpdateRequest{}
newTags := getNewTags()
updateRequest.Tags = &newTags

Expand Down Expand Up @@ -174,10 +174,113 @@ func TestIntegrationTags_Volume(t *testing.T) {
}
}

func TestIntegrationTags_VolumeFromSnapshot(t *testing.T) {
integrationTest(t)
ctx := context.Background()

// first create a volume and a snapshot
createVolumeRequest := &cloudscale.VolumeCreateRequest{
Name: testRunPrefix,
SizeGB: 3,
}
sourceVolume, err := client.Volumes.Create(ctx, createVolumeRequest)
if err != nil {
t.Fatalf("Volumes.Create returned error %s\n", err)
}
snapshotCreateRequest := &cloudscale.VolumeSnapshotCreateRequest{
Name: testRunPrefix,
SourceVolume: sourceVolume.UUID,
}
snapshot, err := client.VolumeSnapshots.Create(ctx, snapshotCreateRequest)
if err != nil {
t.Fatalf("VolumeSnapshots.Create: %v", err)
}

createVolumeFromSnapshotRequest := &cloudscale.VolumeCreateRequest{
Name: fmt.Sprintf("%s-from-snapshot", testRunPrefix),
VolumeSnapshotUUID: snapshot.UUID,
}

initialTags := getInitialTags()
createVolumeFromSnapshotRequest.Tags = &initialTags

volume, err := client.Volumes.Create(ctx, createVolumeFromSnapshotRequest)
if err != nil {
t.Fatalf("Volumes.Create returned error %s\n", err)
}

getResult, err := client.Volumes.Get(ctx, volume.UUID)
if err != nil {
t.Errorf("Volumes.Get returned error %s\n", err)
}
if !reflect.DeepEqual(getResult.Tags, initialTags) {
t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags)
}

updateRequest := cloudscale.VolumeUpdateRequest{}
newTags := getNewTags()
updateRequest.Tags = &newTags

err = client.Volumes.Update(ctx, volume.UUID, &updateRequest)
if err != nil {
t.Errorf("Volumes.Update returned error: %v", err)
}
getResult2, err := client.Volumes.Get(ctx, volume.UUID)
if err != nil {
t.Errorf("Volumes.Get returned error %s\n", err)
}
if !reflect.DeepEqual(getResult2.Tags, newTags) {
t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags)
}

// test querying with tags
initialTagsKeyOnly := getInitialTagsKeyOnly()
for _, tags := range []cloudscale.TagMap{initialTags, initialTagsKeyOnly} {
res, err := client.Volumes.List(ctx, cloudscale.WithTagFilter(tags))
if err != nil {
t.Errorf("Volumes.List returned error %s\n", err)
}
if len(res) > 0 {
t.Errorf("Expected no result when filter with %#v, got: %#v", tags, res)
}
}

newTagsKeyOnly := getNewTagsKeyOnly()
for _, tags := range []cloudscale.TagMap{newTags, newTagsKeyOnly} {
res, err := client.Volumes.List(ctx, cloudscale.WithTagFilter(tags))
if err != nil {
t.Errorf("Volumes.List returned error %s\n", err)
}
if len(res) != 1 {
t.Errorf("Expected exactly one result when filter with %#v, got: %#v", tags, len(res))
}
}

if err := client.VolumeSnapshots.Delete(ctx, snapshot.UUID); err != nil {
t.Fatalf("Warning: failed to delete snapshot %s: %v", snapshot.UUID, err)
}

// Wait for snapshot to be fully deleted before deleting volume
// As a volume has been created, deletion can take a few seconds longer
err = waitForSnapshotDeletion(ctx, snapshot.UUID, 30)
if err != nil {
t.Fatalf("Snapshot deletion timeout: %v", err)
}

if err := client.Volumes.Delete(ctx, sourceVolume.UUID); err != nil {
t.Fatalf("Volumes.Delete returned error %s: %v", sourceVolume.UUID, err)
}

err = client.Volumes.Delete(ctx, volume.UUID)
if err != nil {
t.Fatalf("Volumes.Delete returned error %s\n", err)
}
}

func TestIntegrationTags_Snapshot(t *testing.T) {
integrationTest(t)

createVolumeRequest := cloudscale.VolumeRequest{
createVolumeRequest := cloudscale.VolumeCreateRequest{
Name: testRunPrefix,
SizeGB: 3,
}
Expand Down
4 changes: 2 additions & 2 deletions test/integration/volume_snapshots_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestIntegrationVolumeSnapshot_CRUD(t *testing.T) {
ctx := context.Background()

// A source volume is needed to create a snapshot.
volumeCreateRequest := &cloudscale.VolumeRequest{
volumeCreateRequest := &cloudscale.VolumeCreateRequest{
Name: "test-volume-for-snapshot",
SizeGB: 50,
Type: "ssd",
Expand Down Expand Up @@ -79,7 +79,7 @@ func TestIntegrationVolumeSnapshot_Update(t *testing.T) {
ctx := context.Background()

// A source volume is needed to create a snapshot.
volumeCreateRequest := &cloudscale.VolumeRequest{
volumeCreateRequest := &cloudscale.VolumeCreateRequest{
Name: "test-volume-for-snapshot",
SizeGB: 50,
Type: "ssd",
Expand Down
74 changes: 64 additions & 10 deletions test/integration/volumes_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package integration

import (
"context"
"fmt"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -40,7 +41,7 @@ func TestIntegrationVolume_CreateAttached(t *testing.T) {
t.Fatalf("Servers.WaitFor returned error %s\n", err)
}

createVolumeRequest := &cloudscale.VolumeRequest{
createVolumeRequest := &cloudscale.VolumeCreateRequest{
Name: testRunPrefix,
SizeGB: 50,
ServerUUIDs: &[]string{server.UUID},
Expand All @@ -56,14 +57,14 @@ func TestIntegrationVolume_CreateAttached(t *testing.T) {
}

time.Sleep(3 * time.Second)
detachVolumeRequest := &cloudscale.VolumeRequest{
detachVolumeRequest := &cloudscale.VolumeUpdateRequest{
ServerUUIDs: &[]string{},
}
err = client.Volumes.Update(context.TODO(), volume.UUID, detachVolumeRequest)
if err != nil {
t.Errorf("Volumes.Update returned error %s\n", err)
}
attachVolumeRequest := &cloudscale.VolumeRequest{
attachVolumeRequest := &cloudscale.VolumeUpdateRequest{
ServerUUIDs: &[]string{server.UUID},
}

Expand All @@ -83,8 +84,61 @@ func TestIntegrationVolume_CreateAttached(t *testing.T) {
}
}

func TestIntegrationVolume_CreateFromSnapshot(t *testing.T) {
integrationTest(t)
ctx := context.Background()

// volume is need to create a snapshot
createVolumeRequest := &cloudscale.VolumeCreateRequest{
Name: fmt.Sprintf("%s-source", testRunPrefix),
SizeGB: 10,
}
volume, err := client.Volumes.Create(ctx, createVolumeRequest)
if err != nil {
t.Fatalf("Volumes.Create returned error %s\n", err)
}

snapshotCreateRequest := &cloudscale.VolumeSnapshotCreateRequest{
Name: testRunPrefix,
SourceVolume: volume.UUID,
}
snapshot, err := client.VolumeSnapshots.Create(ctx, snapshotCreateRequest)
if err != nil {
t.Fatalf("VolumeSnapshots.Create: %v", err)
}

createVolumeFromSnapshotRequest := &cloudscale.VolumeCreateRequest{
Name: fmt.Sprintf("%s-from-snapshot", testRunPrefix),
VolumeSnapshotUUID: snapshot.UUID,
}

volumeCreatedFromSnapshot, err := client.Volumes.Create(ctx, createVolumeFromSnapshotRequest)
if err != nil {
t.Fatalf("Volumes.Create: %v", err)
}

if err := client.VolumeSnapshots.Delete(ctx, snapshot.UUID); err != nil {
t.Fatalf("Warning: failed to delete snapshot %s: %v", snapshot.UUID, err)
}

// Wait for snapshot to be fully deleted before deleting volume
// As a volume has been created, deletion can take a few seconds longer
err = waitForSnapshotDeletion(ctx, snapshot.UUID, 30)
if err != nil {
t.Fatalf("Snapshot deletion timeout: %v", err)
}

if err := client.Volumes.Delete(ctx, volume.UUID); err != nil {
t.Fatalf("Warning: failed to delete volume %s: %v", volume.UUID, err)
}

if err := client.Volumes.Delete(ctx, volumeCreatedFromSnapshot.UUID); err != nil {
t.Fatalf("Warning: failed to delete volume %s: %v", volume.UUID, err)
}
}

func TestIntegrationVolume_CreateWithoutServer(t *testing.T) {
createVolumeRequest := &cloudscale.VolumeRequest{
createVolumeRequest := &cloudscale.VolumeCreateRequest{
Name: testRunPrefix,
SizeGB: 50,
}
Expand All @@ -109,7 +163,7 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) {
t.Errorf("Volume %s not found\n", volume.UUID)
}

multiUpdateVolumeRequest := &cloudscale.VolumeRequest{
multiUpdateVolumeRequest := &cloudscale.VolumeUpdateRequest{
SizeGB: 50,
Name: testRunPrefix + "Foo",
}
Expand All @@ -133,7 +187,7 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) {

const scaleSize = 200
// Try to scale.
scaleVolumeRequest := &cloudscale.VolumeRequest{SizeGB: scaleSize}
scaleVolumeRequest := &cloudscale.VolumeUpdateRequest{SizeGB: scaleSize}
err = client.Volumes.Update(context.TODO(), volume.UUID, scaleVolumeRequest)
getVolume, err := client.Volumes.Get(context.TODO(), volume.UUID)
if err == nil {
Expand All @@ -151,7 +205,7 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) {
}

func TestIntegrationVolume_AttachToNewServer(t *testing.T) {
createVolumeRequest := &cloudscale.VolumeRequest{
createVolumeRequest := &cloudscale.VolumeCreateRequest{
Name: testRunPrefix,
SizeGB: 50,
}
Expand Down Expand Up @@ -184,7 +238,7 @@ func TestIntegrationVolume_AttachToNewServer(t *testing.T) {
if err != nil {
t.Fatalf("Servers.WaitFor returned error %s\n", err)
}
volumeAttachRequest := &cloudscale.VolumeRequest{
volumeAttachRequest := &cloudscale.VolumeUpdateRequest{
ServerUUIDs: &[]string{server.UUID},
}

Expand All @@ -205,7 +259,7 @@ func TestIntegrationVolume_AttachToNewServer(t *testing.T) {

func TestIntegrationVolume_ListByName(t *testing.T) {
volumeName := testRunPrefix + "-name-test"
createVolumeRequest := &cloudscale.VolumeRequest{
createVolumeRequest := &cloudscale.VolumeCreateRequest{
Name: volumeName,
SizeGB: 5,
}
Expand Down Expand Up @@ -270,7 +324,7 @@ func TestIntegrationVolume_MultiSite(t *testing.T) {
func createVolumeInZoneAndAssert(t *testing.T, zone cloudscale.Zone, wg *sync.WaitGroup) {
defer wg.Done()

createVolumeRequest := &cloudscale.VolumeRequest{
createVolumeRequest := &cloudscale.VolumeCreateRequest{
Name: testRunPrefix,
SizeGB: 50,
}
Expand Down
16 changes: 13 additions & 3 deletions volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ type Volume struct {
CreatedAt time.Time `json:"created_at"`
}

type VolumeRequest struct {
type VolumeCreateRequest struct {
ZonalResourceRequest
TaggedResourceRequest
Name string `json:"name,omitempty"`
SizeGB int `json:"size_gb,omitempty"`
Type string `json:"type,omitempty"`
ServerUUIDs *[]string `json:"server_uuids,omitempty"`
VolumeSnapshotUUID string `json:"volume_snapshot_uuid,omitempty"`
}

type VolumeUpdateRequest struct {
ZonalResourceRequest
TaggedResourceRequest
Name string `json:"name,omitempty"`
Expand All @@ -32,10 +42,10 @@ type VolumeRequest struct {
}

type VolumeService interface {
GenericCreateService[Volume, VolumeRequest]
GenericCreateService[Volume, VolumeCreateRequest]
GenericGetService[Volume]
GenericListService[Volume]
GenericUpdateService[Volume, VolumeRequest]
GenericUpdateService[Volume, VolumeUpdateRequest]
GenericDeleteService[Volume]
GenericWaitForService[Volume]
}
Expand Down