Skip to content

Commit

Permalink
feat(instance): use sbs api for block volumes (#4435)
Browse files Browse the repository at this point in the history
  • Loading branch information
Codelax authored Jan 23, 2025
1 parent 080a371 commit 9850cc4
Show file tree
Hide file tree
Showing 22 changed files with 6,201 additions and 5,291 deletions.
10 changes: 7 additions & 3 deletions internal/namespaces/instance/v1/custom_server_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/alecthomas/assert"
"github.com/scaleway/scaleway-cli/v2/core"
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
"github.com/scaleway/scaleway-cli/v2/internal/namespaces/instance/v1"
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
instanceSDK "github.com/scaleway/scaleway-sdk-go/api/instance/v1"
Expand Down Expand Up @@ -63,16 +64,19 @@ func Test_ServerTerminate(t *testing.T) {
}))

t.Run("without block", core.Test(&core.TestConfig{
Commands: instance.GetCommands(),
Commands: core.NewCommandsMerge(
instance.GetCommands(),
block.GetCommands(),
),
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("image=ubuntu-jammy additional-volumes.0=block:10G -w")),
Cmd: `scw instance server terminate {{ .Server.ID }} with-ip=true with-block=false`,
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(0),
),
AfterFunc: core.AfterFuncCombine(
core.ExecAfterCmd(`scw instance volume wait {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
),
DisableParallel: true,
}))
Expand Down
49 changes: 33 additions & 16 deletions internal/namespaces/instance/v1/custom_server_create_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (sb *ServerBuilder) AddImage(image string) (*ServerBuilder, error) {
ImageID: *(sb.createReq.Image),
})
if err != nil {
logger.Warningf("cannot get image %s: %s", sb.createReq.Image, err)
logger.Warningf("cannot get image %s: %s", *sb.createReq.Image, err)
} else {
sb.serverImage = getImageResponse.Image
}
Expand Down Expand Up @@ -546,12 +546,11 @@ func NewVolumeBuilder(zone scw.Zone, flagV string) (*VolumeBuilder, error) {
switch parts[0] {
case "l", "local":
vb.VolumeType = instance.VolumeVolumeTypeLSSD
case "b", "block":
vb.VolumeType = instance.VolumeVolumeTypeBSSD
case "sbs", "b", "block":
vb.VolumeType = instance.VolumeVolumeTypeSbsVolume
case "s", "scratch":
vb.VolumeType = instance.VolumeVolumeTypeScratch
case "sbs":
vb.VolumeType = instance.VolumeVolumeTypeSbsVolume

default:
return nil, fmt.Errorf("invalid volume type %s in %s volume", parts[0], flagV)
}
Expand Down Expand Up @@ -584,31 +583,49 @@ func NewVolumeBuilder(zone scw.Zone, flagV string) (*VolumeBuilder, error) {
}

// buildSnapshotVolume builds the requested volume template to create a new volume from a snapshot
func (vb *VolumeBuilder) buildSnapshotVolume(api *instance.API) (*instance.VolumeServerTemplate, error) {
func (vb *VolumeBuilder) buildSnapshotVolume(api *instance.API, blockAPI *block.API) (*instance.VolumeServerTemplate, error) {
if vb.SnapshotID == nil {
return nil, errors.New("tried to build a volume from snapshot with an empty ID")
}
res, err := api.GetSnapshot(&instance.GetSnapshotRequest{
Zone: vb.Zone,
SnapshotID: *vb.SnapshotID,
})
if err != nil && !core.IsNotFoundError(err) {
return nil, fmt.Errorf("invalid snapshot %s: %w", *vb.SnapshotID, err)
}

if res != nil {
snapshotType := res.Snapshot.VolumeType

if snapshotType != instance.VolumeVolumeTypeUnified && snapshotType != vb.VolumeType {
return nil, fmt.Errorf("snapshot of type %s not compatible with requested volume type %s", snapshotType, vb.VolumeType)
}

return &instance.VolumeServerTemplate{
Name: &res.Snapshot.Name,
VolumeType: vb.VolumeType,
BaseSnapshot: &res.Snapshot.ID,
Size: &res.Snapshot.Size,
}, nil
}

blockRes, err := blockAPI.GetSnapshot(&block.GetSnapshotRequest{
Zone: vb.Zone,
SnapshotID: *vb.SnapshotID,
})
if err != nil {
if core.IsNotFoundError(err) {
return nil, fmt.Errorf("snapshot %s does not exist", *vb.SnapshotID)
}
}

snapshotType := res.Snapshot.VolumeType

if snapshotType != instance.VolumeVolumeTypeUnified && snapshotType != vb.VolumeType {
return nil, fmt.Errorf("snapshot of type %s not compatible with requested volume type %s", snapshotType, vb.VolumeType)
return nil, err
}

return &instance.VolumeServerTemplate{
Name: &res.Snapshot.Name,
Name: &blockRes.Name,
VolumeType: vb.VolumeType,
BaseSnapshot: &res.Snapshot.ID,
Size: &res.Snapshot.Size,
BaseSnapshot: &blockRes.ID,
Size: &blockRes.Size,
}, nil
}

Expand Down Expand Up @@ -671,7 +688,7 @@ func (vb *VolumeBuilder) buildNewVolume() (*instance.VolumeServerTemplate, error
// BuildVolumeServerTemplate builds the requested volume template to be used in a CreateServerRequest
func (vb *VolumeBuilder) BuildVolumeServerTemplate(apiInstance *instance.API, apiBlock *block.API) (*instance.VolumeServerTemplate, error) {
if vb.SnapshotID != nil {
return vb.buildSnapshotVolume(apiInstance)
return vb.buildSnapshotVolume(apiInstance, apiBlock)
}

if vb.VolumeID != nil {
Expand Down
38 changes: 13 additions & 25 deletions internal/namespaces/instance/v1/custom_server_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,28 +203,24 @@ func Test_CreateServer(t *testing.T) {
}))

t.Run("valid double snapshot", core.Test(&core.TestConfig{
Commands: instance.GetCommands(),
Commands: core.NewCommandsMerge(
instance.GetCommands(),
block.GetCommands(),
),
BeforeFunc: core.BeforeFuncCombine(
core.ExecStoreBeforeCmd("Server", testServerCommand("image=ubuntu_bionic root-volume=local:20GB stopped=true")),
core.ExecStoreBeforeCmd("Snapshot", `scw instance snapshot create unified=true volume-id={{ (index .Server.Volumes "0").ID }}`),
core.ExecStoreBeforeCmd("Server", testServerCommand("image=ubuntu_jammy root-volume=block:20GB stopped=true")),
core.ExecStoreBeforeCmd("Snapshot", `scw block snapshot create volume-id={{ (index .Server.Volumes "0").ID }} -w`),
),
Cmd: testServerCommand("image=ubuntu_bionic root-volume=block:{{ .Snapshot.Snapshot.ID }} additional-volumes.0=local:{{ .Snapshot.Snapshot.ID }} stopped=true"),
Cmd: testServerCommand("image=ubuntu_jammy root-volume=block:{{ .Snapshot.ID }} additional-volumes.0=block:{{ .Snapshot.ID }} stopped=true"),
Check: core.TestCheckCombine(
core.TestCheckExitCode(0),
func(t *testing.T, ctx *core.CheckFuncCtx) {
t.Helper()
assert.NotNil(t, ctx.Result)
server := testhelpers.Value[*instanceSDK.Server](t, ctx.Result)
size0 := testhelpers.MapTValue(t, server.Volumes, "0").Size
size1 := testhelpers.MapTValue(t, server.Volumes, "1").Size
assert.Equal(t, 20*scw.GB, instance.SizeValue(size0), "Size of volume should be 20 GB")
assert.Equal(t, 20*scw.GB, instance.SizeValue(size1), "Size of volume should be 20 GB")
},
testServerSBSVolumeSize("0", 20),
testServerSBSVolumeSize("1", 20),
),
AfterFunc: core.AfterFuncCombine(
deleteServer("Server"),
deleteServerAfterFunc(),
deleteSnapshot("Snapshot"),
deleteBlockSnapshot("Snapshot"),
),
}))

Expand All @@ -233,17 +229,9 @@ func Test_CreateServer(t *testing.T) {
Cmd: testServerCommand("image=ubuntu_bionic additional-volumes.0=b:1G additional-volumes.1=b:5G additional-volumes.2=b:10G stopped=true"),
Check: core.TestCheckCombine(
core.TestCheckExitCode(0),
func(t *testing.T, ctx *core.CheckFuncCtx) {
t.Helper()
assert.NotNil(t, ctx.Result)
server := testhelpers.Value[*instanceSDK.Server](t, ctx.Result)
size1 := testhelpers.MapTValue(t, server.Volumes, "1").Size
size2 := testhelpers.MapTValue(t, server.Volumes, "2").Size
size3 := testhelpers.MapTValue(t, server.Volumes, "3").Size
assert.Equal(t, 1*scw.GB, instance.SizeValue(size1), "Size of volume should be 1 GB")
assert.Equal(t, 5*scw.GB, instance.SizeValue(size2), "Size of volume should be 5 GB")
assert.Equal(t, 10*scw.GB, instance.SizeValue(size3), "Size of volume should be 10 GB")
},
testServerSBSVolumeSize("1", 1),
testServerSBSVolumeSize("2", 5),
testServerSBSVolumeSize("3", 10),
),
AfterFunc: deleteServerAfterFunc(),
}))
Expand Down
40 changes: 27 additions & 13 deletions internal/namespaces/instance/v1/custom_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"github.com/alecthomas/assert"
"github.com/scaleway/scaleway-cli/v2/core"
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
blockCli "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
"github.com/scaleway/scaleway-cli/v2/internal/namespaces/instance/v1"
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
blockSDK "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
instanceSDK "github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -73,7 +73,10 @@ func Test_ServerVolumeUpdate(t *testing.T) {
})
t.Run("Detach", func(t *testing.T) {
t.Run("simple block volume", core.Test(&core.TestConfig{
Commands: instance.GetCommands(),
Commands: core.NewCommandsMerge(
block.GetCommands(),
instance.GetCommands(),
),
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")),
Cmd: `scw instance server detach-volume volume-id={{ (index .Server.Volumes "1").ID }}`,
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
Expand All @@ -85,7 +88,8 @@ func Test_ServerVolumeUpdate(t *testing.T) {
assert.Equal(t, 1, len(ctx.Result.(*instanceSDK.DetachVolumeResponse).Server.Volumes))
},
AfterFunc: core.AfterFuncCombine(
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
deleteServer("Server"),
),
DisableParallel: true,
Expand Down Expand Up @@ -246,7 +250,10 @@ func Test_ServerUpdateCustom(t *testing.T) {
}))

t.Run("detach all volumes", core.Test(&core.TestConfig{
Commands: instance.GetCommands(),
Commands: core.NewCommandsMerge(
block.GetCommands(),
instance.GetCommands(),
),
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")),
Cmd: `scw instance server update {{ .Server.ID }} volume-ids=none`,
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
Expand All @@ -255,8 +262,9 @@ func Test_ServerUpdateCustom(t *testing.T) {
assert.Equal(t, 0, len(ctx.Result.(*instanceSDK.UpdateServerResponse).Server.Volumes))
},
AfterFunc: core.AfterFuncCombine(
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "0").ID }}`),
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "0").ID }}`), // Local volume
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
deleteServer("Server"),
),
}))
Expand Down Expand Up @@ -292,14 +300,20 @@ func Test_ServerDelete(t *testing.T) {
}))

t.Run("only local volumes", core.Test(&core.TestConfig{
Commands: instance.GetCommands(),
Commands: core.NewCommandsMerge(
block.GetCommands(),
instance.GetCommands(),
),
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")),
Cmd: `scw instance server delete {{ .Server.ID }} with-ip=true with-volumes=local`,
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(0),
),
AfterFunc: core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
AfterFunc: core.AfterFuncCombine(
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
),
DisableParallel: true,
}))

Expand Down Expand Up @@ -327,7 +341,7 @@ func Test_ServerDelete(t *testing.T) {
t.Run("with sbs volumes", core.Test(&core.TestConfig{
Commands: core.NewCommandsMerge(
instance.GetCommands(),
blockCli.GetCommands(),
block.GetCommands(),
),
BeforeFunc: core.BeforeFuncCombine(
core.ExecStoreBeforeCmd("BlockVolume", "scw block volume create perf-iops=5000 from-empty.size=10G name=cli-test-server-delete-with-sbs-volumes"),
Expand All @@ -340,9 +354,9 @@ func Test_ServerDelete(t *testing.T) {
core.TestCheckExitCode(0),
func(t *testing.T, ctx *core.CheckFuncCtx) {
t.Helper()
api := block.NewAPI(ctx.Client)
blockVolume := ctx.Meta["BlockVolume"].(*block.Volume)
resp, err := api.GetVolume(&block.GetVolumeRequest{
api := blockSDK.NewAPI(ctx.Client)
blockVolume := ctx.Meta["BlockVolume"].(*blockSDK.Volume)
resp, err := api.GetVolume(&blockSDK.GetVolumeRequest{
Zone: blockVolume.Zone,
VolumeID: blockVolume.ID,
})
Expand Down
28 changes: 28 additions & 0 deletions internal/namespaces/instance/v1/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package instance_test
import (
"fmt"
"strings"
"testing"

"github.com/scaleway/scaleway-cli/v2/core"
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/stretchr/testify/require"
)

//
Expand Down Expand Up @@ -166,6 +170,11 @@ func deleteSnapshot(metaKey string) core.AfterFunc {
return core.ExecAfterCmd("scw instance snapshot delete {{ ." + metaKey + ".Snapshot.ID }}")
}

// deleteSnapshot deletes a snapshot previously registered in the context Meta at metaKey.
func deleteBlockSnapshot(metaKey string) core.AfterFunc {
return core.ExecAfterCmd("scw block snapshot delete {{ ." + metaKey + ".ID }}")
}

func createPN() core.BeforeFunc {
return core.ExecStoreBeforeCmd(
"PN",
Expand All @@ -179,3 +188,22 @@ func createNIC() core.BeforeFunc {
"scw instance private-nic create server-id={{ .Server.ID }} private-network-id={{ .PN.ID }}",
)
}

// testServerSBSVolumeSize checks the size of a volume in Result's server.
// The server must be returned as result of the test's Cmd
func testServerSBSVolumeSize(volumeKey string, sizeInGB int) core.TestCheck {
return func(t *testing.T, ctx *core.CheckFuncCtx) {
t.Helper()
require.NotNil(t, ctx.Result)
server := testhelpers.Value[*instance.Server](t, ctx.Result)
blockAPI := block.NewAPI(ctx.Client)
serverVolume := testhelpers.MapTValue(t, server.Volumes, volumeKey)
volume, err := blockAPI.GetVolume(&block.GetVolumeRequest{
Zone: server.Zone,
VolumeID: serverVolume.ID,
})
require.NoError(t, err)

require.Equal(t, scw.Size(sizeInGB)*scw.GB, volume.Size, "Size of volume should be %d GB", sizeInGB)
}
}
Loading

0 comments on commit 9850cc4

Please sign in to comment.