diff --git a/internal/scheduler/internaltypes/resource_fraction_list.go b/internal/scheduler/internaltypes/resource_fraction_list.go index 233b4f6e5e6..b351636f024 100644 --- a/internal/scheduler/internaltypes/resource_fraction_list.go +++ b/internal/scheduler/internaltypes/resource_fraction_list.go @@ -2,6 +2,7 @@ package internaltypes import ( "fmt" + "math" ) type ResourceFractionList struct { @@ -13,6 +14,29 @@ func (rfl ResourceFractionList) IsEmpty() bool { return rfl.factory == nil } +func (rfl ResourceFractionList) Multiply(other ResourceFractionList) ResourceFractionList { + assertSameResourceListFactory(rfl.factory, other.factory) + if rfl.IsEmpty() || other.IsEmpty() { + return ResourceFractionList{} + } + + result := make([]float64, len(rfl.fractions)) + for i, r := range rfl.fractions { + result[i] = r * other.fractions[i] + } + return ResourceFractionList{factory: rfl.factory, fractions: result} +} + +func (rfl ResourceFractionList) Max() float64 { + result := math.Inf(-1) + for _, val := range rfl.fractions { + if val > result { + result = val + } + } + return result +} + func (rfl ResourceFractionList) GetByName(name string) (float64, error) { if rfl.IsEmpty() { return 0, fmt.Errorf("resource type %s not found as resource fraction list is empty", name) @@ -23,9 +47,3 @@ func (rfl ResourceFractionList) GetByName(name string) (float64, error) { } return rfl.fractions[index], nil } - -func (rfl ResourceFractionList) assertSameResourceListFactory(other ResourceList) { - if rfl.factory != nil && other.factory != nil && rfl.factory != other.factory { - panic("mismatched ResourceListFactory") - } -} diff --git a/internal/scheduler/internaltypes/resource_fraction_list_test.go b/internal/scheduler/internaltypes/resource_fraction_list_test.go index 5990282f4bf..a187fa778e1 100644 --- a/internal/scheduler/internaltypes/resource_fraction_list_test.go +++ b/internal/scheduler/internaltypes/resource_fraction_list_test.go @@ -1,6 +1,7 @@ package internaltypes import ( + "math" "testing" "github.com/stretchr/testify/assert" @@ -10,13 +11,41 @@ func TestResourceFractionList_IsEmpty(t *testing.T) { factory := testFactory() assert.True(t, ResourceFractionList{}.IsEmpty()) - assert.False(t, testResourceFractionList(factory, 0, 0).IsEmpty()) - assert.False(t, testResourceFractionList(factory, 1, 1).IsEmpty()) + assert.False(t, testResourceFractionList(factory, 0, 0, 0).IsEmpty()) + assert.False(t, testResourceFractionList(factory, 1, 1, 1).IsEmpty()) +} + +func TestMax(t *testing.T) { + factory := testFactory() + assert.Equal(t, 0.0, testResourceFractionList(factory, -0.1, 0.0, 0.0).Max()) + assert.Equal(t, 0.0, testResourceFractionList(factory, 0.0, 0.0, 0.0).Max()) + assert.Equal(t, 0.9, testResourceFractionList(factory, 0.1, 0.9, 0.7).Max()) +} + +func TestMax_HandlesEmptyCorrectly(t *testing.T) { + assert.Equal(t, math.Inf(-1), ResourceFractionList{}.Max()) +} + +func TestResourceFractionList_Multiply(t *testing.T) { + factory := testFactory() + + assert.Equal(t, + testResourceFractionList(factory, 0.125, 0.25, 1), + testResourceFractionList(factory, 0.25, 0.5, 1).Multiply( + testResourceFractionList(factory, 0.5, 0.5, 1))) +} + +func TestResourceFractionList_Multiply_HandlesEmptyCorrectly(t *testing.T) { + factory := testFactory() + + assert.Equal(t, ResourceFractionList{}, ResourceFractionList{}.Multiply(ResourceFractionList{})) + assert.Equal(t, ResourceFractionList{}, ResourceFractionList{}.Multiply(testResourceFractionList(factory, 1, 1, 1))) + assert.Equal(t, ResourceFractionList{}, testResourceFractionList(factory, 1, 1, 1).Multiply(ResourceFractionList{})) } func TestResourceFractionList_GetByName(t *testing.T) { factory := testFactory() - a := testResourceFractionList(factory, 0.1, 0.2) + a := testResourceFractionList(factory, 0.1, 0.2, 1) cpu, err := a.GetByName("cpu") assert.Nil(t, err) diff --git a/internal/scheduler/internaltypes/resource_list.go b/internal/scheduler/internaltypes/resource_list.go index b8d46ff692d..be4b42825f2 100644 --- a/internal/scheduler/internaltypes/resource_list.go +++ b/internal/scheduler/internaltypes/resource_list.go @@ -30,7 +30,7 @@ type Resource struct { } func (rl ResourceList) Equal(other ResourceList) bool { - rl.assertSameResourceListFactory(other) + assertSameResourceListFactory(rl.factory, other.factory) if rl.IsEmpty() && other.IsEmpty() { return true } @@ -157,7 +157,7 @@ func (rl ResourceList) IsEmpty() bool { // - if no resources in this ResourceList exceed available, the last return value is false. // - empty resource lists are considered equivalent to all zero. func (rl ResourceList) ExceedsAvailable(available ResourceList) (string, k8sResource.Quantity, k8sResource.Quantity, bool) { - rl.assertSameResourceListFactory(available) + assertSameResourceListFactory(rl.factory, available.factory) if rl.IsEmpty() && available.IsEmpty() { return "", k8sResource.Quantity{}, k8sResource.Quantity{}, false @@ -196,7 +196,7 @@ func (rl ResourceList) OfType(t ResourceType) ResourceList { } func (rl ResourceList) Add(other ResourceList) ResourceList { - rl.assertSameResourceListFactory(other) + assertSameResourceListFactory(rl.factory, other.factory) if rl.IsEmpty() { return other } @@ -211,7 +211,7 @@ func (rl ResourceList) Add(other ResourceList) ResourceList { } func (rl ResourceList) Subtract(other ResourceList) ResourceList { - rl.assertSameResourceListFactory(other) + assertSameResourceListFactory(rl.factory, other.factory) if other.IsEmpty() { return rl } @@ -226,7 +226,7 @@ func (rl ResourceList) Subtract(other ResourceList) ResourceList { } func (rl ResourceList) Multiply(multipliers ResourceFractionList) ResourceList { - multipliers.assertSameResourceListFactory(rl) + assertSameResourceListFactory(rl.factory, multipliers.factory) if rl.IsEmpty() || multipliers.IsEmpty() { return ResourceList{} } @@ -238,6 +238,23 @@ func (rl ResourceList) Multiply(multipliers ResourceFractionList) ResourceList { return ResourceList{factory: rl.factory, resources: result} } +// Divide, return 0 on attempt to divide by 0 +func (rl ResourceList) DivideZeroOnError(other ResourceList) ResourceFractionList { + assertSameResourceListFactory(rl.factory, other.factory) + if rl.IsEmpty() || other.IsEmpty() { + return ResourceFractionList{} + } + + result := make([]float64, len(rl.resources)) + for i, r := range rl.resources { + denom := other.resources[i] + if denom != 0 { + result[i] = float64(r) / float64(denom) + } + } + return ResourceFractionList{factory: rl.factory, fractions: result} +} + func (rl ResourceList) Negate() ResourceList { if rl.IsEmpty() { return rl @@ -274,12 +291,16 @@ func resourcesZeroIfEmpty(resources []int64, factory *ResourceListFactory) []int return resources } -func (rl ResourceList) assertSameResourceListFactory(other ResourceList) { - if rl.factory != nil && other.factory != nil && rl.factory != other.factory { +func assertSameResourceListFactory(a, b *ResourceListFactory) { + if a != nil && b != nil && a != b { panic("mismatched ResourceListFactory") } } func multiplyResource(res int64, multiplier float64) int64 { + if multiplier == 1.0 { + // avoid rounding error in the simple case + return res + } return int64(float64(res) * multiplier) } diff --git a/internal/scheduler/internaltypes/resource_list_test.go b/internal/scheduler/internaltypes/resource_list_test.go index b1942b1d895..216ab626670 100644 --- a/internal/scheduler/internaltypes/resource_list_test.go +++ b/internal/scheduler/internaltypes/resource_list_test.go @@ -258,25 +258,53 @@ func TestMultiply(t *testing.T) { assert.Equal(t, testResourceList(factory, "100", "150Ki"), testResourceList(factory, "400", "200Ki").Multiply( - testResourceFractionList(factory, 0.25, 0.75))) + testResourceFractionList(factory, 0.25, 0.75, 1))) assert.Equal(t, testResourceList(factory, "0", "0"), testResourceList(factory, "0", "200Ki").Multiply( - testResourceFractionList(factory, 0.25, 0))) + testResourceFractionList(factory, 0.25, 0, 1))) + assert.Equal(t, + testResourceList(factory, "2", "100Ki"), + testResourceList(factory, "2", "100Ki").Multiply( + testResourceFractionList(factory, 1, 1, 1))) assert.Equal(t, testResourceList(factory, "-100", "150Ki"), testResourceList(factory, "400", "-200Ki").Multiply( - testResourceFractionList(factory, -0.25, -0.75))) + testResourceFractionList(factory, -0.25, -0.75, 1))) } func TestMultiply_HandlesEmptyCorrectly(t *testing.T) { factory := testFactory() assert.Equal(t, ResourceList{}, ResourceList{}.Multiply(ResourceFractionList{})) - assert.Equal(t, ResourceList{}, ResourceList{}.Multiply(testResourceFractionList(factory, 1, 1))) + assert.Equal(t, ResourceList{}, ResourceList{}.Multiply(testResourceFractionList(factory, 1, 1, 1))) assert.Equal(t, ResourceList{}, testResourceList(factory, "1", "1Ki").Multiply(ResourceFractionList{})) } +func TestDivideZeroOnError(t *testing.T) { + factory := testFactory() + + expected := testResourceFractionList(factory, 0.5, 0.25, 0) + actual := testResourceList(factory, "2", "2Ki").DivideZeroOnError(testResourceList(factory, "4", "8Ki")) + assert.Equal(t, expected, actual) +} + +func TestDDivideZeroOnError_HandlesZeroDenominatorCorrectly(t *testing.T) { + factory := testFactory() + + expected := testResourceFractionList(factory, 2, 0, 0) + actual := testResourceList(factory, "2", "2Ki").DivideZeroOnError(testResourceList(factory, "1", "0Ki")) + assert.Equal(t, expected, actual) +} + +func TestDivideZeroOnError_HandlesEmptyCorrectly(t *testing.T) { + factory := testFactory() + + assert.Equal(t, ResourceFractionList{}, testResourceList(factory, "1", "1Ki").DivideZeroOnError(ResourceList{})) + assert.Equal(t, ResourceFractionList{}, ResourceList{}.DivideZeroOnError(testResourceList(factory, "1", "1Ki"))) + assert.Equal(t, ResourceFractionList{}, ResourceList{}.DivideZeroOnError(ResourceList{})) +} + func TestNegate(t *testing.T) { factory := testFactory() @@ -308,9 +336,9 @@ func testResourceList(factory *ResourceListFactory, cpu string, memory string) R }) } -func testResourceFractionList(factory *ResourceListFactory, cpu float64, memory float64) ResourceFractionList { +func testResourceFractionList(factory *ResourceListFactory, cpu float64, memory float64, defaultValue float64) ResourceFractionList { return factory.MakeResourceFractionList(map[string]float64{ "cpu": cpu, "memory": memory, - }, 1) + }, defaultValue) }