Skip to content

Commit 9850cc4

Browse files
authored
feat(instance): use sbs api for block volumes (#4435)
1 parent 080a371 commit 9850cc4

22 files changed

+6201
-5291
lines changed

internal/namespaces/instance/v1/custom_server_action_test.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/alecthomas/assert"
77
"github.com/scaleway/scaleway-cli/v2/core"
88
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
9+
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
910
"github.com/scaleway/scaleway-cli/v2/internal/namespaces/instance/v1"
1011
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
1112
instanceSDK "github.com/scaleway/scaleway-sdk-go/api/instance/v1"
@@ -63,16 +64,19 @@ func Test_ServerTerminate(t *testing.T) {
6364
}))
6465

6566
t.Run("without block", core.Test(&core.TestConfig{
66-
Commands: instance.GetCommands(),
67+
Commands: core.NewCommandsMerge(
68+
instance.GetCommands(),
69+
block.GetCommands(),
70+
),
6771
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("image=ubuntu-jammy additional-volumes.0=block:10G -w")),
6872
Cmd: `scw instance server terminate {{ .Server.ID }} with-ip=true with-block=false`,
6973
Check: core.TestCheckCombine(
7074
core.TestCheckGolden(),
7175
core.TestCheckExitCode(0),
7276
),
7377
AfterFunc: core.AfterFuncCombine(
74-
core.ExecAfterCmd(`scw instance volume wait {{ (index .Server.Volumes "1").ID }}`),
75-
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
78+
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
79+
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
7680
),
7781
DisableParallel: true,
7882
}))

internal/namespaces/instance/v1/custom_server_create_builder.go

+33-16
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func (sb *ServerBuilder) AddImage(image string) (*ServerBuilder, error) {
164164
ImageID: *(sb.createReq.Image),
165165
})
166166
if err != nil {
167-
logger.Warningf("cannot get image %s: %s", sb.createReq.Image, err)
167+
logger.Warningf("cannot get image %s: %s", *sb.createReq.Image, err)
168168
} else {
169169
sb.serverImage = getImageResponse.Image
170170
}
@@ -546,12 +546,11 @@ func NewVolumeBuilder(zone scw.Zone, flagV string) (*VolumeBuilder, error) {
546546
switch parts[0] {
547547
case "l", "local":
548548
vb.VolumeType = instance.VolumeVolumeTypeLSSD
549-
case "b", "block":
550-
vb.VolumeType = instance.VolumeVolumeTypeBSSD
549+
case "sbs", "b", "block":
550+
vb.VolumeType = instance.VolumeVolumeTypeSbsVolume
551551
case "s", "scratch":
552552
vb.VolumeType = instance.VolumeVolumeTypeScratch
553-
case "sbs":
554-
vb.VolumeType = instance.VolumeVolumeTypeSbsVolume
553+
555554
default:
556555
return nil, fmt.Errorf("invalid volume type %s in %s volume", parts[0], flagV)
557556
}
@@ -584,31 +583,49 @@ func NewVolumeBuilder(zone scw.Zone, flagV string) (*VolumeBuilder, error) {
584583
}
585584

586585
// buildSnapshotVolume builds the requested volume template to create a new volume from a snapshot
587-
func (vb *VolumeBuilder) buildSnapshotVolume(api *instance.API) (*instance.VolumeServerTemplate, error) {
586+
func (vb *VolumeBuilder) buildSnapshotVolume(api *instance.API, blockAPI *block.API) (*instance.VolumeServerTemplate, error) {
588587
if vb.SnapshotID == nil {
589588
return nil, errors.New("tried to build a volume from snapshot with an empty ID")
590589
}
591590
res, err := api.GetSnapshot(&instance.GetSnapshotRequest{
592591
Zone: vb.Zone,
593592
SnapshotID: *vb.SnapshotID,
594593
})
594+
if err != nil && !core.IsNotFoundError(err) {
595+
return nil, fmt.Errorf("invalid snapshot %s: %w", *vb.SnapshotID, err)
596+
}
597+
598+
if res != nil {
599+
snapshotType := res.Snapshot.VolumeType
600+
601+
if snapshotType != instance.VolumeVolumeTypeUnified && snapshotType != vb.VolumeType {
602+
return nil, fmt.Errorf("snapshot of type %s not compatible with requested volume type %s", snapshotType, vb.VolumeType)
603+
}
604+
605+
return &instance.VolumeServerTemplate{
606+
Name: &res.Snapshot.Name,
607+
VolumeType: vb.VolumeType,
608+
BaseSnapshot: &res.Snapshot.ID,
609+
Size: &res.Snapshot.Size,
610+
}, nil
611+
}
612+
613+
blockRes, err := blockAPI.GetSnapshot(&block.GetSnapshotRequest{
614+
Zone: vb.Zone,
615+
SnapshotID: *vb.SnapshotID,
616+
})
595617
if err != nil {
596618
if core.IsNotFoundError(err) {
597619
return nil, fmt.Errorf("snapshot %s does not exist", *vb.SnapshotID)
598620
}
599-
}
600-
601-
snapshotType := res.Snapshot.VolumeType
602-
603-
if snapshotType != instance.VolumeVolumeTypeUnified && snapshotType != vb.VolumeType {
604-
return nil, fmt.Errorf("snapshot of type %s not compatible with requested volume type %s", snapshotType, vb.VolumeType)
621+
return nil, err
605622
}
606623

607624
return &instance.VolumeServerTemplate{
608-
Name: &res.Snapshot.Name,
625+
Name: &blockRes.Name,
609626
VolumeType: vb.VolumeType,
610-
BaseSnapshot: &res.Snapshot.ID,
611-
Size: &res.Snapshot.Size,
627+
BaseSnapshot: &blockRes.ID,
628+
Size: &blockRes.Size,
612629
}, nil
613630
}
614631

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

677694
if vb.VolumeID != nil {

internal/namespaces/instance/v1/custom_server_create_test.go

+13-25
Original file line numberDiff line numberDiff line change
@@ -203,28 +203,24 @@ func Test_CreateServer(t *testing.T) {
203203
}))
204204

205205
t.Run("valid double snapshot", core.Test(&core.TestConfig{
206-
Commands: instance.GetCommands(),
206+
Commands: core.NewCommandsMerge(
207+
instance.GetCommands(),
208+
block.GetCommands(),
209+
),
207210
BeforeFunc: core.BeforeFuncCombine(
208-
core.ExecStoreBeforeCmd("Server", testServerCommand("image=ubuntu_bionic root-volume=local:20GB stopped=true")),
209-
core.ExecStoreBeforeCmd("Snapshot", `scw instance snapshot create unified=true volume-id={{ (index .Server.Volumes "0").ID }}`),
211+
core.ExecStoreBeforeCmd("Server", testServerCommand("image=ubuntu_jammy root-volume=block:20GB stopped=true")),
212+
core.ExecStoreBeforeCmd("Snapshot", `scw block snapshot create volume-id={{ (index .Server.Volumes "0").ID }} -w`),
210213
),
211-
Cmd: testServerCommand("image=ubuntu_bionic root-volume=block:{{ .Snapshot.Snapshot.ID }} additional-volumes.0=local:{{ .Snapshot.Snapshot.ID }} stopped=true"),
214+
Cmd: testServerCommand("image=ubuntu_jammy root-volume=block:{{ .Snapshot.ID }} additional-volumes.0=block:{{ .Snapshot.ID }} stopped=true"),
212215
Check: core.TestCheckCombine(
213216
core.TestCheckExitCode(0),
214-
func(t *testing.T, ctx *core.CheckFuncCtx) {
215-
t.Helper()
216-
assert.NotNil(t, ctx.Result)
217-
server := testhelpers.Value[*instanceSDK.Server](t, ctx.Result)
218-
size0 := testhelpers.MapTValue(t, server.Volumes, "0").Size
219-
size1 := testhelpers.MapTValue(t, server.Volumes, "1").Size
220-
assert.Equal(t, 20*scw.GB, instance.SizeValue(size0), "Size of volume should be 20 GB")
221-
assert.Equal(t, 20*scw.GB, instance.SizeValue(size1), "Size of volume should be 20 GB")
222-
},
217+
testServerSBSVolumeSize("0", 20),
218+
testServerSBSVolumeSize("1", 20),
223219
),
224220
AfterFunc: core.AfterFuncCombine(
225221
deleteServer("Server"),
226222
deleteServerAfterFunc(),
227-
deleteSnapshot("Snapshot"),
223+
deleteBlockSnapshot("Snapshot"),
228224
),
229225
}))
230226

@@ -233,17 +229,9 @@ func Test_CreateServer(t *testing.T) {
233229
Cmd: testServerCommand("image=ubuntu_bionic additional-volumes.0=b:1G additional-volumes.1=b:5G additional-volumes.2=b:10G stopped=true"),
234230
Check: core.TestCheckCombine(
235231
core.TestCheckExitCode(0),
236-
func(t *testing.T, ctx *core.CheckFuncCtx) {
237-
t.Helper()
238-
assert.NotNil(t, ctx.Result)
239-
server := testhelpers.Value[*instanceSDK.Server](t, ctx.Result)
240-
size1 := testhelpers.MapTValue(t, server.Volumes, "1").Size
241-
size2 := testhelpers.MapTValue(t, server.Volumes, "2").Size
242-
size3 := testhelpers.MapTValue(t, server.Volumes, "3").Size
243-
assert.Equal(t, 1*scw.GB, instance.SizeValue(size1), "Size of volume should be 1 GB")
244-
assert.Equal(t, 5*scw.GB, instance.SizeValue(size2), "Size of volume should be 5 GB")
245-
assert.Equal(t, 10*scw.GB, instance.SizeValue(size3), "Size of volume should be 10 GB")
246-
},
232+
testServerSBSVolumeSize("1", 1),
233+
testServerSBSVolumeSize("2", 5),
234+
testServerSBSVolumeSize("3", 10),
247235
),
248236
AfterFunc: deleteServerAfterFunc(),
249237
}))

internal/namespaces/instance/v1/custom_server_test.go

+27-13
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
"github.com/alecthomas/assert"
77
"github.com/scaleway/scaleway-cli/v2/core"
88
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
9-
blockCli "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
9+
block "github.com/scaleway/scaleway-cli/v2/internal/namespaces/block/v1alpha1"
1010
"github.com/scaleway/scaleway-cli/v2/internal/namespaces/instance/v1"
1111
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
12-
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
12+
blockSDK "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
1313
instanceSDK "github.com/scaleway/scaleway-sdk-go/api/instance/v1"
1414
"github.com/scaleway/scaleway-sdk-go/scw"
1515
"github.com/stretchr/testify/require"
@@ -73,7 +73,10 @@ func Test_ServerVolumeUpdate(t *testing.T) {
7373
})
7474
t.Run("Detach", func(t *testing.T) {
7575
t.Run("simple block volume", core.Test(&core.TestConfig{
76-
Commands: instance.GetCommands(),
76+
Commands: core.NewCommandsMerge(
77+
block.GetCommands(),
78+
instance.GetCommands(),
79+
),
7780
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")),
7881
Cmd: `scw instance server detach-volume volume-id={{ (index .Server.Volumes "1").ID }}`,
7982
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
@@ -85,7 +88,8 @@ func Test_ServerVolumeUpdate(t *testing.T) {
8588
assert.Equal(t, 1, len(ctx.Result.(*instanceSDK.DetachVolumeResponse).Server.Volumes))
8689
},
8790
AfterFunc: core.AfterFuncCombine(
88-
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
91+
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
92+
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
8993
deleteServer("Server"),
9094
),
9195
DisableParallel: true,
@@ -246,7 +250,10 @@ func Test_ServerUpdateCustom(t *testing.T) {
246250
}))
247251

248252
t.Run("detach all volumes", core.Test(&core.TestConfig{
249-
Commands: instance.GetCommands(),
253+
Commands: core.NewCommandsMerge(
254+
block.GetCommands(),
255+
instance.GetCommands(),
256+
),
250257
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")),
251258
Cmd: `scw instance server update {{ .Server.ID }} volume-ids=none`,
252259
Check: func(t *testing.T, ctx *core.CheckFuncCtx) {
@@ -255,8 +262,9 @@ func Test_ServerUpdateCustom(t *testing.T) {
255262
assert.Equal(t, 0, len(ctx.Result.(*instanceSDK.UpdateServerResponse).Server.Volumes))
256263
},
257264
AfterFunc: core.AfterFuncCombine(
258-
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "0").ID }}`),
259-
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
265+
core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "0").ID }}`), // Local volume
266+
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
267+
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
260268
deleteServer("Server"),
261269
),
262270
}))
@@ -292,14 +300,20 @@ func Test_ServerDelete(t *testing.T) {
292300
}))
293301

294302
t.Run("only local volumes", core.Test(&core.TestConfig{
295-
Commands: instance.GetCommands(),
303+
Commands: core.NewCommandsMerge(
304+
block.GetCommands(),
305+
instance.GetCommands(),
306+
),
296307
BeforeFunc: core.ExecStoreBeforeCmd("Server", testServerCommand("stopped=true image=ubuntu-bionic additional-volumes.0=block:10G")),
297308
Cmd: `scw instance server delete {{ .Server.ID }} with-ip=true with-volumes=local`,
298309
Check: core.TestCheckCombine(
299310
core.TestCheckGolden(),
300311
core.TestCheckExitCode(0),
301312
),
302-
AfterFunc: core.ExecAfterCmd(`scw instance volume delete {{ (index .Server.Volumes "1").ID }}`),
313+
AfterFunc: core.AfterFuncCombine(
314+
core.ExecAfterCmd(`scw block volume wait terminal-status=available {{ (index .Server.Volumes "1").ID }}`),
315+
core.ExecAfterCmd(`scw block volume delete {{ (index .Server.Volumes "1").ID }}`),
316+
),
303317
DisableParallel: true,
304318
}))
305319

@@ -327,7 +341,7 @@ func Test_ServerDelete(t *testing.T) {
327341
t.Run("with sbs volumes", core.Test(&core.TestConfig{
328342
Commands: core.NewCommandsMerge(
329343
instance.GetCommands(),
330-
blockCli.GetCommands(),
344+
block.GetCommands(),
331345
),
332346
BeforeFunc: core.BeforeFuncCombine(
333347
core.ExecStoreBeforeCmd("BlockVolume", "scw block volume create perf-iops=5000 from-empty.size=10G name=cli-test-server-delete-with-sbs-volumes"),
@@ -340,9 +354,9 @@ func Test_ServerDelete(t *testing.T) {
340354
core.TestCheckExitCode(0),
341355
func(t *testing.T, ctx *core.CheckFuncCtx) {
342356
t.Helper()
343-
api := block.NewAPI(ctx.Client)
344-
blockVolume := ctx.Meta["BlockVolume"].(*block.Volume)
345-
resp, err := api.GetVolume(&block.GetVolumeRequest{
357+
api := blockSDK.NewAPI(ctx.Client)
358+
blockVolume := ctx.Meta["BlockVolume"].(*blockSDK.Volume)
359+
resp, err := api.GetVolume(&blockSDK.GetVolumeRequest{
346360
Zone: blockVolume.Zone,
347361
VolumeID: blockVolume.ID,
348362
})

internal/namespaces/instance/v1/helpers_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package instance_test
33
import (
44
"fmt"
55
"strings"
6+
"testing"
67

78
"github.com/scaleway/scaleway-cli/v2/core"
9+
"github.com/scaleway/scaleway-cli/v2/internal/testhelpers"
810
block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
911
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
12+
"github.com/scaleway/scaleway-sdk-go/scw"
13+
"github.com/stretchr/testify/require"
1014
)
1115

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

173+
// deleteSnapshot deletes a snapshot previously registered in the context Meta at metaKey.
174+
func deleteBlockSnapshot(metaKey string) core.AfterFunc {
175+
return core.ExecAfterCmd("scw block snapshot delete {{ ." + metaKey + ".ID }}")
176+
}
177+
169178
func createPN() core.BeforeFunc {
170179
return core.ExecStoreBeforeCmd(
171180
"PN",
@@ -179,3 +188,22 @@ func createNIC() core.BeforeFunc {
179188
"scw instance private-nic create server-id={{ .Server.ID }} private-network-id={{ .PN.ID }}",
180189
)
181190
}
191+
192+
// testServerSBSVolumeSize checks the size of a volume in Result's server.
193+
// The server must be returned as result of the test's Cmd
194+
func testServerSBSVolumeSize(volumeKey string, sizeInGB int) core.TestCheck {
195+
return func(t *testing.T, ctx *core.CheckFuncCtx) {
196+
t.Helper()
197+
require.NotNil(t, ctx.Result)
198+
server := testhelpers.Value[*instance.Server](t, ctx.Result)
199+
blockAPI := block.NewAPI(ctx.Client)
200+
serverVolume := testhelpers.MapTValue(t, server.Volumes, volumeKey)
201+
volume, err := blockAPI.GetVolume(&block.GetVolumeRequest{
202+
Zone: server.Zone,
203+
VolumeID: serverVolume.ID,
204+
})
205+
require.NoError(t, err)
206+
207+
require.Equal(t, scw.Size(sizeInGB)*scw.GB, volume.Size, "Size of volume should be %d GB", sizeInGB)
208+
}
209+
}

0 commit comments

Comments
 (0)