Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(instance): use sbs api for block volumes #4435

Merged
merged 7 commits into from
Jan 23, 2025
Merged
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
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
Loading