@@ -11,12 +11,10 @@ import (
1111
1212 "github.com/dustin/go-humanize"
1313 "github.com/scaleway/scaleway-cli/v2/core"
14- block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1"
1514 "github.com/scaleway/scaleway-sdk-go/api/instance/v1"
1615 "github.com/scaleway/scaleway-sdk-go/api/marketplace/v2"
1716 "github.com/scaleway/scaleway-sdk-go/logger"
1817 "github.com/scaleway/scaleway-sdk-go/scw"
19- "github.com/scaleway/scaleway-sdk-go/validation"
2018)
2119
2220type instanceCreateServerRequest struct {
@@ -161,6 +159,10 @@ func serverCreateCommand() *core.Command {
161159 Short : "Create an instance with 2 local volumes (10GB and 10GB)" ,
162160 ArgsJSON : `{"image":"ubuntu_focal","root_volume":"local:10GB","additional_volumes":["local:10GB"]}` ,
163161 },
162+ {
163+ Short : "Create an instance with a SBS root volume (100GB and 15000 iops)" ,
164+ ArgsJSON : `{"image":"ubuntu_focal","root_volume":"sbs:100GB:15000"}` ,
165+ },
164166 {
165167 Short : "Create an instance with volumes from snapshots" ,
166168 ArgsJSON : `{"image":"ubuntu_focal","root_volume":"local:<snapshot_id>","additional_volumes":["block:<snapshot_id>"]}` ,
@@ -239,6 +241,7 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac
239241 }
240242
241243 createReq , createIPReq := serverBuilder .Build ()
244+ postCreationSetup := serverBuilder .BuildPostCreationSetup ()
242245 needIPCreation := createIPReq != nil
243246
244247 //
@@ -280,6 +283,13 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac
280283 server := serverRes .Server
281284 logger .Debugf ("server created %s" , server .ID )
282285
286+ // Post server creation setup
287+ /// Setup SBS volumes IOPS
288+ err = postCreationSetup (ctx , server )
289+ if err != nil {
290+ logger .Warningf ("error while setting up server after creation: %s" , err .Error ())
291+ }
292+
283293 //
284294 // Cloud-init
285295 //
@@ -357,162 +367,6 @@ func addDefaultVolumes(serverType *instance.ServerType, volumes map[string]*inst
357367 return volumes
358368}
359369
360- // buildVolumes creates the initial volume map.
361- // It is not the definitive one, it will be mutated all along the process.
362- func buildVolumes (api * instance.API , blockAPI * block.API , zone scw.Zone , serverName , rootVolume string , additionalVolumes []string ) (map [string ]* instance.VolumeServerTemplate , error ) {
363- volumes := make (map [string ]* instance.VolumeServerTemplate )
364- if rootVolume != "" {
365- rootVolumeTemplate , err := buildVolumeTemplate (api , blockAPI , zone , rootVolume )
366- if err != nil {
367- return nil , err
368- }
369-
370- volumes ["0" ] = rootVolumeTemplate
371- }
372-
373- for i , v := range additionalVolumes {
374- volumeTemplate , err := buildVolumeTemplate (api , blockAPI , zone , v )
375- if err != nil {
376- return nil , err
377- }
378- index := strconv .Itoa (i + 1 )
379- volumeTemplate .Name = scw .StringPtr (serverName + "-" + index )
380-
381- volumes [index ] = volumeTemplate
382- }
383-
384- return volumes , nil
385- }
386-
387- // buildVolumeTemplate creates a instance.VolumeTemplate from a 'volumes' argument item.
388- //
389- // Volumes definition must be through multiple arguments (eg: volumes.0="l:20GB" volumes.1="b:100GB")
390- //
391- // A valid volume format is either
392- // - a "creation" format: ^((local|l|block|b|scratch|s):)?\d+GB?$ (size is handled by go-humanize, so other sizes are supported)
393- // - a "creation" format with a snapshot id: l:<uuid> b:<uuid>
394- // - a UUID format
395- func buildVolumeTemplate (api * instance.API , blockAPI * block.API , zone scw.Zone , flagV string ) (* instance.VolumeServerTemplate , error ) {
396- parts := strings .Split (strings .TrimSpace (flagV ), ":" )
397-
398- // Create volume.
399- if len (parts ) == 2 {
400- vt := & instance.VolumeServerTemplate {}
401-
402- switch parts [0 ] {
403- case "l" , "local" :
404- vt .VolumeType = instance .VolumeVolumeTypeLSSD
405- case "b" , "block" :
406- vt .VolumeType = instance .VolumeVolumeTypeBSSD
407- case "s" , "scratch" :
408- vt .VolumeType = instance .VolumeVolumeTypeScratch
409- case "sbs" :
410- vt .VolumeType = instance .VolumeVolumeTypeSbsVolume
411- default :
412- return nil , fmt .Errorf ("invalid volume type %s in %s volume" , parts [0 ], flagV )
413- }
414-
415- if validation .IsUUID (parts [1 ]) {
416- return buildVolumeTemplateFromSnapshot (api , zone , parts [1 ], vt .VolumeType )
417- }
418-
419- size , err := humanize .ParseBytes (parts [1 ])
420- if err != nil {
421- return nil , fmt .Errorf ("invalid size format %s in %s volume" , parts [1 ], flagV )
422- }
423- vt .Size = scw .SizePtr (scw .Size (size ))
424-
425- return vt , nil
426- }
427-
428- // UUID format.
429- if len (parts ) == 1 && validation .IsUUID (parts [0 ]) {
430- return buildVolumeTemplateFromUUID (api , blockAPI , zone , parts [0 ])
431- }
432-
433- return nil , & core.CliError {
434- Err : fmt .Errorf ("invalid volume format '%s'" , flagV ),
435- Details : "" ,
436- Hint : `You must provide either a UUID ("11111111-1111-1111-1111-111111111111"), a local volume size ("local:100G" or "l:100G") or a block volume size ("block:100G" or "b:100G").` ,
437- }
438- }
439-
440- // buildVolumeTemplateFromUUID validate an UUID volume and add their types and sizes.
441- // Add volume types and sizes allow US to treat UUID volumes like the others and simplify the implementation.
442- // The instance API refuse the type and the size for UUID volumes, therefore,
443- // sanitizeVolumeMap function will remove them.
444- func buildVolumeTemplateFromUUID (api * instance.API , blockAPI * block.API , zone scw.Zone , volumeUUID string ) (* instance.VolumeServerTemplate , error ) {
445- res , err := api .GetVolume (& instance.GetVolumeRequest {
446- Zone : zone ,
447- VolumeID : volumeUUID ,
448- })
449- if err != nil && ! core .IsNotFoundError (err ) {
450- return nil , err
451- }
452-
453- if res != nil {
454- // Check that volume is not already attached to a server.
455- if res .Volume .Server != nil {
456- return nil , fmt .Errorf ("volume %s is already attached to %s server" , res .Volume .ID , res .Volume .Server .ID )
457- }
458-
459- return & instance.VolumeServerTemplate {
460- ID : & res .Volume .ID ,
461- VolumeType : res .Volume .VolumeType ,
462- Size : & res .Volume .Size ,
463- }, nil
464- }
465-
466- blockRes , err := blockAPI .GetVolume (& block.GetVolumeRequest {
467- Zone : zone ,
468- VolumeID : volumeUUID ,
469- })
470- if err != nil {
471- if core .IsNotFoundError (err ) {
472- return nil , fmt .Errorf ("volume %s does not exist" , volumeUUID )
473- }
474- return nil , err
475- }
476-
477- if len (blockRes .References ) > 0 {
478- return nil , fmt .Errorf ("volume %s is already attached to %s %s" , blockRes .ID , blockRes .References [0 ].ProductResourceID , blockRes .References [0 ].ProductResourceType )
479- }
480-
481- return & instance.VolumeServerTemplate {
482- ID : & blockRes .ID ,
483- VolumeType : instance .VolumeVolumeTypeSbsVolume , // TODO: support snapshot
484- }, nil
485- }
486-
487- // buildVolumeTemplateFromUUID validate a snapshot UUID and check that requested volume type is compatible.
488- // The instance API refuse the size for Snapshot volumes, therefore,
489- // sanitizeVolumeMap function will remove them.
490- func buildVolumeTemplateFromSnapshot (api * instance.API , zone scw.Zone , snapshotUUID string , volumeType instance.VolumeVolumeType ) (* instance.VolumeServerTemplate , error ) {
491- res , err := api .GetSnapshot (& instance.GetSnapshotRequest {
492- Zone : zone ,
493- SnapshotID : snapshotUUID ,
494- })
495- if err != nil {
496- if core .IsNotFoundError (err ) {
497- return nil , fmt .Errorf ("snapshot %s does not exist" , snapshotUUID )
498- }
499- return nil , err
500- }
501-
502- snapshotType := res .Snapshot .VolumeType
503-
504- if snapshotType != instance .VolumeVolumeTypeUnified && snapshotType != volumeType {
505- return nil , fmt .Errorf ("snapshot of type %s not compatible with requested volume type %s" , snapshotType , volumeType )
506- }
507-
508- return & instance.VolumeServerTemplate {
509- Name : & res .Snapshot .Name ,
510- VolumeType : volumeType ,
511- BaseSnapshot : & res .Snapshot .ID ,
512- Size : & res .Snapshot .Size ,
513- }, nil
514- }
515-
516370func validateImageServerTypeCompatibility (image * instance.Image , serverType * instance.ServerType , commercialType string ) error {
517371 // An instance might not have any constraints on the local volume size
518372 if serverType .VolumesConstraint .MaxSize == 0 {
0 commit comments