blob: 8ce6fb12e9011597f1f7b04dc5368d3b375b1e3a [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
import (
"fmt"
"testing"
"gotest.tools/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
apis "k8s.io/apimachinery/pkg/apis/meta/v1"
siCommon "github.com/apache/yunikorn-scheduler-interface/lib/go/common"
"github.com/apache/yunikorn-scheduler-interface/lib/go/si"
)
func TestAdd(t *testing.T) {
r1 := NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
r2 := NewResourceBuilder().
AddResource(siCommon.Memory, 2).
AddResource(siCommon.CPU, 2).
Build()
r := Add(r1, r2)
assert.Equal(t, len(r.Resources), 2)
assert.Equal(t, r.Resources[siCommon.Memory].Value, int64(3))
assert.Equal(t, r.Resources[siCommon.CPU].Value, int64(3))
r1 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
Build()
r2 = NewResourceBuilder().
AddResource(siCommon.Memory, 2).
AddResource(siCommon.CPU, 2).
Build()
r = Add(r1, r2)
assert.Equal(t, len(r.Resources), 2)
assert.Equal(t, r.Resources[siCommon.Memory].Value, int64(3))
assert.Equal(t, r.Resources[siCommon.CPU].Value, int64(2))
r1 = nil
r2 = nil
r = Add(r1, r2)
assert.Equal(t, len(r.Resources), 0)
r1 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
Build()
r2 = nil
r = Add(r1, r2)
assert.Equal(t, len(r.Resources), 1)
assert.Equal(t, r.Resources[siCommon.Memory].Value, int64(1))
r1 = nil
r2 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
Build()
r = Add(r1, r2)
assert.Equal(t, len(r.Resources), 1)
assert.Equal(t, r.Resources[siCommon.Memory].Value, int64(1))
r1 = NewResourceBuilder().
AddResource(siCommon.Memory, 1024).
AddResource(siCommon.CPU, 20).
AddResource("nvidia.com/gpu", 2).
Build()
r2 = NewResourceBuilder().
AddResource(siCommon.Memory, 2048).
AddResource(siCommon.CPU, 30).
AddResource("nvidia.com/gpu", 3).
Build()
r = Add(r1, r2)
assert.Equal(t, len(r.Resources), 3)
assert.Equal(t, r.Resources[siCommon.Memory].Value, int64(3072))
assert.Equal(t, r.Resources[siCommon.CPU].Value, int64(50))
assert.Equal(t, r.Resources["nvidia.com/gpu"].Value, int64(5))
}
func TestEquals(t *testing.T) {
r1 := NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
r2 := NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
assert.Equal(t, Equals(r1, r2), true)
r1 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
r2 = NewResourceBuilder().
AddResource(siCommon.Memory, 2).
AddResource(siCommon.CPU, 1).
Build()
assert.Equal(t, Equals(r1, r2), false)
r1 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
Build()
r2 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
assert.Equal(t, Equals(r1, r2), false)
r1 = nil
r2 = nil
assert.Equal(t, Equals(r1, r2), true)
r1 = nil
r2 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
assert.Equal(t, Equals(r1, r2), false)
r1 = NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
r2 = nil
assert.Equal(t, Equals(r1, r2), false)
}
func TestParsePodResource(t *testing.T) {
containers := make([]v1.Container, 0)
// container 01
c1Resources := make(map[v1.ResourceName]resource.Quantity)
c1Resources[v1.ResourceMemory] = resource.MustParse("500M")
c1Resources[v1.ResourceCPU] = resource.MustParse("1")
c1Resources[v1.ResourceName("nvidia.com/gpu")] = resource.MustParse("1")
containers = append(containers, v1.Container{
Name: "container-01",
Resources: v1.ResourceRequirements{
Requests: c1Resources,
},
})
// container 02
c2Resources := make(map[v1.ResourceName]resource.Quantity)
c2Resources[v1.ResourceMemory] = resource.MustParse("1024M")
c2Resources[v1.ResourceCPU] = resource.MustParse("2")
c2Resources[v1.ResourceName("nvidia.com/gpu")] = resource.MustParse("4")
containers = append(containers, v1.Container{
Name: "container-02",
Resources: v1.ResourceRequirements{
Requests: c2Resources,
},
})
// pod
pod := &v1.Pod{
TypeMeta: apis.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: apis.ObjectMeta{
Name: "pod-resource-test-00001",
UID: "UID-00001",
},
Spec: v1.PodSpec{
Containers: containers,
},
}
// verify we get aggregated resource from containers
res := GetPodResource(pod)
assert.Equal(t, res.Resources[siCommon.Memory].GetValue(), int64(1524*1000*1000))
assert.Equal(t, res.Resources[siCommon.CPU].GetValue(), int64(3000))
assert.Equal(t, res.Resources["nvidia.com/gpu"].GetValue(), int64(5))
// test initcontainer and container resouce compare
initContainers := make([]v1.Container, 0)
initc1Resources := make(map[v1.ResourceName]resource.Quantity)
initc1Resources[v1.ResourceMemory] = resource.MustParse("4096M")
initc1Resources[v1.ResourceCPU] = resource.MustParse("0.5")
initc1Resources[v1.ResourceName("nvidia.com/gpu")] = resource.MustParse("1")
initContainers = append(initContainers, v1.Container{
Name: "initcontainer-01",
Resources: v1.ResourceRequirements{
Requests: initc1Resources,
},
})
initc2Resources := make(map[v1.ResourceName]resource.Quantity)
initc2Resources[v1.ResourceMemory] = resource.MustParse("10000M")
initc2Resources[v1.ResourceCPU] = resource.MustParse("5.12")
initc2Resources[v1.ResourceName("nvidia.com/gpu")] = resource.MustParse("4")
initContainers = append(initContainers, v1.Container{
Name: "initcontainer-02",
Resources: v1.ResourceRequirements{
Requests: initc2Resources,
},
})
containers[0].Resources.Requests[v1.ResourceMemory] = resource.MustParse("2000M")
containers[0].Resources.Requests[v1.ResourceCPU] = resource.MustParse("4.096")
containers[0].Resources.Requests[v1.ResourceName("nvidia.com/gpu")] = resource.MustParse("2")
containers[1].Resources.Requests[v1.ResourceMemory] = resource.MustParse("5000M")
containers[1].Resources.Requests[v1.ResourceCPU] = resource.MustParse("1.024")
containers[1].Resources.Requests[v1.ResourceName("nvidia.com/gpu")] = resource.MustParse("2")
pod.ObjectMeta = apis.ObjectMeta{
Name: "pod-resource-test-00002",
UID: "UID-00002",
}
pod.Spec = v1.PodSpec{
Containers: containers,
InitContainers: initContainers,
}
// initcontainers
// IC1{500mi, 1000m, 1}
// IC2{5120mi, 10000m, 4}
// sum of containers{5120mi, 7000m, 4}
// C1{4096mi, 2000m, 2}
// C2{1024mi, 5000m, 2}
// result {5120mi, 10000m, 4}
res = GetPodResource(pod)
assert.Equal(t, res.Resources[siCommon.Memory].GetValue(), int64(10000000000))
assert.Equal(t, res.Resources[siCommon.CPU].GetValue(), int64(5120))
assert.Equal(t, res.Resources["nvidia.com/gpu"].GetValue(), int64(4))
delete(containers[0].Resources.Requests, v1.ResourceName("nvidia.com/gpu"))
delete(containers[1].Resources.Requests, v1.ResourceName("nvidia.com/gpu"))
delete(initContainers[1].Resources.Requests, v1.ResourceCPU)
delete(initContainers[1].Resources.Requests, v1.ResourceName("nvidia.com/gpu"))
pod.Spec = v1.PodSpec{
Containers: containers,
InitContainers: initContainers,
}
// IC1{500mi, 1000m, 1}
// IC2{0mi, 10000m}
// sum of containers{5120mi, 7000m}
// result {5120mi, 10000m, 1}
res = GetPodResource(pod)
assert.Equal(t, res.Resources[siCommon.Memory].GetValue(), int64(10000000000))
assert.Equal(t, res.Resources[siCommon.CPU].GetValue(), int64(5120))
assert.Equal(t, res.Resources["nvidia.com/gpu"].GetValue(), int64(1))
}
func TestBestEffortPod(t *testing.T) {
resources := make(map[v1.ResourceName]resource.Quantity)
containers := make([]v1.Container, 0)
containers = append(containers, v1.Container{
Name: "container-01",
Resources: v1.ResourceRequirements{
Requests: resources,
},
})
// pod, no resources requested
pod := &v1.Pod{
TypeMeta: apis.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: apis.ObjectMeta{
Name: "pod-resource-test-00001",
UID: "UID-00001",
},
Spec: v1.PodSpec{
Containers: containers,
},
}
// best effort pod all resources are nil or zero
res := GetPodResource(pod)
assert.Equal(t, len(res.Resources), 1)
assert.Equal(t, res.Resources[siCommon.Memory].GetValue(), int64(1000000))
// Add a resource to existing container (other than mem)
resources[v1.ResourceCPU] = resource.MustParse("1")
res = GetPodResource(pod)
assert.Equal(t, len(res.Resources), 1)
assert.Equal(t, res.Resources[siCommon.CPU].GetValue(), int64(1000))
// reset the cpu request to zero and add memory
resources[v1.ResourceMemory] = resource.MustParse("0")
resources[v1.ResourceCPU] = resource.MustParse("0")
res = GetPodResource(pod)
assert.Equal(t, len(res.Resources), 1)
assert.Equal(t, res.Resources[siCommon.Memory].GetValue(), int64(1000000))
}
func TestNodeResource(t *testing.T) {
nodeCapacity := make(map[v1.ResourceName]resource.Quantity)
nodeCapacity[v1.ResourceCPU] = resource.MustParse("14500m")
result := GetNodeResource(&v1.NodeStatus{
Allocatable: nodeCapacity,
})
assert.Equal(t, result.Resources[siCommon.CPU].GetValue(), int64(14500))
}
func TestIsZero(t *testing.T) {
r := NewResourceBuilder().
AddResource(siCommon.Memory, 1).
AddResource(siCommon.CPU, 1).
Build()
assert.Equal(t, IsZero(r), false)
r = NewResourceBuilder().
AddResource(siCommon.CPU, 0).
Build()
assert.Equal(t, IsZero(r), true)
r = NewResourceBuilder().
AddResource(siCommon.Memory, 0).
AddResource(siCommon.CPU, 0).
Build()
assert.Equal(t, IsZero(r), true)
r = NewResourceBuilder().
AddResource(siCommon.Memory, 0).
AddResource(siCommon.CPU, 1).
Build()
assert.Equal(t, IsZero(r), false)
assert.Equal(t, IsZero(nil), true)
r = &si.Resource{}
assert.Equal(t, IsZero(r), true)
}
func TestSub(t *testing.T) {
// simple case (nil checks)
result := Sub(nil, nil)
if result == nil || len(result.Resources) != 0 {
t.Errorf("sub nil resources did not return zero resource: %v", result)
}
// empty resources
left := NewResourceBuilder().Build()
result = Sub(left, nil)
if result == nil || len(result.Resources) != 0 || result != left {
t.Errorf("sub Zero resource (right) did not return cloned resource: %v", result)
}
// simple empty resources
res1 := NewResourceBuilder().
AddResource("a", 5).
Build()
result = Sub(left, res1)
expected := NewResourceBuilder().
AddResource("a", -5).
Build()
if !Equals(result, expected) {
t.Errorf("sub failed expected %v, actual %v", expected, result.Resources)
}
// complex case: just checking the resource merge, values check is secondary
res1 = NewResourceBuilder().
AddResource("a", 0).
AddResource("b", 1).
Build()
res2 := NewResourceBuilder().
AddResource("a", 1).
AddResource("c", 0).
AddResource("d", -1).
Build()
res3 := Sub(res1, res2)
expected = NewResourceBuilder().
AddResource("a", -1).
AddResource("b", 1).
AddResource("c", 0).
AddResource("d", 1).
Build()
if !Equals(res3, expected) {
t.Errorf("sub failed expected %v, actual %v", expected, res3.Resources)
}
}
func TestParseResourceString(t *testing.T) {
testCases := []struct {
cpu string
mem string
cpuExist bool
memoryExist bool
expectCPU int64
expectMemory int64
}{
// empty values
{"", "", false, false, 0, 0},
// cpu values
{"1", "", true, false, 1000, 0},
{"0.5", "", true, false, 500, 0},
{"0.33", "", true, false, 330, 0},
{"1000", "", true, false, 1000000, 0},
{"-10", "", true, false, -10000, 0},
{"100m", "", true, false, 100, 0},
// memory values
{"", "65536", false, true, 0, 65536},
{"", "129M", false, true, 0, 129 * 1000 * 1000},
{"", "123Mi", false, true, 0, 123 * 1024 * 1024},
{"", "128974848", false, true, 0, 128974848},
{"", "1G", false, true, 0, 1000 * 1000 * 1000},
{"", "1Gi", false, true, 0, 1024 * 1024 * 1024},
{"", "1T", false, true, 0, 1000 * 1000 * 1000 * 1000},
{"", "1P", false, true, 0, 1000 * 1000 * 1000 * 1000 * 1000},
{"", "1E", false, true, 0, 1000 * 1000 * 1000 * 1000 * 1000 * 1000},
// cpu and memory
{"0.5", "64M", true, true, 500, 64 * 1000 * 1000},
// parsing error on cpu
{"xyz", "64M", false, false, 0, 0},
// parsing error on memory
{"100m", "64MiB", false, false, 0, 0},
// parsing failed for both cpu and memory
{"xyz", "64MiB", false, false, 0, 0},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("cpu: %s, memory: %s", tc.cpu, tc.mem), func(t *testing.T) {
siResource := ParseResource(tc.cpu, tc.mem)
cpuRes, hasCPU := siResource.GetResources()[siCommon.CPU]
assert.Equal(t, hasCPU, tc.cpuExist)
assert.Equal(t, cpuRes.GetValue(), tc.expectCPU)
memRes, hasMem := siResource.GetResources()[siCommon.Memory]
assert.Equal(t, hasMem, tc.memoryExist)
assert.Equal(t, memRes.GetValue(), tc.expectMemory)
})
}
}