Skip to content

Commit 94cb5b9

Browse files
authored
fix: check image size is compatible with instance type (#690)
1 parent 8ac53ae commit 94cb5b9

File tree

57 files changed

+23932
-17209
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+23932
-17209
lines changed

internal/namespaces/instance/v1/custom_server_create.go

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,24 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac
189189
serverReq.Image = args.Image
190190
}
191191

192+
getImageResponse, err := apiInstance.GetImage(&instance.GetImageRequest{
193+
Zone: args.Zone,
194+
ImageID: serverReq.Image,
195+
})
196+
if err != nil {
197+
logger.Warningf("cannot get image %s: %s", serverReq.Image, err)
198+
}
199+
200+
serverType := getServeType(apiInstance, serverReq.Zone, serverReq.CommercialType)
201+
202+
if serverType != nil && getImageResponse != nil {
203+
if err := validateImageServerTypeCompatibility(getImageResponse.Image, serverType, serverReq.CommercialType); err != nil {
204+
return nil, err
205+
}
206+
} else {
207+
logger.Warningf("skipping image server-type compatibility validation")
208+
}
209+
192210
//
193211
// IP.
194212
//
@@ -241,13 +259,17 @@ func instanceServerCreateRun(ctx context.Context, argsI interface{}) (i interfac
241259
}
242260

243261
// Validate root volume type and size.
244-
if err := validateRootVolume(apiInstance, args.Zone, serverReq.Image, volumes["0"]); err != nil {
262+
if err := validateRootVolume(getImageResponse.Image.RootVolume.Size, volumes["0"]); err != nil {
245263
return nil, err
246264
}
247265

248266
// Validate total local volume sizes.
249-
if err := validateLocalVolumeSizes(apiInstance, volumes, args.Zone, serverReq.CommercialType); err != nil {
250-
return nil, err
267+
if serverType != nil {
268+
if err := validateLocalVolumeSizes(volumes, serverType, serverReq.CommercialType); err != nil {
269+
return nil, err
270+
}
271+
} else {
272+
logger.Warningf("skip local volume size validation")
251273
}
252274

253275
// Sanitize the volume map to respect API schemas
@@ -453,8 +475,22 @@ func buildVolumeTemplateFromUUID(api *instance.API, zone scw.Zone, volumeUUID st
453475
}, nil
454476
}
455477

478+
func validateImageServerTypeCompatibility(image *instance.Image, serverType *instance.ServerType, CommercialType string) error {
479+
if image.RootVolume.Size > serverType.VolumesConstraint.MaxSize {
480+
return fmt.Errorf("image %s requires %s on root volume, but root volume is constrained between %s and %s on %s",
481+
image.ID,
482+
humanize.Bytes(uint64(image.RootVolume.Size)),
483+
humanize.Bytes(uint64(serverType.VolumesConstraint.MinSize)),
484+
humanize.Bytes(uint64(serverType.VolumesConstraint.MaxSize)),
485+
CommercialType,
486+
)
487+
}
488+
489+
return nil
490+
}
491+
456492
// validateLocalVolumeSizes validates the total size of local volumes.
457-
func validateLocalVolumeSizes(api *instance.API, volumes map[string]*instance.VolumeTemplate, zone scw.Zone, commercialType string) error {
493+
func validateLocalVolumeSizes(volumes map[string]*instance.VolumeTemplate, serverType *instance.ServerType, commercialType string) error {
458494
// Calculate local volume total size.
459495
var localVolumeTotalSize scw.Size
460496
for _, volume := range volumes {
@@ -463,44 +499,27 @@ func validateLocalVolumeSizes(api *instance.API, volumes map[string]*instance.Vo
463499
}
464500
}
465501

466-
// Get server types.
467-
serverTypesRes, err := api.ListServersTypes(&instance.ListServersTypesRequest{
468-
Zone: zone,
469-
})
470-
if err != nil {
471-
// Ignore root volume size check.
472-
logger.Warningf("cannot get server types: %s", err)
473-
logger.Warningf("skip local volume size validation")
474-
return nil
475-
}
502+
volumeConstraint := serverType.VolumesConstraint
476503

477-
// Validate total size.
478-
if serverType, exists := serverTypesRes.Servers[commercialType]; exists {
479-
volumeConstraint := serverType.VolumesConstraint
504+
// If no root volume provided, count the default root volume size added by the API.
505+
if rootVolume := volumes["0"]; rootVolume == nil {
506+
localVolumeTotalSize += volumeConstraint.MinSize
507+
}
480508

481-
// If no root volume provided, count the default root volume size added by the API.
482-
if rootVolume := volumes["0"]; rootVolume == nil {
483-
localVolumeTotalSize += volumeConstraint.MinSize
509+
if localVolumeTotalSize < volumeConstraint.MinSize || localVolumeTotalSize > volumeConstraint.MaxSize {
510+
min := humanize.Bytes(uint64(volumeConstraint.MinSize))
511+
if volumeConstraint.MinSize == volumeConstraint.MaxSize {
512+
return fmt.Errorf("%s total local volume size must be equal to %s", commercialType, min)
484513
}
485514

486-
if localVolumeTotalSize < volumeConstraint.MinSize || localVolumeTotalSize > volumeConstraint.MaxSize {
487-
min := humanize.Bytes(uint64(volumeConstraint.MinSize))
488-
if volumeConstraint.MinSize == volumeConstraint.MaxSize {
489-
return fmt.Errorf("%s total local volume size must be equal to %s", commercialType, min)
490-
}
491-
492-
max := humanize.Bytes(uint64(volumeConstraint.MaxSize))
493-
return fmt.Errorf("%s total local volume size must be between %s and %s", commercialType, min, max)
494-
}
495-
} else {
496-
logger.Warningf("unrecognized server type: %s", commercialType)
497-
logger.Warningf("skip local volume size validation")
515+
max := humanize.Bytes(uint64(volumeConstraint.MaxSize))
516+
return fmt.Errorf("%s total local volume size must be between %s and %s", commercialType, min, max)
498517
}
499518

500519
return nil
501520
}
502521

503-
func validateRootVolume(api *instance.API, zone scw.Zone, image string, rootVolume *instance.VolumeTemplate) error {
522+
func validateRootVolume(imageRequiredSize scw.Size, rootVolume *instance.VolumeTemplate) error {
504523
if rootVolume == nil {
505524
return nil
506525
}
@@ -516,17 +535,8 @@ func validateRootVolume(api *instance.API, zone scw.Zone, image string, rootVolu
516535
}
517536
}
518537

519-
res, err := api.GetImage(&instance.GetImageRequest{
520-
Zone: zone,
521-
ImageID: image,
522-
})
523-
if err != nil {
524-
logger.Warningf("cannot get image %s: %s", image, err)
525-
logger.Warningf("skip root volume size validation")
526-
}
527-
528-
if rootVolume.Size < res.Image.RootVolume.Size {
529-
return fmt.Errorf("first volume size must be at least %s for this image", humanize.Bytes(uint64(res.Image.RootVolume.Size)))
538+
if rootVolume.Size < imageRequiredSize {
539+
return fmt.Errorf("first volume size must be at least %s for this image", humanize.Bytes(uint64(imageRequiredSize)))
530540
}
531541

532542
return nil
@@ -573,3 +583,22 @@ func instanceServerCreateImageAutoCompleteFunc(ctx context.Context, prefix strin
573583

574584
return suggestions
575585
}
586+
587+
// getServeType is a util to get a instance.ServerType by its commercialType
588+
func getServeType(apiInstance *instance.API, zone scw.Zone, commercialType string) *instance.ServerType {
589+
serverType := (*instance.ServerType)(nil)
590+
591+
serverTypesRes, err := apiInstance.ListServersTypes(&instance.ListServersTypesRequest{
592+
Zone: zone,
593+
})
594+
if err != nil {
595+
logger.Warningf("cannot get server types: %s", err)
596+
} else {
597+
serverType = serverTypesRes.Servers[commercialType]
598+
if serverType == nil {
599+
logger.Warningf("unrecognized server type: %s", commercialType)
600+
}
601+
}
602+
603+
return serverType
604+
}

internal/namespaces/instance/v1/custom_server_create_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,4 +444,13 @@ func Test_CreateServerErrors(t *testing.T) {
444444
core.TestCheckExitCode(1),
445445
),
446446
}))
447+
448+
t.Run("Error: image size is incompatible with instance type", core.Test(&core.TestConfig{
449+
Commands: GetCommands(),
450+
Cmd: "scw instance server create image=d4067cdc-dc9d-4810-8a26-0dae51d7df42 type=DEV1-S",
451+
Check: core.TestCheckCombine(
452+
core.TestCheckGolden(),
453+
core.TestCheckExitCode(1),
454+
),
455+
}))
447456
}

0 commit comments

Comments
 (0)