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

CA: GCE: implement GetMachineFamily, fix IsCustomMachine #5024

Merged
merged 1 commit into from
Jul 13, 2022
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
17 changes: 4 additions & 13 deletions cluster-autoscaler/cloudprovider/gce/gce_price_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package gce
import (
"math"
"strconv"
"strings"
"time"

apiv1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -142,10 +141,10 @@ func (model *GcePriceModel) getPreemptibleDiscount(node *apiv1.Node) float64 {
if !found {
return 1.0
}
instanceFamily := getInstanceFamily(instanceType)
instanceFamily, _ := GetMachineFamily(instanceType)

discountMap := model.PriceInfo.PredefinedPreemptibleDiscount()
if isInstanceCustom(instanceType) {
if IsCustomMachine(instanceType) {
discountMap = model.PriceInfo.CustomPreemptibleDiscount()
}

Expand All @@ -171,8 +170,8 @@ func (model *GcePriceModel) getBasePrice(resources apiv1.ResourceList, instanceT
return 0
}
hours := getHours(startTime, endTime)
instanceFamily := getInstanceFamily(instanceType)
isCustom := isInstanceCustom(instanceType)
instanceFamily, _ := GetMachineFamily(instanceType)
isCustom := IsCustomMachine(instanceType)
price := 0.0

cpu := resources[apiv1.ResourceCPU]
Expand Down Expand Up @@ -224,14 +223,6 @@ func getHours(startTime time.Time, endTime time.Time) float64 {
return hours
}

func getInstanceFamily(instanceType string) string {
return strings.Split(instanceType, "-")[0]
}

func isInstanceCustom(instanceType string) bool {
return strings.Contains(instanceType, "custom")
}

// hasPreemptiblePricing returns whether we should use preemptible pricing for a node, based on labels. Spot VMs have
// dynamic pricing, which is different than the static pricing for Preemptible VMs we use here. However it should be close
// enough in practice and we really only look at prices in comparison with each other. Spot VMs will always be cheaper
Expand Down
58 changes: 47 additions & 11 deletions cluster-autoscaler/cloudprovider/gce/machine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package gce

import (
"fmt"
"strconv"
"strings"

"k8s.io/autoscaler/cluster-autoscaler/utils/units"
Expand All @@ -37,8 +38,29 @@ type MachineType struct {
}

// IsCustomMachine checks if a machine type is custom or predefined.
func IsCustomMachine(name string) bool {
return strings.HasPrefix(name, "custom-")
func IsCustomMachine(machineType string) bool {
// Custom types are in the form "<family>-custom-<num_cpu>-<memory_mb>", with the "<family>-" prefix being optional for the N1
// family. Examples: n2-custom-48-184320, custom-48-184320 (equivalent to n1-custom-48-184320).
// Docs: https://cloud.google.com/compute/docs/instances/creating-instance-with-custom-machine-type#gcloud.
parts := strings.Split(machineType, "-")
if len(parts) < 2 {
return false
}
return parts[0] == "custom" || parts[1] == "custom"
}

// GetMachineFamily gets the machine family from the machine type. Machine family is determined as everything before the first
// dash character, unless this first part is "custom", which actually means "n1":
// https://cloud.google.com/compute/docs/instances/creating-instance-with-custom-machine-type#gcloud.
func GetMachineFamily(machineType string) (string, error) {
parts := strings.Split(machineType, "-")
if len(parts) < 2 {
return "", fmt.Errorf("unable to parse machine type %q", machineType)
}
if parts[0] == "custom" {
return "n1", nil
}
return parts[0], nil
}

// NewMachineTypeFromAPI creates a MachineType object based on machine type representation
Expand All @@ -57,20 +79,34 @@ func NewMachineTypeFromAPI(name string, mt *gce_api.MachineType) (MachineType, e
// NewCustomMachineType creates a MachineType object describing a custom GCE machine.
// CPU and Memory are based on parsing custom machine name.
func NewCustomMachineType(name string) (MachineType, error) {
// example custom-2-2816
var cpu, mem int64
var count int
count, err := fmt.Sscanf(name, "custom-%d-%d", &cpu, &mem)
if !IsCustomMachine(name) {
return MachineType{}, fmt.Errorf("%q is not a valid custom machine type", name)
}

parts := strings.Split(name, "-")
var cpuPart, memPart string
if len(parts) == 3 {
cpuPart = parts[1]
memPart = parts[2]
} else if len(parts) == 4 {
cpuPart = parts[2]
memPart = parts[3]
} else {
return MachineType{}, fmt.Errorf("unable to parse custom machine type %q", name)
}

cpu, err := strconv.ParseInt(cpuPart, 10, 64)
if err != nil {
return MachineType{}, err
return MachineType{}, fmt.Errorf("unable to parse CPU %q from machine type %q: %v", cpuPart, name, err)
}
if count != 2 {
return MachineType{}, fmt.Errorf("failed to parse all params in %s", name)
memBytes, err := strconv.ParseInt(memPart, 10, 64)
if err != nil {
return MachineType{}, fmt.Errorf("unable to parse memory %q from machine type %q: %v", memPart, name, err)
}
mem = mem * units.MiB

return MachineType{
Name: name,
CPU: cpu,
Memory: mem,
Memory: memBytes * units.MiB,
}, nil
}
47 changes: 47 additions & 0 deletions cluster-autoscaler/cloudprovider/gce/machine_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (

"k8s.io/autoscaler/cluster-autoscaler/utils/units"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
)

Expand All @@ -37,6 +39,12 @@ func TestNewCustomMachineType(t *testing.T) {
expectCPU: 2,
expectMemory: 2816 * units.MiB,
},
{
name: "n2-custom-2-2816",
expectCustom: true,
expectCPU: 2,
expectMemory: 2816 * units.MiB,
},
{
name: "other-a2-2816",
},
Expand All @@ -62,3 +70,42 @@ func TestNewCustomMachineType(t *testing.T) {
})
}
}

func TestGetMachineFamily(t *testing.T) {
for tn, tc := range map[string]struct {
machineType string
wantFamily string
wantErr error
}{
"predefined machine type": {
machineType: "n1-standard-8",
wantFamily: "n1",
},
"predefined short machine type": {
machineType: "e2-small",
wantFamily: "e2",
},
"custom machine type with family prefix": {
machineType: "n2-custom-2-2816",
wantFamily: "n2",
},
"custom machine type without family prefix": {
machineType: "custom-2-2816",
wantFamily: "n1",
},
"invalid machine type": {
machineType: "nodashes",
wantErr: cmpopts.AnyError,
},
} {
t.Run(tn, func(t *testing.T) {
gotFamily, gotErr := GetMachineFamily(tc.machineType)
if diff := cmp.Diff(tc.wantFamily, gotFamily); diff != "" {
t.Errorf("GetMachineFamily(%q): diff (-want +got):\n%s", tc.machineType, diff)
}
if diff := cmp.Diff(tc.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
t.Errorf("GetMachineFamily(%q): err diff (-want +got):\n%s", tc.machineType, diff)
}
})
}
}