Commit 58457daf authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #31652 from intelsdi-x/poc-opaque-int-resources

Automatic merge from submit-queue [PHASE 1] Opaque integer resource accounting. ## [PHASE 1] Opaque integer resource accounting. This change provides a simple way to advertise some amount of arbitrary countable resource for a node in a Kubernetes cluster. Users can consume these resources by including them in pod specs, and the scheduler takes them into account when placing pods on nodes. See the example at the bottom of the PR description for more info. Summary of changes: - Defines opaque integer resources as any resource with prefix `pod.alpha.kubernetes.io/opaque-int-resource-`. - Prevent kubelet from overwriting capacity. - Handle opaque resources in scheduler. - Validate integer-ness of opaque int quantities in API server. - Tests for above. Feature issue: https://github.com/kubernetes/features/issues/76 Design: http://goo.gl/IoKYP1 Issues: kubernetes/kubernetes#28312 kubernetes/kubernetes#19082 Related: kubernetes/kubernetes#19080 CC @davidopp @timothysc @balajismaniam **Release note**: <!-- Steps to write your release note: 1. Use the release-note-* labels to set the release note state (if you have access) 2. Enter your extended release note in the below block; leaving it blank means using the PR title as the release note. If no release note is required, just write `NONE`. --> ```release-note Added support for accounting opaque integer resources. Allows cluster operators to advertise new node-level resources that would be otherwise unknown to Kubernetes. Users can consume these resources in pod specs just like CPU and memory. The scheduler takes care of the resource accounting so that no more than the available amount is simultaneously allocated to pods. ``` ## Usage example ```sh $ echo '[{"op": "add", "path": "pod.alpha.kubernetes.io~1opaque-int-resource-bananas", "value": "555"}]' | \ > http PATCH http://localhost:8080/api/v1/nodes/localhost.localdomain/status \ > Content-Type:application/json-patch+json ``` ```http HTTP/1.1 200 OK Content-Type: application/json Date: Thu, 11 Aug 2016 16:44:55 GMT Transfer-Encoding: chunked { "apiVersion": "v1", "kind": "Node", "metadata": { "annotations": { "volumes.kubernetes.io/controller-managed-attach-detach": "true" }, "creationTimestamp": "2016-07-12T04:07:43Z", "labels": { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/os": "linux", "kubernetes.io/hostname": "localhost.localdomain" }, "name": "localhost.localdomain", "resourceVersion": "12837", "selfLink": "/api/v1/nodes/localhost.localdomain/status", "uid": "2ee9ea1c-47e6-11e6-9fb4-525400659b2e" }, "spec": { "externalID": "localhost.localdomain" }, "status": { "addresses": [ { "address": "10.0.2.15", "type": "LegacyHostIP" }, { "address": "10.0.2.15", "type": "InternalIP" } ], "allocatable": { "alpha.kubernetes.io/nvidia-gpu": "0", "cpu": "2", "memory": "8175808Ki", "pods": "110" }, "capacity": { "alpha.kubernetes.io/nvidia-gpu": "0", "pod.alpha.kubernetes.io/opaque-int-resource-bananas": "555", "cpu": "2", "memory": "8175808Ki", "pods": "110" }, "conditions": [ { "lastHeartbeatTime": "2016-08-11T16:44:47Z", "lastTransitionTime": "2016-07-12T04:07:43Z", "message": "kubelet has sufficient disk space available", "reason": "KubeletHasSufficientDisk", "status": "False", "type": "OutOfDisk" }, { "lastHeartbeatTime": "2016-08-11T16:44:47Z", "lastTransitionTime": "2016-07-12T04:07:43Z", "message": "kubelet has sufficient memory available", "reason": "KubeletHasSufficientMemory", "status": "False", "type": "MemoryPressure" }, { "lastHeartbeatTime": "2016-08-11T16:44:47Z", "lastTransitionTime": "2016-08-10T06:27:11Z", "message": "kubelet is posting ready status", "reason": "KubeletReady", "status": "True", "type": "Ready" }, { "lastHeartbeatTime": "2016-08-11T16:44:47Z", "lastTransitionTime": "2016-08-10T06:27:01Z", "message": "kubelet has no disk pressure", "reason": "KubeletHasNoDiskPressure", "status": "False", "type": "DiskPressure" } ], "daemonEndpoints": { "kubeletEndpoint": { "Port": 10250 } }, "images": [], "nodeInfo": { "architecture": "amd64", "bootID": "1f7e95ca-a4c2-490e-8ca2-6621ae1eb5f0", "containerRuntimeVersion": "docker://1.10.3", "kernelVersion": "4.5.7-202.fc23.x86_64", "kubeProxyVersion": "v1.3.0-alpha.4.4285+7e4b86c96110d3-dirty", "kubeletVersion": "v1.3.0-alpha.4.4285+7e4b86c96110d3-dirty", "machineID": "cac4063395254bc89d06af5d05322453", "operatingSystem": "linux", "osImage": "Fedora 23 (Cloud Edition)", "systemUUID": "D6EE0782-5DEB-4465-B35D-E54190C5EE96" } } } ``` After patching, the kubelet's next sync fills in allocatable: ``` $ kubectl get node localhost.localdomain -o json | jq .status.allocatable ``` ```json { "alpha.kubernetes.io/nvidia-gpu": "0", "pod.alpha.kubernetes.io/opaque-int-resource-bananas": "555", "cpu": "2", "memory": "8175808Ki", "pods": "110" } ``` Create two pods, one that needs a single banana and another that needs a truck load: ``` $ kubectl create -f chimp.yaml $ kubectl create -f superchimp.yaml ``` Inspect the scheduler result and pod status: ``` $ kubectl describe pods chimp Name: chimp Namespace: default Node: localhost.localdomain/10.0.2.15 Start Time: Thu, 11 Aug 2016 19:58:46 +0000 Labels: <none> Status: Running IP: 172.17.0.2 Controllers: <none> Containers: nginx: Container ID: docker://46ff268f2f9217c59cc49f97cc4f0f085d5ac0e251f508cc08938601117c0cec Image: nginx:1.10 Image ID: docker://sha256:82e97a2b0390a20107ab1310dea17f539ff6034438099384998fd91fc540b128 Port: 80/TCP Limits: cpu: 500m memory: 64Mi pod.alpha.kubernetes.io/opaque-int-resource-bananas: 3 Requests: cpu: 250m memory: 32Mi pod.alpha.kubernetes.io/opaque-int-resource-bananas: 1 State: Running Started: Thu, 11 Aug 2016 19:58:51 +0000 Ready: True Restart Count: 0 Volume Mounts: <none> Environment Variables: <none> Conditions: Type Status Initialized True Ready True PodScheduled True No volumes. QoS Class: Burstable Events: FirstSeen LastSeen Count From SubobjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 9m 9m 1 {default-scheduler } Normal Scheduled Successfully assigned chimp to localhost.localdomain 9m 9m 2 {kubelet localhost.localdomain} Warning MissingClusterDNS kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to DNSDefault policy. 9m 9m 1 {kubelet localhost.localdomain} spec.containers{nginx} Normal Pulled Container image "nginx:1.10" already present on machine 9m 9m 1 {kubelet localhost.localdomain} spec.containers{nginx} Normal Created Created container with docker id 46ff268f2f92 9m 9m 1 {kubelet localhost.localdomain} spec.containers{nginx} Normal Started Started container with docker id 46ff268f2f92 ``` ``` $ kubectl describe pods superchimp Name: superchimp Namespace: default Node: / Labels: <none> Status: Pending IP: Controllers: <none> Containers: nginx: Image: nginx:1.10 Port: 80/TCP Requests: cpu: 250m memory: 32Mi pod.alpha.kubernetes.io/opaque-int-resource-bananas: 10Ki Volume Mounts: <none> Environment Variables: <none> Conditions: Type Status PodScheduled False No volumes. QoS Class: Burstable Events: FirstSeen LastSeen Count From SubobjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 3m 1s 15 {default-scheduler } Warning FailedScheduling pod (superchimp) failed to fit in any node fit failure on node (localhost.localdomain): Insufficient pod.alpha.kubernetes.io/opaque-int-resource-bananas ```
parents 2efdd9ed b0421c1b
......@@ -120,6 +120,22 @@ func IsStandardContainerResourceName(str string) bool {
return standardContainerResources.Has(str)
}
// IsOpaqueIntResourceName returns true if the resource name has the opaque
// integer resource prefix.
func IsOpaqueIntResourceName(name ResourceName) bool {
return strings.HasPrefix(string(name), ResourceOpaqueIntPrefix)
}
// OpaqueIntResourceName returns a ResourceName with the canonical opaque
// integer prefix prepended. If the argument already has the prefix, it is
// returned unmodified.
func OpaqueIntResourceName(name string) ResourceName {
if IsOpaqueIntResourceName(ResourceName(name)) {
return ResourceName(name)
}
return ResourceName(fmt.Sprintf("%s%s", ResourceOpaqueIntPrefix, name))
}
var standardLimitRangeTypes = sets.NewString(
string(LimitTypePod),
string(LimitTypeContainer),
......@@ -193,7 +209,7 @@ var integerResources = sets.NewString(
// IsIntegerResourceName returns true if the resource is measured in integer values
func IsIntegerResourceName(str string) bool {
return integerResources.Has(str)
return integerResources.Has(str) || IsOpaqueIntResourceName(ResourceName(str))
}
// NewDeleteOptions returns a DeleteOptions indicating the resource should
......
......@@ -2631,6 +2631,11 @@ const (
// Number of Pods that may be running on this Node: see ResourcePods
)
const (
// Namespace prefix for opaque counted resources (alpha).
ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-"
)
// ResourceList is a set of (resource name, quantity) pairs.
type ResourceList map[ResourceName]resource.Quantity
......
......@@ -2845,6 +2845,17 @@ func ValidateNodeUpdate(node, oldNode *api.Node) field.ErrorList {
// allErrs = append(allErrs, field.Invalid("status", node.Status, "must be empty"))
// }
// Validate resource quantities in capacity.
for k, v := range node.Status.Capacity {
resPath := field.NewPath("status", "capacity", string(k))
allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...)
}
// Validate resource quantities in allocatable.
for k, v := range node.Status.Allocatable {
resPath := field.NewPath("status", "allocatable", string(k))
allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...)
}
// Validte no duplicate addresses in node status.
addresses := make(map[api.NodeAddress]bool)
for i, address := range node.Status.Addresses {
......@@ -3236,9 +3247,10 @@ func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPat
fldPath := limPath.Key(string(resourceName))
// Validate resource name.
allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...)
if api.IsStandardResourceName(string(resourceName)) {
allErrs = append(allErrs, validateBasicResource(quantity, fldPath.Key(string(resourceName)))...)
}
// Validate resource quantity.
allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
// Check that request <= limit.
requestQuantity, exists := requirements.Requests[resourceName]
if exists {
......@@ -3254,10 +3266,10 @@ func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPat
fldPath := reqPath.Key(string(resourceName))
// Validate resource name.
allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...)
if api.IsStandardResourceName(string(resourceName)) {
allErrs = append(allErrs, validateBasicResource(quantity, fldPath.Key(string(resourceName)))...)
}
// Validate resource quantity.
allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
}
return allErrs
}
......
......@@ -3665,6 +3665,52 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec,
},
{ // valid opaque integer resources for init container
ObjectMeta: api.ObjectMeta{Name: "valid-opaque-int", Namespace: "ns"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Name: "valid-opaque-int",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("10"),
},
Limits: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("20"),
},
},
},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
{ // valid opaque integer resources for regular container
ObjectMeta: api.ObjectMeta{Name: "valid-opaque-int", Namespace: "ns"},
Spec: api.PodSpec{
InitContainers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{
{
Name: "valid-opaque-int",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("10"),
},
Limits: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("20"),
},
},
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
}
for _, pod := range successCases {
if errs := ValidatePod(&pod); len(errs) != 0 {
......@@ -4155,6 +4201,112 @@ func TestValidatePod(t *testing.T) {
},
Spec: validPodSpec,
},
"invalid opaque integer resource requirement: request must be <= limit": {
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "invalid",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("2"),
},
Limits: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("1"),
},
},
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
"invalid fractional opaque integer resource in container request": {
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "invalid",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("500m"),
},
},
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
"invalid fractional opaque integer resource in init container request": {
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Name: "invalid",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("500m"),
},
},
},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
"invalid fractional opaque integer resource in container limit": {
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "invalid",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("5"),
},
Limits: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("2.5"),
},
},
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
"invalid fractional opaque integer resource in init container limit": {
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Name: "invalid",
Image: "image",
ImagePullPolicy: "IfNotPresent",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("5"),
},
Limits: api.ResourceList{
api.OpaqueIntResourceName("A"): resource.MustParse("2.5"),
},
},
},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
}
for k, v := range errorCases {
if errs := ValidatePod(&v); len(errs) == 0 {
......@@ -6347,6 +6499,60 @@ func TestValidateNodeUpdate(t *testing.T) {
},
},
}, false},
{api.Node{
ObjectMeta: api.ObjectMeta{
Name: "valid-opaque-int-resources",
},
}, api.Node{
ObjectMeta: api.ObjectMeta{
Name: "valid-opaque-int-resources",
},
Status: api.NodeStatus{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
api.OpaqueIntResourceName("A"): resource.MustParse("5"),
api.OpaqueIntResourceName("B"): resource.MustParse("10"),
},
},
}, true},
{api.Node{
ObjectMeta: api.ObjectMeta{
Name: "invalid-fractional-opaque-int-capacity",
},
}, api.Node{
ObjectMeta: api.ObjectMeta{
Name: "invalid-fractional-opaque-int-capacity",
},
Status: api.NodeStatus{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
api.OpaqueIntResourceName("A"): resource.MustParse("500m"),
},
},
}, false},
{api.Node{
ObjectMeta: api.ObjectMeta{
Name: "invalid-fractional-opaque-int-allocatable",
},
}, api.Node{
ObjectMeta: api.ObjectMeta{
Name: "invalid-fractional-opaque-int-allocatable",
},
Status: api.NodeStatus{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
api.OpaqueIntResourceName("A"): resource.MustParse("5"),
},
Allocatable: api.ResourceList{
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
api.OpaqueIntResourceName("A"): resource.MustParse("4.5"),
},
},
}, false},
}
for i, test := range tests {
test.oldNode.ObjectMeta.ResourceVersion = "1"
......
......@@ -436,23 +436,32 @@ func (kl *Kubelet) setNodeAddress(node *api.Node) error {
}
func (kl *Kubelet) setNodeStatusMachineInfo(node *api.Node) {
// Note: avoid blindly overwriting the capacity in case opaque
// resources are being advertised.
if node.Status.Capacity == nil {
node.Status.Capacity = api.ResourceList{}
}
// TODO: Post NotReady if we cannot get MachineInfo from cAdvisor. This needs to start
// cAdvisor locally, e.g. for test-cmd.sh, and in integration test.
info, err := kl.GetCachedMachineInfo()
if err != nil {
// TODO(roberthbailey): This is required for test-cmd.sh to pass.
// See if the test should be updated instead.
node.Status.Capacity = api.ResourceList{
api.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI),
api.ResourceMemory: resource.MustParse("0Gi"),
api.ResourcePods: *resource.NewQuantity(int64(kl.maxPods), resource.DecimalSI),
api.ResourceNvidiaGPU: *resource.NewQuantity(int64(kl.nvidiaGPUs), resource.DecimalSI),
}
node.Status.Capacity[api.ResourceCPU] = *resource.NewMilliQuantity(0, resource.DecimalSI)
node.Status.Capacity[api.ResourceMemory] = resource.MustParse("0Gi")
node.Status.Capacity[api.ResourcePods] = *resource.NewQuantity(int64(kl.maxPods), resource.DecimalSI)
node.Status.Capacity[api.ResourceNvidiaGPU] = *resource.NewQuantity(int64(kl.nvidiaGPUs), resource.DecimalSI)
glog.Errorf("Error getting machine info: %v", err)
} else {
node.Status.NodeInfo.MachineID = info.MachineID
node.Status.NodeInfo.SystemUUID = info.SystemUUID
node.Status.Capacity = cadvisor.CapacityFromMachineInfo(info)
for rName, rCap := range cadvisor.CapacityFromMachineInfo(info) {
node.Status.Capacity[rName] = rCap
}
if kl.podsPerCore > 0 {
node.Status.Capacity[api.ResourcePods] = *resource.NewQuantity(
int64(math.Min(float64(info.NumCores*kl.podsPerCore), float64(kl.maxPods))), resource.DecimalSI)
......
......@@ -430,19 +430,53 @@ func (c *VolumeZoneChecker) predicate(pod *api.Pod, meta interface{}, nodeInfo *
func GetResourceRequest(pod *api.Pod) *schedulercache.Resource {
result := schedulercache.Resource{}
for _, container := range pod.Spec.Containers {
requests := container.Resources.Requests
result.Memory += requests.Memory().Value()
result.MilliCPU += requests.Cpu().MilliValue()
result.NvidiaGPU += requests.NvidiaGPU().Value()
for rName, rQuantity := range container.Resources.Requests {
switch rName {
case api.ResourceMemory:
result.Memory += rQuantity.Value()
case api.ResourceCPU:
result.MilliCPU += rQuantity.MilliValue()
case api.ResourceNvidiaGPU:
result.NvidiaGPU += rQuantity.Value()
default:
if api.IsOpaqueIntResourceName(rName) {
// Lazily allocate this map only if required.
if result.OpaqueIntResources == nil {
result.OpaqueIntResources = map[api.ResourceName]int64{}
}
result.OpaqueIntResources[rName] += rQuantity.Value()
}
}
}
}
// take max_resource(sum_pod, any_init_container)
for _, container := range pod.Spec.InitContainers {
requests := container.Resources.Requests
if mem := requests.Memory().Value(); mem > result.Memory {
result.Memory = mem
}
if cpu := requests.Cpu().MilliValue(); cpu > result.MilliCPU {
result.MilliCPU = cpu
for rName, rQuantity := range container.Resources.Requests {
switch rName {
case api.ResourceMemory:
if mem := rQuantity.Value(); mem > result.Memory {
result.Memory = mem
}
case api.ResourceCPU:
if cpu := rQuantity.MilliValue(); cpu > result.MilliCPU {
result.MilliCPU = cpu
}
case api.ResourceNvidiaGPU:
if gpu := rQuantity.Value(); gpu > result.NvidiaGPU {
result.NvidiaGPU = gpu
}
default:
if api.IsOpaqueIntResourceName(rName) {
// Lazily allocate this map only if required.
if result.OpaqueIntResources == nil {
result.OpaqueIntResources = map[api.ResourceName]int64{}
}
value := rQuantity.Value()
if value > result.OpaqueIntResources[rName] {
result.OpaqueIntResources[rName] = value
}
}
}
}
}
return &result
......@@ -471,7 +505,7 @@ func PodFitsResources(pod *api.Pod, meta interface{}, nodeInfo *schedulercache.N
// We couldn't parse metadata - fallback to computing it.
podRequest = GetResourceRequest(pod)
}
if podRequest.MilliCPU == 0 && podRequest.Memory == 0 && podRequest.NvidiaGPU == 0 {
if podRequest.MilliCPU == 0 && podRequest.Memory == 0 && podRequest.NvidiaGPU == 0 && len(podRequest.OpaqueIntResources) == 0 {
return len(predicateFails) == 0, predicateFails, nil
}
......@@ -485,6 +519,12 @@ func PodFitsResources(pod *api.Pod, meta interface{}, nodeInfo *schedulercache.N
if allocatable.NvidiaGPU < podRequest.NvidiaGPU+nodeInfo.RequestedResource().NvidiaGPU {
predicateFails = append(predicateFails, NewInsufficientResourceError(api.ResourceNvidiaGPU, podRequest.NvidiaGPU, nodeInfo.RequestedResource().NvidiaGPU, allocatable.NvidiaGPU))
}
for rName, rQuant := range podRequest.OpaqueIntResources {
if allocatable.OpaqueIntResources[rName] < rQuant+nodeInfo.RequestedResource().OpaqueIntResources[rName] {
predicateFails = append(predicateFails, NewInsufficientResourceError(rName, podRequest.OpaqueIntResources[rName], nodeInfo.RequestedResource().OpaqueIntResources[rName], allocatable.OpaqueIntResources[rName]))
}
}
if glog.V(10) {
// We explicitly don't do glog.V(10).Infof() to avoid computing all the parameters if this is
// not logged. There is visible performance gain from it.
......
......@@ -21,6 +21,7 @@ go_library(
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/client/cache:go_default_library",
"//pkg/labels:go_default_library",
"//pkg/util/wait:go_default_library",
......
......@@ -22,6 +22,7 @@ import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
clientcache "k8s.io/kubernetes/pkg/client/cache"
priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util"
)
......@@ -55,9 +56,22 @@ type NodeInfo struct {
// Resource is a collection of compute resource.
type Resource struct {
MilliCPU int64
Memory int64
NvidiaGPU int64
MilliCPU int64
Memory int64
NvidiaGPU int64
OpaqueIntResources map[api.ResourceName]int64
}
func (r *Resource) ResourceList() api.ResourceList {
result := api.ResourceList{
api.ResourceCPU: *resource.NewMilliQuantity(r.MilliCPU, resource.DecimalSI),
api.ResourceMemory: *resource.NewQuantity(r.Memory, resource.BinarySI),
api.ResourceNvidiaGPU: *resource.NewQuantity(r.NvidiaGPU, resource.DecimalSI),
}
for rName, rQuant := range r.OpaqueIntResources {
result[rName] = *resource.NewQuantity(rQuant, resource.DecimalSI)
}
return result
}
// NewNodeInfo returns a ready to use empty NodeInfo object.
......@@ -169,10 +183,17 @@ func hasPodAffinityConstraints(pod *api.Pod) bool {
// addPod adds pod information to this NodeInfo.
func (n *NodeInfo) addPod(pod *api.Pod) {
cpu, mem, nvidia_gpu, non0_cpu, non0_mem := calculateResource(pod)
n.requestedResource.MilliCPU += cpu
n.requestedResource.Memory += mem
n.requestedResource.NvidiaGPU += nvidia_gpu
// cpu, mem, nvidia_gpu, non0_cpu, non0_mem := calculateResource(pod)
res, non0_cpu, non0_mem := calculateResource(pod)
n.requestedResource.MilliCPU += res.MilliCPU
n.requestedResource.Memory += res.Memory
n.requestedResource.NvidiaGPU += res.NvidiaGPU
if n.requestedResource.OpaqueIntResources == nil && len(res.OpaqueIntResources) > 0 {
n.requestedResource.OpaqueIntResources = map[api.ResourceName]int64{}
}
for rName, rQuant := range res.OpaqueIntResources {
n.requestedResource.OpaqueIntResources[rName] += rQuant
}
n.nonzeroRequest.MilliCPU += non0_cpu
n.nonzeroRequest.Memory += non0_mem
n.pods = append(n.pods, pod)
......@@ -213,10 +234,17 @@ func (n *NodeInfo) removePod(pod *api.Pod) error {
n.pods[i] = n.pods[len(n.pods)-1]
n.pods = n.pods[:len(n.pods)-1]
// reduce the resource data
cpu, mem, nvidia_gpu, non0_cpu, non0_mem := calculateResource(pod)
n.requestedResource.MilliCPU -= cpu
n.requestedResource.Memory -= mem
n.requestedResource.NvidiaGPU -= nvidia_gpu
res, non0_cpu, non0_mem := calculateResource(pod)
n.requestedResource.MilliCPU -= res.MilliCPU
n.requestedResource.Memory -= res.Memory
n.requestedResource.NvidiaGPU -= res.NvidiaGPU
if len(res.OpaqueIntResources) > 0 && n.requestedResource.OpaqueIntResources == nil {
n.requestedResource.OpaqueIntResources = map[api.ResourceName]int64{}
}
for rName, rQuant := range res.OpaqueIntResources {
n.requestedResource.OpaqueIntResources[rName] -= rQuant
}
n.nonzeroRequest.MilliCPU -= non0_cpu
n.nonzeroRequest.Memory -= non0_mem
n.generation++
......@@ -226,17 +254,31 @@ func (n *NodeInfo) removePod(pod *api.Pod) error {
return fmt.Errorf("no corresponding pod %s in pods of node %s", pod.Name, n.node.Name)
}
func calculateResource(pod *api.Pod) (cpu int64, mem int64, nvidia_gpu int64, non0_cpu int64, non0_mem int64) {
func calculateResource(pod *api.Pod) (res Resource, non0_cpu int64, non0_mem int64) {
for _, c := range pod.Spec.Containers {
req := c.Resources.Requests
cpu += req.Cpu().MilliValue()
mem += req.Memory().Value()
nvidia_gpu += req.NvidiaGPU().Value()
for rName, rQuant := range c.Resources.Requests {
switch rName {
case api.ResourceCPU:
res.MilliCPU += rQuant.MilliValue()
case api.ResourceMemory:
res.Memory += rQuant.Value()
case api.ResourceNvidiaGPU:
res.NvidiaGPU += rQuant.Value()
default:
if api.IsOpaqueIntResourceName(rName) {
// Lazily allocate opaque resource map.
if res.OpaqueIntResources == nil {
res.OpaqueIntResources = map[api.ResourceName]int64{}
}
res.OpaqueIntResources[rName] += rQuant.Value()
}
}
}
non0_cpu_req, non0_mem_req := priorityutil.GetNonzeroRequests(&req)
non0_cpu_req, non0_mem_req := priorityutil.GetNonzeroRequests(&c.Resources.Requests)
non0_cpu += non0_cpu_req
non0_mem += non0_mem_req
// No non-zero resources for GPUs
// No non-zero resources for GPUs or opaque resources.
}
return
}
......@@ -244,10 +286,26 @@ func calculateResource(pod *api.Pod) (cpu int64, mem int64, nvidia_gpu int64, no
// Sets the overall node information.
func (n *NodeInfo) SetNode(node *api.Node) error {
n.node = node
n.allocatableResource.MilliCPU = node.Status.Allocatable.Cpu().MilliValue()
n.allocatableResource.Memory = node.Status.Allocatable.Memory().Value()
n.allocatableResource.NvidiaGPU = node.Status.Allocatable.NvidiaGPU().Value()
n.allowedPodNumber = int(node.Status.Allocatable.Pods().Value())
for rName, rQuant := range node.Status.Allocatable {
switch rName {
case api.ResourceCPU:
n.allocatableResource.MilliCPU = rQuant.MilliValue()
case api.ResourceMemory:
n.allocatableResource.Memory = rQuant.Value()
case api.ResourceNvidiaGPU:
n.allocatableResource.NvidiaGPU = rQuant.Value()
case api.ResourcePods:
n.allowedPodNumber = int(rQuant.Value())
default:
if api.IsOpaqueIntResourceName(rName) {
// Lazily allocate opaque resource map.
if n.allocatableResource.OpaqueIntResources == nil {
n.allocatableResource.OpaqueIntResources = map[api.ResourceName]int64{}
}
n.allocatableResource.OpaqueIntResources[rName] = rQuant.Value()
}
}
}
n.generation++
return nil
}
......
......@@ -72,6 +72,7 @@ go_library(
"networking_perf.go",
"node_problem_detector.go",
"nodeoutofdisk.go",
"opaque_resource.go",
"pd.go",
"persistent_volumes.go",
"petset.go",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment