Skip to content
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
48 changes: 40 additions & 8 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,21 @@ type ProductVolumeConstraint struct {
MaxSize uint64 `json:"max_size,omitempty"`
}

// ProductVolumeConstraint contains any per volume constraint that the offer has
type ProductPerVolumeConstraint struct {
LSsdConstraint ProductVolumeConstraint `json:"l_ssd,omitempty"`
}

// ProductServerOffer represents a specific offer
type ProductServer struct {
Arch string `json:"arch,omitempty"`
Ncpus uint64 `json:"ncpus,omitempty"`
Ram uint64 `json:"ram,omitempty"`
Baremetal bool `json:"baremetal,omitempty"`
VolumesConstraint ProductVolumeConstraint `json:"volumes_constraint,omitempty"`
AltNames []string `json:"alt_names,omitempty"`
Network ProductNetwork `json:"network,omitempty"`
Arch string `json:"arch,omitempty"`
Ncpus uint64 `json:"ncpus,omitempty"`
Ram uint64 `json:"ram,omitempty"`
Baremetal bool `json:"baremetal,omitempty"`
VolumesConstraint ProductVolumeConstraint `json:"volumes_constraint,omitempty"`
PerVolumesConstraint ProductPerVolumeConstraint `json:"per_volume_constraint,omitempty"`
AltNames []string `json:"alt_names,omitempty"`
Network ProductNetwork `json:"network,omitempty"`
}

// Products holds a map of all Scaleway servers
Expand Down Expand Up @@ -634,6 +640,32 @@ type ScalewayServerPatchDefinition struct {
BootType *string `json:"boot_type,omitempty"`
}

type ScalewayServerVolumeDefinition interface {
isScalewayServerVolumeDefinition()
}

type ScalewayServerVolumeDefinitionNew struct {
Name string `json:"name"`
OrganizationId string `json:"organization"`
Size uint64 `json:"size"`
VolumeType string `json:"volume_type"`
}

func (*ScalewayServerVolumeDefinitionNew) isScalewayServerVolumeDefinition() {
}

type ScalewayServerVolumeDefinitionResize struct {
Size uint64 `json:"size"`
}

func (*ScalewayServerVolumeDefinitionResize) isScalewayServerVolumeDefinition() {
}

type ScalewayServerVolumeDefinitionFromId string

func (ScalewayServerVolumeDefinitionFromId) isScalewayServerVolumeDefinition() {
}

// ScalewayServerDefinition represents a Scaleway server with image definition
type ScalewayServerDefinition struct {
// Name is the user-defined name of the server
Expand All @@ -643,7 +675,7 @@ type ScalewayServerDefinition struct {
Image *string `json:"image,omitempty"`

// Volumes are the attached volumes
Volumes map[string]string `json:"volumes,omitempty"`
Volumes map[string]ScalewayServerVolumeDefinition `json:"volumes,omitempty"`

// DynamicIPRequired is a flag that defines a server with a dynamic ip address attached
DynamicIPRequired *bool `json:"dynamic_ip_required,omitempty"`
Expand Down
111 changes: 68 additions & 43 deletions pkg/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package api
import (
"errors"
"fmt"
"math"
"os"
"sort"
"strings"
Expand Down Expand Up @@ -95,34 +94,29 @@ func CreateVolumeFromHumanSize(api *ScalewayAPI, size string) (*string, error) {
return &volumeID, nil
}

// VolumesFromSize returns a string of standard sized volumes from a given size
func VolumesFromSize(size uint64) string {
const DefaultVolumeSize float64 = 50000000000
StdVolumeSizes := []struct {
kind string
capacity float64
}{
{"150G", 150000000000},
{"100G", 100000000000},
{"50G", 50000000000},
}

RequiredSize := float64(size) - DefaultVolumeSize
Volumes := ""
for _, v := range StdVolumeSizes {
q := RequiredSize / v.capacity
r := math.Mod(RequiredSize, v.capacity)
RequiredSize = r

if q > 0 {
Volumes += strings.Repeat(v.kind+" ", int(q))
}
if r == 0 {
break
}
func min(a, b uint64) uint64 {
if a > b {
return b
}
return a
}

const Giga = 1000000000

return strings.TrimSpace(Volumes)
// VolumesFromSize returns a string of standard sized volumes from a given size
func VolumesFromSize(rootVolumeSize, targetSize, perVolumeMaxSize uint64) string {
if targetSize <= rootVolumeSize {
return ""
}
targetSize -= rootVolumeSize
q := targetSize / perVolumeMaxSize
r := targetSize % perVolumeMaxSize
humanSize := fmt.Sprintf("%dG", perVolumeMaxSize/Giga)
volumes := strings.Repeat(humanSize+" ", int(q))
if r != 0 {
volumes += fmt.Sprintf("%dG", r/Giga)
}
return strings.TrimSpace(volumes)
}

// fillIdentifierCache fills the cache by fetching from the API
Expand Down Expand Up @@ -396,7 +390,7 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) {
var server ScalewayServerDefinition

server.CommercialType = commercialType
server.Volumes = make(map[string]string)
server.Volumes = make(map[string]ScalewayServerVolumeDefinition)
server.DynamicIPRequired = &c.DynamicIPRequired
server.EnableIPV6 = c.EnableIPV6
server.BootType = c.BootType
Expand Down Expand Up @@ -435,21 +429,59 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) {
if err != nil {
return "", fmt.Errorf("Unknow commercial type %v: %v", server.CommercialType, err)
}
//
// Find the correct root size
//
// 1- the user define a custom root size
// 2- (default) use the largest possible size ==> min(categoryMaxSize,volumeMaxSize)
// 3- the user specify additional volumes ==> min(50G,volumeMaxSize)
//
isUserDefinedRootSize := true
rootVolumeSize, err := humanize.ParseBytes(c.ImageName)
if err != nil {
isUserDefinedRootSize = false
rootVolumeSize = min(offer.PerVolumesConstraint.LSsdConstraint.MaxSize, offer.VolumesConstraint.MaxSize)
if c.AdditionalVolumes != "" {
rootVolumeSize = min(50*Giga, offer.VolumesConstraint.MaxSize) // create a volume up to 50GB
}
}

if isUserDefinedRootSize {
// create a new volume from scratch
server.Volumes["0"] = &ScalewayServerVolumeDefinitionNew{
OrganizationId: api.Organization,
VolumeType: "l_ssd",
Name: "Volume-0",
Size: rootVolumeSize,
}
} else {
// leverage compute image resizing
server.Volumes["0"] = &ScalewayServerVolumeDefinitionResize{
Size: rootVolumeSize,
}
}

if offer.VolumesConstraint.MinSize > 0 && c.AdditionalVolumes == "" {
c.AdditionalVolumes = VolumesFromSize(offer.VolumesConstraint.MinSize)
log.Debugf("%s needs at least %s. Automatically creates the following volumes: %s",
server.CommercialType, humanize.Bytes(offer.VolumesConstraint.MinSize), c.AdditionalVolumes)
c.AdditionalVolumes = VolumesFromSize(rootVolumeSize, offer.VolumesConstraint.MinSize, offer.PerVolumesConstraint.LSsdConstraint.MaxSize)
log.Debugf("%s needs at least %s. Automatically creates the following volumes: %dG %s",
server.CommercialType, humanize.Bytes(offer.VolumesConstraint.MinSize), rootVolumeSize/Giga, c.AdditionalVolumes)
}

if c.AdditionalVolumes != "" {
volumes := strings.Split(c.AdditionalVolumes, " ")
for i := range volumes {
volumeID, err := CreateVolumeFromHumanSize(api, volumes[i])
rootSize, err := humanize.ParseBytes(volumes[i])
if err != nil {
return "", err
}

volumeIDx := fmt.Sprintf("%d", i+1)
server.Volumes[volumeIDx] = *volumeID
server.Volumes[volumeIDx] = &ScalewayServerVolumeDefinitionNew{
OrganizationId: api.Organization,
VolumeType: "l_ssd",
Name: "Volume-" + volumeIDx,
Size: rootSize,
}
}
}

Expand All @@ -462,15 +494,8 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) {
}
server.Name = c.Name
inheritingVolume := false
_, err = humanize.ParseBytes(c.ImageName)
if err == nil {
// Create a new root volume
volumeID, errCreateVol := CreateVolumeFromHumanSize(api, c.ImageName)
if errCreateVol != nil {
return "", errCreateVol
}
server.Volumes["0"] = *volumeID
} else {

if !isUserDefinedRootSize {
// Use an existing image
inheritingVolume = true
if anonuuid.IsUUID(c.ImageName) == nil {
Expand All @@ -494,7 +519,7 @@ func CreateServer(api *ScalewayAPI, c *ConfigCreateServer) (string, error) {
if snapshot.BaseVolume.Identifier == "" {
return "", fmt.Errorf("snapshot %v does not have base volume", snapshot.Name)
}
server.Volumes["0"] = snapshot.BaseVolume.Identifier
server.Volumes["0"] = ScalewayServerVolumeDefinitionFromId(snapshot.BaseVolume.Identifier)
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions pkg/api/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package api

import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)

type VolumesFromSizeCase struct {
name string
input struct {
rootVolumeSize, targeSize, perVolumeMaxSize uint64
}
output string
}

func TestVolumesFromSize(t *testing.T) {
tests := []VolumesFromSizeCase{
{
name: "200G 200G 200G",
input: struct{ rootVolumeSize, targeSize, perVolumeMaxSize uint64 }{
200 * Giga, 600 * Giga, 200 * Giga,
},
output: "200G 200G",
},
{
name: "200G 200G 100G",
input: struct{ rootVolumeSize, targeSize, perVolumeMaxSize uint64 }{
200 * Giga, 500 * Giga, 200 * Giga,
},
output: "200G 100G",
},
{
name: "25G",
input: struct{ rootVolumeSize, targeSize, perVolumeMaxSize uint64 }{
25 * Giga, 25 * Giga, 200 * Giga,
},
output: "",
},
{
name: "100G 150G",
input: struct{ rootVolumeSize, targeSize, perVolumeMaxSize uint64 }{
100 * Giga, 250 * Giga, 200 * Giga,
},
output: "150G",
},
{
name: "200G 50G 50G 50G 50G",
input: struct{ rootVolumeSize, targeSize, perVolumeMaxSize uint64 }{
200 * Giga, 400 * Giga, 50 * Giga,
},
output: "50G 50G 50G 50G",
},
}
for _, test := range tests {
Convey("Testing VolumesFromSize with expected "+test.name, t, func(c C) {
output := VolumesFromSize(test.input.rootVolumeSize, test.input.targeSize, test.input.perVolumeMaxSize)
c.So(output, ShouldEqual, test.output)
})
}

}
10 changes: 5 additions & 5 deletions pkg/api/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ func (l *defaultLogger) LogHTTP(r *http.Request) {
}

func (l *defaultLogger) Fatalf(format string, v ...interface{}) {
l.Printf("[FATAL] %s\n", fmt.Sprintf(format, v))
l.Printf("[FATAL] %s\n", fmt.Sprintf(format, v...))
os.Exit(1)
}

func (l *defaultLogger) Debugf(format string, v ...interface{}) {
l.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v))
l.Printf("[DEBUG] %s\n", fmt.Sprintf(format, v...))
}

func (l *defaultLogger) Infof(format string, v ...interface{}) {
l.Printf("[INFO ] %s\n", fmt.Sprintf(format, v))
l.Printf("[INFO ] %s\n", fmt.Sprintf(format, v...))
}

func (l *defaultLogger) Warnf(format string, v ...interface{}) {
l.Printf("[WARN ] %s\n", fmt.Sprintf(format, v))
l.Printf("[WARN ] %s\n", fmt.Sprintf(format, v...))
}

type disableLogger struct {
Expand All @@ -64,7 +64,7 @@ func (d *disableLogger) LogHTTP(r *http.Request) {
}

func (d *disableLogger) Fatalf(format string, v ...interface{}) {
panic(fmt.Sprintf(format, v))
panic(fmt.Sprintf(format, v...))
}

func (d *disableLogger) Debugf(format string, v ...interface{}) {
Expand Down