mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
Merge pull request #48922 from ConnorDoyle/integer-resources-as-default
Automatic merge from submit-queue (batch tested with PRs 46317, 48922, 50651, 50230, 47599) Resources outside the `*kubernetes.io` namespace are integers and cannot be over-committed. **What this PR does / why we need it**: Fixes #50473 Rationale: since the scheduler handles all resources except CPU as integers, that could just be the default behavior for namespaced resources. cc @RenaudWasTaken @vishh **Release note**: ```release-note Resources outside the `*kubernetes.io` namespace are integers and cannot be over-committed. ```
This commit is contained in:
commit
ce1485c626
@ -115,6 +115,21 @@ func IsStandardContainerResourceName(str string) bool {
|
|||||||
return standardContainerResources.Has(str)
|
return standardContainerResources.Has(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsExtendedResourceName returns true if the resource name is not in the
|
||||||
|
// default namespace, or it has the opaque integer resource prefix.
|
||||||
|
func IsExtendedResourceName(name api.ResourceName) bool {
|
||||||
|
// TODO: Remove OIR part following deprecation.
|
||||||
|
return !IsDefaultNamespaceResource(name) || IsOpaqueIntResourceName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDefaultNamespaceResource returns true if the resource name is in the
|
||||||
|
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
||||||
|
// implicitly in the kubernetes.io/ namespace.
|
||||||
|
func IsDefaultNamespaceResource(name api.ResourceName) bool {
|
||||||
|
return !strings.Contains(string(name), "/") ||
|
||||||
|
strings.Contains(string(name), api.ResourceDefaultNamespacePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
// IsOpaqueIntResourceName returns true if the resource name has the opaque
|
// IsOpaqueIntResourceName returns true if the resource name has the opaque
|
||||||
// integer resource prefix.
|
// integer resource prefix.
|
||||||
func IsOpaqueIntResourceName(name api.ResourceName) bool {
|
func IsOpaqueIntResourceName(name api.ResourceName) bool {
|
||||||
@ -131,6 +146,15 @@ func OpaqueIntResourceName(name string) api.ResourceName {
|
|||||||
return api.ResourceName(fmt.Sprintf("%s%s", api.ResourceOpaqueIntPrefix, name))
|
return api.ResourceName(fmt.Sprintf("%s%s", api.ResourceOpaqueIntPrefix, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var overcommitBlacklist = sets.NewString(string(api.ResourceNvidiaGPU))
|
||||||
|
|
||||||
|
// IsOvercommitAllowed returns true if the resource is in the default
|
||||||
|
// namespace and not blacklisted.
|
||||||
|
func IsOvercommitAllowed(name api.ResourceName) bool {
|
||||||
|
return IsDefaultNamespaceResource(name) &&
|
||||||
|
!overcommitBlacklist.Has(string(name))
|
||||||
|
}
|
||||||
|
|
||||||
var standardLimitRangeTypes = sets.NewString(
|
var standardLimitRangeTypes = sets.NewString(
|
||||||
string(api.LimitTypePod),
|
string(api.LimitTypePod),
|
||||||
string(api.LimitTypeContainer),
|
string(api.LimitTypeContainer),
|
||||||
@ -204,7 +228,7 @@ var integerResources = sets.NewString(
|
|||||||
|
|
||||||
// IsIntegerResourceName returns true if the resource is measured in integer values
|
// IsIntegerResourceName returns true if the resource is measured in integer values
|
||||||
func IsIntegerResourceName(str string) bool {
|
func IsIntegerResourceName(str string) bool {
|
||||||
return integerResources.Has(str) || IsOpaqueIntResourceName(api.ResourceName(str))
|
return integerResources.Has(str) || IsExtendedResourceName(api.ResourceName(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function aims to check if the service's ClusterIP is set or not
|
// this function aims to check if the service's ClusterIP is set or not
|
||||||
|
@ -3178,6 +3178,8 @@ const (
|
|||||||
const (
|
const (
|
||||||
// Namespace prefix for opaque counted resources (alpha).
|
// Namespace prefix for opaque counted resources (alpha).
|
||||||
ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-"
|
ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-"
|
||||||
|
// Default namespace prefix.
|
||||||
|
ResourceDefaultNamespacePrefix = "kubernetes.io/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResourceList is a set of (resource name, quantity) pairs.
|
// ResourceList is a set of (resource name, quantity) pairs.
|
||||||
|
@ -26,6 +26,7 @@ go_library(
|
|||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/selection:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/selection:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,9 +24,25 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/kubernetes/pkg/api/helper"
|
"k8s.io/kubernetes/pkg/api/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsExtendedResourceName returns true if the resource name is not in the
|
||||||
|
// default namespace, or it has the opaque integer resource prefix.
|
||||||
|
func IsExtendedResourceName(name v1.ResourceName) bool {
|
||||||
|
// TODO: Remove OIR part following deprecation.
|
||||||
|
return !IsDefaultNamespaceResource(name) || IsOpaqueIntResourceName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDefaultNamespaceResource returns true if the resource name is in the
|
||||||
|
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
||||||
|
// implicitly in the kubernetes.io/ namespace.
|
||||||
|
func IsDefaultNamespaceResource(name v1.ResourceName) bool {
|
||||||
|
return !strings.Contains(string(name), "/") ||
|
||||||
|
strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
// IsOpaqueIntResourceName returns true if the resource name has the opaque
|
// IsOpaqueIntResourceName returns true if the resource name has the opaque
|
||||||
// integer resource prefix.
|
// integer resource prefix.
|
||||||
func IsOpaqueIntResourceName(name v1.ResourceName) bool {
|
func IsOpaqueIntResourceName(name v1.ResourceName) bool {
|
||||||
@ -43,6 +59,15 @@ func OpaqueIntResourceName(name string) v1.ResourceName {
|
|||||||
return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceOpaqueIntPrefix, name))
|
return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceOpaqueIntPrefix, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var overcommitBlacklist = sets.NewString(string(v1.ResourceNvidiaGPU))
|
||||||
|
|
||||||
|
// IsOvercommitAllowed returns true if the resource is in the default
|
||||||
|
// namespace and not blacklisted.
|
||||||
|
func IsOvercommitAllowed(name v1.ResourceName) bool {
|
||||||
|
return IsDefaultNamespaceResource(name) &&
|
||||||
|
!overcommitBlacklist.Has(string(name))
|
||||||
|
}
|
||||||
|
|
||||||
// this function aims to check if the service's ClusterIP is set or not
|
// this function aims to check if the service's ClusterIP is set or not
|
||||||
// the objective is not to perform validation here
|
// the objective is not to perform validation here
|
||||||
func IsServiceIPSet(service *v1.Service) bool {
|
func IsServiceIPSet(service *v1.Service) bool {
|
||||||
|
@ -10,6 +10,8 @@ go_library(
|
|||||||
srcs = ["validation.go"],
|
srcs = ["validation.go"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api/helper:go_default_library",
|
"//pkg/api/helper:go_default_library",
|
||||||
|
"//pkg/api/v1/helper:go_default_library",
|
||||||
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
|
@ -20,12 +20,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/kubernetes/pkg/api/helper"
|
"k8s.io/kubernetes/pkg/api/helper"
|
||||||
|
v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const isNegativeErrorMsg string = `must be greater than or equal to 0`
|
const isNegativeErrorMsg string = `must be greater than or equal to 0`
|
||||||
@ -46,9 +49,9 @@ func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath
|
|||||||
// Check that request <= limit.
|
// Check that request <= limit.
|
||||||
requestQuantity, exists := requirements.Requests[resourceName]
|
requestQuantity, exists := requirements.Requests[resourceName]
|
||||||
if exists {
|
if exists {
|
||||||
// For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal.
|
// Ensure overcommit is allowed for the resource if request != limit
|
||||||
if resourceName == v1.ResourceNvidiaGPU && quantity.Cmp(requestQuantity) != 0 {
|
if quantity.Cmp(requestQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) {
|
||||||
allErrs = append(allErrs, field.Invalid(reqPath, requestQuantity.String(), fmt.Sprintf("must be equal to %s limit", v1.ResourceNvidiaGPU)))
|
allErrs = append(allErrs, field.Invalid(reqPath, requestQuantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName)))
|
||||||
} else if quantity.Cmp(requestQuantity) < 0 {
|
} else if quantity.Cmp(requestQuantity) < 0 {
|
||||||
allErrs = append(allErrs, field.Invalid(limPath, quantity.String(), fmt.Sprintf("must be greater than or equal to %s request", resourceName)))
|
allErrs = append(allErrs, field.Invalid(limPath, quantity.String(), fmt.Sprintf("must be greater than or equal to %s request", resourceName)))
|
||||||
}
|
}
|
||||||
@ -99,6 +102,12 @@ func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) f
|
|||||||
// Validate compute resource typename.
|
// Validate compute resource typename.
|
||||||
// Refer to docs/design/resources.md for more details.
|
// Refer to docs/design/resources.md for more details.
|
||||||
func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
|
func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
|
||||||
|
// Opaque integer resources (OIR) deprecation began in v1.8
|
||||||
|
// TODO: Remove warning after OIR deprecation cycle.
|
||||||
|
if v1helper.IsOpaqueIntResourceName(v1.ResourceName(value)) {
|
||||||
|
glog.Errorf("DEPRECATION WARNING! Opaque integer resources are deprecated starting with v1.8: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
for _, msg := range validation.IsQualifiedName(value) {
|
for _, msg := range validation.IsQualifiedName(value) {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
|
allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
|
||||||
|
@ -3358,6 +3358,12 @@ func ValidateNodeUpdate(node, oldNode *api.Node) field.ErrorList {
|
|||||||
// Validate compute resource typename.
|
// Validate compute resource typename.
|
||||||
// Refer to docs/design/resources.md for more details.
|
// Refer to docs/design/resources.md for more details.
|
||||||
func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
|
func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
|
||||||
|
// Opaque integer resources (OIR) deprecation began in v1.8
|
||||||
|
// TODO: Remove warning after OIR deprecation cycle.
|
||||||
|
if helper.IsOpaqueIntResourceName(api.ResourceName(value)) {
|
||||||
|
glog.Errorf("DEPRECATION WARNING! Opaque integer resources are deprecated starting with v1.8: %s", value)
|
||||||
|
}
|
||||||
|
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
for _, msg := range validation.IsQualifiedName(value) {
|
for _, msg := range validation.IsQualifiedName(value) {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
|
allErrs = append(allErrs, field.Invalid(fldPath, value, msg))
|
||||||
@ -3715,9 +3721,9 @@ func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPat
|
|||||||
// Check that request <= limit.
|
// Check that request <= limit.
|
||||||
requestQuantity, exists := requirements.Requests[resourceName]
|
requestQuantity, exists := requirements.Requests[resourceName]
|
||||||
if exists {
|
if exists {
|
||||||
// For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal.
|
// Ensure overcommit is allowed for the resource if request != limit
|
||||||
if resourceName == api.ResourceNvidiaGPU && quantity.Cmp(requestQuantity) != 0 {
|
if quantity.Cmp(requestQuantity) != 0 && !helper.IsOvercommitAllowed(resourceName) {
|
||||||
allErrs = append(allErrs, field.Invalid(reqPath, requestQuantity.String(), fmt.Sprintf("must be equal to %s limit", api.ResourceNvidiaGPU)))
|
allErrs = append(allErrs, field.Invalid(reqPath, requestQuantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName)))
|
||||||
} else if quantity.Cmp(requestQuantity) < 0 {
|
} else if quantity.Cmp(requestQuantity) < 0 {
|
||||||
allErrs = append(allErrs, field.Invalid(limPath, quantity.String(), fmt.Sprintf("must be greater than or equal to %s request", resourceName)))
|
allErrs = append(allErrs, field.Invalid(limPath, quantity.String(), fmt.Sprintf("must be greater than or equal to %s request", resourceName)))
|
||||||
}
|
}
|
||||||
|
@ -3287,7 +3287,7 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
Limits: api.ResourceList{
|
Limits: api.ResourceList{
|
||||||
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
|
api.ResourceName("my.org/resource"): resource.MustParse("10"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImagePullPolicy: "IfNotPresent",
|
ImagePullPolicy: "IfNotPresent",
|
||||||
@ -3349,12 +3349,12 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
Requests: api.ResourceList{
|
Requests: api.ResourceList{
|
||||||
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
|
api.ResourceName("my.org/resource"): resource.MustParse("10"),
|
||||||
},
|
},
|
||||||
Limits: api.ResourceList{
|
Limits: api.ResourceList{
|
||||||
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
||||||
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
|
api.ResourceName("my.org/resource"): resource.MustParse("10"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImagePullPolicy: "IfNotPresent",
|
ImagePullPolicy: "IfNotPresent",
|
||||||
@ -3370,7 +3370,7 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Limits: api.ResourceList{
|
Limits: api.ResourceList{
|
||||||
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
||||||
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
|
api.ResourceName("my.org/resource"): resource.MustParse("10"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImagePullPolicy: "IfNotPresent",
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
@ -577,11 +577,11 @@ func (kl *Kubelet) setNodeStatusMachineInfo(node *v1.Node) {
|
|||||||
if node.Status.Allocatable == nil {
|
if node.Status.Allocatable == nil {
|
||||||
node.Status.Allocatable = make(v1.ResourceList)
|
node.Status.Allocatable = make(v1.ResourceList)
|
||||||
}
|
}
|
||||||
// Remove opaque integer resources from allocatable that are no longer
|
// Remove extended resources from allocatable that are no longer
|
||||||
// present in capacity.
|
// present in capacity.
|
||||||
for k := range node.Status.Allocatable {
|
for k := range node.Status.Allocatable {
|
||||||
_, found := node.Status.Capacity[k]
|
_, found := node.Status.Capacity[k]
|
||||||
if !found && v1helper.IsOpaqueIntResourceName(k) {
|
if !found && v1helper.IsExtendedResourceName(k) {
|
||||||
delete(node.Status.Allocatable, k)
|
delete(node.Status.Allocatable, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -533,10 +533,10 @@ func GetResourceRequest(pod *v1.Pod) *schedulercache.Resource {
|
|||||||
result.StorageOverlay = overlay
|
result.StorageOverlay = overlay
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if v1helper.IsOpaqueIntResourceName(rName) {
|
if v1helper.IsExtendedResourceName(rName) {
|
||||||
value := rQuantity.Value()
|
value := rQuantity.Value()
|
||||||
if value > result.OpaqueIntResources[rName] {
|
if value > result.ExtendedResources[rName] {
|
||||||
result.SetOpaque(rName, value)
|
result.SetExtended(rName, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -572,7 +572,7 @@ func PodFitsResources(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.No
|
|||||||
// We couldn't parse metadata - fallback to computing it.
|
// We couldn't parse metadata - fallback to computing it.
|
||||||
podRequest = GetResourceRequest(pod)
|
podRequest = GetResourceRequest(pod)
|
||||||
}
|
}
|
||||||
if podRequest.MilliCPU == 0 && podRequest.Memory == 0 && podRequest.NvidiaGPU == 0 && podRequest.StorageOverlay == 0 && podRequest.StorageScratch == 0 && len(podRequest.OpaqueIntResources) == 0 {
|
if podRequest.MilliCPU == 0 && podRequest.Memory == 0 && podRequest.NvidiaGPU == 0 && podRequest.StorageOverlay == 0 && podRequest.StorageScratch == 0 && len(podRequest.ExtendedResources) == 0 {
|
||||||
return len(predicateFails) == 0, predicateFails, nil
|
return len(predicateFails) == 0, predicateFails, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,9 +603,9 @@ func PodFitsResources(pod *v1.Pod, meta interface{}, nodeInfo *schedulercache.No
|
|||||||
predicateFails = append(predicateFails, NewInsufficientResourceError(v1.ResourceStorageOverlay, podRequest.StorageOverlay, nodeInfo.RequestedResource().StorageOverlay, allocatable.StorageOverlay))
|
predicateFails = append(predicateFails, NewInsufficientResourceError(v1.ResourceStorageOverlay, podRequest.StorageOverlay, nodeInfo.RequestedResource().StorageOverlay, allocatable.StorageOverlay))
|
||||||
}
|
}
|
||||||
|
|
||||||
for rName, rQuant := range podRequest.OpaqueIntResources {
|
for rName, rQuant := range podRequest.ExtendedResources {
|
||||||
if allocatable.OpaqueIntResources[rName] < rQuant+nodeInfo.RequestedResource().OpaqueIntResources[rName] {
|
if allocatable.ExtendedResources[rName] < rQuant+nodeInfo.RequestedResource().ExtendedResources[rName] {
|
||||||
predicateFails = append(predicateFails, NewInsufficientResourceError(rName, podRequest.OpaqueIntResources[rName], nodeInfo.RequestedResource().OpaqueIntResources[rName], allocatable.OpaqueIntResources[rName]))
|
predicateFails = append(predicateFails, NewInsufficientResourceError(rName, podRequest.ExtendedResources[rName], nodeInfo.RequestedResource().ExtendedResources[rName], allocatable.ExtendedResources[rName]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,85 +254,85 @@ func TestPodFitsResources(t *testing.T) {
|
|||||||
test: "equal edge case for init container",
|
test: "equal edge case for init container",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourcePod(schedulercache.Resource{OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
pod: newResourcePod(schedulercache.Resource{ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(newResourcePod(schedulercache.Resource{})),
|
nodeInfo: schedulercache.NewNodeInfo(newResourcePod(schedulercache.Resource{})),
|
||||||
fits: true,
|
fits: true,
|
||||||
test: "opaque resource fits",
|
test: "opaque resource fits",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), schedulercache.Resource{OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}), schedulercache.Resource{ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(newResourcePod(schedulercache.Resource{})),
|
nodeInfo: schedulercache.NewNodeInfo(newResourcePod(schedulercache.Resource{})),
|
||||||
fits: true,
|
fits: true,
|
||||||
test: "opaque resource fits for init container",
|
test: "opaque resource fits for init container",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourcePod(
|
pod: newResourcePod(
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 10}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 10}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 0}})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 0}})),
|
||||||
fits: false,
|
fits: false,
|
||||||
test: "opaque resource capacity enforced",
|
test: "opaque resource capacity enforced",
|
||||||
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 10, 0, 5)},
|
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 10, 0, 5)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 10}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 10}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 0}})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 0}})),
|
||||||
fits: false,
|
fits: false,
|
||||||
test: "opaque resource capacity enforced for init container",
|
test: "opaque resource capacity enforced for init container",
|
||||||
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 10, 0, 5)},
|
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 10, 0, 5)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourcePod(
|
pod: newResourcePod(
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 5}})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 5}})),
|
||||||
fits: false,
|
fits: false,
|
||||||
test: "opaque resource allocatable enforced",
|
test: "opaque resource allocatable enforced",
|
||||||
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 1, 5, 5)},
|
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 1, 5, 5)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 1}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 5}})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 5}})),
|
||||||
fits: false,
|
fits: false,
|
||||||
test: "opaque resource allocatable enforced for init container",
|
test: "opaque resource allocatable enforced for init container",
|
||||||
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 1, 5, 5)},
|
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 1, 5, 5)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourcePod(
|
pod: newResourcePod(
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 3}},
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 3}},
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})),
|
||||||
fits: false,
|
fits: false,
|
||||||
test: "opaque resource allocatable enforced for multiple containers",
|
test: "opaque resource allocatable enforced for multiple containers",
|
||||||
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 6, 2, 5)},
|
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 6, 2, 5)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 3}},
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 3}},
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})),
|
||||||
fits: true,
|
fits: true,
|
||||||
test: "opaque resource allocatable admits multiple init containers",
|
test: "opaque resource allocatable admits multiple init containers",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 6}},
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 6}},
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 3}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceA: 2}})),
|
||||||
fits: false,
|
fits: false,
|
||||||
test: "opaque resource allocatable enforced for multiple init containers",
|
test: "opaque resource allocatable enforced for multiple init containers",
|
||||||
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 6, 2, 5)},
|
reasons: []algorithm.PredicateFailureReason{NewInsufficientResourceError(opaqueResourceA, 6, 2, 5)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourcePod(
|
pod: newResourcePod(
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceB: 1}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceB: 1}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0})),
|
||||||
fits: false,
|
fits: false,
|
||||||
@ -341,7 +341,7 @@ func TestPodFitsResources(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
pod: newResourceInitPod(newResourcePod(schedulercache.Resource{}),
|
||||||
schedulercache.Resource{MilliCPU: 1, Memory: 1, OpaqueIntResources: map[v1.ResourceName]int64{opaqueResourceB: 1}}),
|
schedulercache.Resource{MilliCPU: 1, Memory: 1, ExtendedResources: map[v1.ResourceName]int64{opaqueResourceB: 1}}),
|
||||||
nodeInfo: schedulercache.NewNodeInfo(
|
nodeInfo: schedulercache.NewNodeInfo(
|
||||||
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0})),
|
newResourcePod(schedulercache.Resource{MilliCPU: 0, Memory: 0})),
|
||||||
fits: false,
|
fits: false,
|
||||||
|
@ -109,9 +109,9 @@ func TestAssumePodScheduled(t *testing.T) {
|
|||||||
pods: []*v1.Pod{testPods[4]},
|
pods: []*v1.Pod{testPods[4]},
|
||||||
wNodeInfo: &NodeInfo{
|
wNodeInfo: &NodeInfo{
|
||||||
requestedResource: &Resource{
|
requestedResource: &Resource{
|
||||||
MilliCPU: 100,
|
MilliCPU: 100,
|
||||||
Memory: 500,
|
Memory: 500,
|
||||||
OpaqueIntResources: map[v1.ResourceName]int64{"pod.alpha.kubernetes.io/opaque-int-resource-oir-foo": 3},
|
ExtendedResources: map[v1.ResourceName]int64{"pod.alpha.kubernetes.io/opaque-int-resource-oir-foo": 3},
|
||||||
},
|
},
|
||||||
nonzeroRequest: &Resource{
|
nonzeroRequest: &Resource{
|
||||||
MilliCPU: 100,
|
MilliCPU: 100,
|
||||||
@ -125,9 +125,9 @@ func TestAssumePodScheduled(t *testing.T) {
|
|||||||
pods: []*v1.Pod{testPods[4], testPods[5]},
|
pods: []*v1.Pod{testPods[4], testPods[5]},
|
||||||
wNodeInfo: &NodeInfo{
|
wNodeInfo: &NodeInfo{
|
||||||
requestedResource: &Resource{
|
requestedResource: &Resource{
|
||||||
MilliCPU: 300,
|
MilliCPU: 300,
|
||||||
Memory: 1524,
|
Memory: 1524,
|
||||||
OpaqueIntResources: map[v1.ResourceName]int64{"pod.alpha.kubernetes.io/opaque-int-resource-oir-foo": 8},
|
ExtendedResources: map[v1.ResourceName]int64{"pod.alpha.kubernetes.io/opaque-int-resource-oir-foo": 8},
|
||||||
},
|
},
|
||||||
nonzeroRequest: &Resource{
|
nonzeroRequest: &Resource{
|
||||||
MilliCPU: 300,
|
MilliCPU: 300,
|
||||||
|
@ -70,8 +70,8 @@ type Resource struct {
|
|||||||
StorageOverlay int64
|
StorageOverlay int64
|
||||||
// We store allowedPodNumber (which is Node.Status.Allocatable.Pods().Value())
|
// We store allowedPodNumber (which is Node.Status.Allocatable.Pods().Value())
|
||||||
// explicitly as int, to avoid conversions and improve performance.
|
// explicitly as int, to avoid conversions and improve performance.
|
||||||
AllowedPodNumber int
|
AllowedPodNumber int
|
||||||
OpaqueIntResources map[v1.ResourceName]int64
|
ExtendedResources map[v1.ResourceName]int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Resource from ResourceList
|
// New creates a Resource from ResourceList
|
||||||
@ -102,8 +102,8 @@ func (r *Resource) Add(rl v1.ResourceList) {
|
|||||||
case v1.ResourceStorageOverlay:
|
case v1.ResourceStorageOverlay:
|
||||||
r.StorageOverlay += rQuant.Value()
|
r.StorageOverlay += rQuant.Value()
|
||||||
default:
|
default:
|
||||||
if v1helper.IsOpaqueIntResourceName(rName) {
|
if v1helper.IsExtendedResourceName(rName) {
|
||||||
r.AddOpaque(rName, rQuant.Value())
|
r.AddExtended(rName, rQuant.Value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ func (r *Resource) ResourceList() v1.ResourceList {
|
|||||||
v1.ResourceStorageOverlay: *resource.NewQuantity(r.StorageOverlay, resource.BinarySI),
|
v1.ResourceStorageOverlay: *resource.NewQuantity(r.StorageOverlay, resource.BinarySI),
|
||||||
v1.ResourceStorageScratch: *resource.NewQuantity(r.StorageScratch, resource.BinarySI),
|
v1.ResourceStorageScratch: *resource.NewQuantity(r.StorageScratch, resource.BinarySI),
|
||||||
}
|
}
|
||||||
for rName, rQuant := range r.OpaqueIntResources {
|
for rName, rQuant := range r.ExtendedResources {
|
||||||
result[rName] = *resource.NewQuantity(rQuant, resource.DecimalSI)
|
result[rName] = *resource.NewQuantity(rQuant, resource.DecimalSI)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -133,25 +133,25 @@ func (r *Resource) Clone() *Resource {
|
|||||||
StorageOverlay: r.StorageOverlay,
|
StorageOverlay: r.StorageOverlay,
|
||||||
StorageScratch: r.StorageScratch,
|
StorageScratch: r.StorageScratch,
|
||||||
}
|
}
|
||||||
if r.OpaqueIntResources != nil {
|
if r.ExtendedResources != nil {
|
||||||
res.OpaqueIntResources = make(map[v1.ResourceName]int64)
|
res.ExtendedResources = make(map[v1.ResourceName]int64)
|
||||||
for k, v := range r.OpaqueIntResources {
|
for k, v := range r.ExtendedResources {
|
||||||
res.OpaqueIntResources[k] = v
|
res.ExtendedResources[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) AddOpaque(name v1.ResourceName, quantity int64) {
|
func (r *Resource) AddExtended(name v1.ResourceName, quantity int64) {
|
||||||
r.SetOpaque(name, r.OpaqueIntResources[name]+quantity)
|
r.SetExtended(name, r.ExtendedResources[name]+quantity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetOpaque(name v1.ResourceName, quantity int64) {
|
func (r *Resource) SetExtended(name v1.ResourceName, quantity int64) {
|
||||||
// Lazily allocate opaque integer resource map.
|
// Lazily allocate opaque integer resource map.
|
||||||
if r.OpaqueIntResources == nil {
|
if r.ExtendedResources == nil {
|
||||||
r.OpaqueIntResources = map[v1.ResourceName]int64{}
|
r.ExtendedResources = map[v1.ResourceName]int64{}
|
||||||
}
|
}
|
||||||
r.OpaqueIntResources[name] = quantity
|
r.ExtendedResources[name] = quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNodeInfo returns a ready to use empty NodeInfo object.
|
// NewNodeInfo returns a ready to use empty NodeInfo object.
|
||||||
@ -306,11 +306,11 @@ func (n *NodeInfo) addPod(pod *v1.Pod) {
|
|||||||
n.requestedResource.NvidiaGPU += res.NvidiaGPU
|
n.requestedResource.NvidiaGPU += res.NvidiaGPU
|
||||||
n.requestedResource.StorageOverlay += res.StorageOverlay
|
n.requestedResource.StorageOverlay += res.StorageOverlay
|
||||||
n.requestedResource.StorageScratch += res.StorageScratch
|
n.requestedResource.StorageScratch += res.StorageScratch
|
||||||
if n.requestedResource.OpaqueIntResources == nil && len(res.OpaqueIntResources) > 0 {
|
if n.requestedResource.ExtendedResources == nil && len(res.ExtendedResources) > 0 {
|
||||||
n.requestedResource.OpaqueIntResources = map[v1.ResourceName]int64{}
|
n.requestedResource.ExtendedResources = map[v1.ResourceName]int64{}
|
||||||
}
|
}
|
||||||
for rName, rQuant := range res.OpaqueIntResources {
|
for rName, rQuant := range res.ExtendedResources {
|
||||||
n.requestedResource.OpaqueIntResources[rName] += rQuant
|
n.requestedResource.ExtendedResources[rName] += rQuant
|
||||||
}
|
}
|
||||||
n.nonzeroRequest.MilliCPU += non0_cpu
|
n.nonzeroRequest.MilliCPU += non0_cpu
|
||||||
n.nonzeroRequest.Memory += non0_mem
|
n.nonzeroRequest.Memory += non0_mem
|
||||||
@ -361,11 +361,11 @@ func (n *NodeInfo) removePod(pod *v1.Pod) error {
|
|||||||
n.requestedResource.MilliCPU -= res.MilliCPU
|
n.requestedResource.MilliCPU -= res.MilliCPU
|
||||||
n.requestedResource.Memory -= res.Memory
|
n.requestedResource.Memory -= res.Memory
|
||||||
n.requestedResource.NvidiaGPU -= res.NvidiaGPU
|
n.requestedResource.NvidiaGPU -= res.NvidiaGPU
|
||||||
if len(res.OpaqueIntResources) > 0 && n.requestedResource.OpaqueIntResources == nil {
|
if len(res.ExtendedResources) > 0 && n.requestedResource.ExtendedResources == nil {
|
||||||
n.requestedResource.OpaqueIntResources = map[v1.ResourceName]int64{}
|
n.requestedResource.ExtendedResources = map[v1.ResourceName]int64{}
|
||||||
}
|
}
|
||||||
for rName, rQuant := range res.OpaqueIntResources {
|
for rName, rQuant := range res.ExtendedResources {
|
||||||
n.requestedResource.OpaqueIntResources[rName] -= rQuant
|
n.requestedResource.ExtendedResources[rName] -= rQuant
|
||||||
}
|
}
|
||||||
n.nonzeroRequest.MilliCPU -= non0_cpu
|
n.nonzeroRequest.MilliCPU -= non0_cpu
|
||||||
n.nonzeroRequest.Memory -= non0_mem
|
n.nonzeroRequest.Memory -= non0_mem
|
||||||
|
@ -3611,6 +3611,8 @@ const (
|
|||||||
const (
|
const (
|
||||||
// Namespace prefix for opaque counted resources (alpha).
|
// Namespace prefix for opaque counted resources (alpha).
|
||||||
ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-"
|
ResourceOpaqueIntPrefix = "pod.alpha.kubernetes.io/opaque-int-resource-"
|
||||||
|
// Default namespace prefix.
|
||||||
|
ResourceDefaultNamespacePrefix = "kubernetes.io/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResourceList is a set of (resource name, quantity) pairs.
|
// ResourceList is a set of (resource name, quantity) pairs.
|
||||||
|
Loading…
Reference in New Issue
Block a user