mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
api: add resource claims to core API
The resource.k8s.io/ClaimTemplate only gets referenced by name, therefore the changes to the core API are limited.
This commit is contained in:
parent
155d49813f
commit
7d11b422e3
@ -550,6 +550,42 @@ func dropDisabledFields(
|
||||
dropDisabledTopologySpreadConstraintsFields(podSpec, oldPodSpec)
|
||||
dropDisabledNodeInclusionPolicyFields(podSpec, oldPodSpec)
|
||||
dropDisabledMatchLabelKeysField(podSpec, oldPodSpec)
|
||||
dropDisabledDynamicResourceAllocationFields(podSpec, oldPodSpec)
|
||||
}
|
||||
|
||||
// dropDisabledDynamicResourceAllocationFields removes pod claim references from
|
||||
// container specs and pod-level resource claims unless they are already used
|
||||
// by the old pod spec.
|
||||
func dropDisabledDynamicResourceAllocationFields(podSpec, oldPodSpec *api.PodSpec) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) && !dynamicResourceAllocationInUse(oldPodSpec) {
|
||||
dropResourceClaimRequests(podSpec.Containers)
|
||||
dropResourceClaimRequests(podSpec.InitContainers)
|
||||
dropEphemeralResourceClaimRequests(podSpec.EphemeralContainers)
|
||||
podSpec.ResourceClaims = nil
|
||||
}
|
||||
}
|
||||
|
||||
func dynamicResourceAllocationInUse(podSpec *api.PodSpec) bool {
|
||||
if podSpec == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// We only need to check this field because the containers cannot have
|
||||
// resource requirements entries for claims without a corresponding
|
||||
// entry at the pod spec level.
|
||||
return len(podSpec.ResourceClaims) > 0
|
||||
}
|
||||
|
||||
func dropResourceClaimRequests(containers []api.Container) {
|
||||
for i := range containers {
|
||||
containers[i].Resources.Claims = nil
|
||||
}
|
||||
}
|
||||
|
||||
func dropEphemeralResourceClaimRequests(containers []api.EphemeralContainer) {
|
||||
for i := range containers {
|
||||
containers[i].Resources.Claims = nil
|
||||
}
|
||||
}
|
||||
|
||||
// dropDisabledTopologySpreadConstraintsFields removes disabled fields from PodSpec related
|
||||
|
@ -784,6 +784,165 @@ func TestDropAppArmor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropDynamicResourceAllocation(t *testing.T) {
|
||||
resourceClaimName := "external-claim"
|
||||
|
||||
podWithClaims := &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Resources: api.ResourceRequirements{
|
||||
Claims: []api.ResourceClaim{{Name: "my-claim"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Resources: api.ResourceRequirements{
|
||||
Claims: []api.ResourceClaim{{Name: "my-claim"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
EphemeralContainers: []api.EphemeralContainer{
|
||||
{
|
||||
EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||
Resources: api.ResourceRequirements{
|
||||
Claims: []api.ResourceClaim{{Name: "my-claim"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ResourceClaims: []api.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: api.ClaimSource{
|
||||
ResourceClaimName: &resourceClaimName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
podWithoutClaims := &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{}},
|
||||
InitContainers: []api.Container{{}},
|
||||
EphemeralContainers: []api.EphemeralContainer{{}},
|
||||
},
|
||||
}
|
||||
|
||||
var noPod *api.Pod
|
||||
|
||||
testcases := []struct {
|
||||
description string
|
||||
enabled bool
|
||||
oldPod *api.Pod
|
||||
newPod *api.Pod
|
||||
wantPod *api.Pod
|
||||
}{
|
||||
{
|
||||
description: "old with claims / new with claims / disabled",
|
||||
oldPod: podWithClaims,
|
||||
newPod: podWithClaims,
|
||||
wantPod: podWithClaims,
|
||||
},
|
||||
{
|
||||
description: "old without claims / new with claims / disabled",
|
||||
oldPod: podWithoutClaims,
|
||||
newPod: podWithClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new with claims / disabled",
|
||||
oldPod: noPod,
|
||||
newPod: podWithClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
|
||||
{
|
||||
description: "old with claims / new without claims / disabled",
|
||||
oldPod: podWithClaims,
|
||||
newPod: podWithoutClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
{
|
||||
description: "old without claims / new without claims / disabled",
|
||||
oldPod: podWithoutClaims,
|
||||
newPod: podWithoutClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new without claims / disabled",
|
||||
oldPod: noPod,
|
||||
newPod: podWithoutClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
|
||||
{
|
||||
description: "old with claims / new with claims / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithClaims,
|
||||
newPod: podWithClaims,
|
||||
wantPod: podWithClaims,
|
||||
},
|
||||
{
|
||||
description: "old without claims / new with claims / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithoutClaims,
|
||||
newPod: podWithClaims,
|
||||
wantPod: podWithClaims,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new with claims / enabled",
|
||||
enabled: true,
|
||||
oldPod: noPod,
|
||||
newPod: podWithClaims,
|
||||
wantPod: podWithClaims,
|
||||
},
|
||||
|
||||
{
|
||||
description: "old with claims / new without claims / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithClaims,
|
||||
newPod: podWithoutClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
{
|
||||
description: "old without claims / new without claims / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithoutClaims,
|
||||
newPod: podWithoutClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new without claims / enabled",
|
||||
enabled: true,
|
||||
oldPod: noPod,
|
||||
newPod: podWithoutClaims,
|
||||
wantPod: podWithoutClaims,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicResourceAllocation, tc.enabled)()
|
||||
|
||||
oldPod := tc.oldPod.DeepCopy()
|
||||
newPod := tc.newPod.DeepCopy()
|
||||
wantPod := tc.wantPod
|
||||
DropDisabledPodFields(newPod, oldPod)
|
||||
|
||||
// old pod should never be changed
|
||||
if diff := cmp.Diff(oldPod, tc.oldPod); diff != "" {
|
||||
t.Errorf("old pod changed: %s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantPod, newPod); diff != "" {
|
||||
t.Errorf("new pod changed (- want, + got): %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropProbeGracePeriod(t *testing.T) {
|
||||
podWithProbeGracePeriod := func() *api.Pod {
|
||||
livenessGracePeriod := int64(10)
|
||||
|
@ -2185,6 +2185,25 @@ type ResourceRequirements struct {
|
||||
// otherwise to an implementation-defined value
|
||||
// +optional
|
||||
Requests ResourceList
|
||||
// Claims lists the names of resources, defined in spec.resourceClaims,
|
||||
// that are used by this container.
|
||||
//
|
||||
// This is an alpha field and requires enabling the
|
||||
// DynamicResourceAllocation feature gate.
|
||||
//
|
||||
// This field is immutable.
|
||||
//
|
||||
// +featureGate=DynamicResourceAllocation
|
||||
// +optional
|
||||
Claims []ResourceClaim
|
||||
}
|
||||
|
||||
// ResourceClaim references one entry in PodSpec.ResourceClaims.
|
||||
type ResourceClaim struct {
|
||||
// Name must match the name of one entry in pod.spec.resourceClaims of
|
||||
// the Pod where this field is used. It makes that resource available
|
||||
// inside a container.
|
||||
Name string
|
||||
}
|
||||
|
||||
// Container represents a single container that is expected to be run on the host.
|
||||
@ -3024,12 +3043,68 @@ type PodSpec struct {
|
||||
// - spec.containers[*].securityContext.runAsGroup
|
||||
// +optional
|
||||
OS *PodOS
|
||||
|
||||
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
|
||||
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
|
||||
//
|
||||
// This is an alpha-level feature enabled by PodSchedulingReadiness feature gate.
|
||||
// +optional
|
||||
SchedulingGates []PodSchedulingGate
|
||||
// ResourceClaims defines which ResourceClaims must be allocated
|
||||
// and reserved before the Pod is allowed to start. The resources
|
||||
// will be made available to those containers which consume them
|
||||
// by name.
|
||||
//
|
||||
// This is an alpha field and requires enabling the
|
||||
// DynamicResourceAllocation feature gate.
|
||||
//
|
||||
// This field is immutable.
|
||||
//
|
||||
// +featureGate=DynamicResourceAllocation
|
||||
// +optional
|
||||
ResourceClaims []PodResourceClaim
|
||||
}
|
||||
|
||||
// PodResourceClaim references exactly one ResourceClaim through a ClaimSource.
|
||||
// It adds a name to it that uniquely identifies the ResourceClaim inside the Pod.
|
||||
// Containers that need access to the ResourceClaim reference it with this name.
|
||||
type PodResourceClaim struct {
|
||||
// Name uniquely identifies this resource claim inside the pod.
|
||||
// This must be a DNS_LABEL.
|
||||
Name string
|
||||
|
||||
// Source describes where to find the ResourceClaim.
|
||||
Source ClaimSource
|
||||
}
|
||||
|
||||
// ClaimSource describes a reference to a ResourceClaim.
|
||||
//
|
||||
// Exactly one of these fields should be set. Consumers of this type must
|
||||
// treat an empty object as if it has an unknown value.
|
||||
type ClaimSource struct {
|
||||
// ResourceClaimName is the name of a ResourceClaim object in the same
|
||||
// namespace as this pod.
|
||||
ResourceClaimName *string
|
||||
|
||||
// ResourceClaimTemplateName is the name of a ResourceClaimTemplate
|
||||
// object in the same namespace as this pod.
|
||||
//
|
||||
// The template will be used to create a new ResourceClaim, which will
|
||||
// be bound to this pod. When this pod is deleted, the ResourceClaim
|
||||
// will also be deleted. The name of the ResourceClaim will be <pod
|
||||
// name>-<resource name>, where <resource name> is the
|
||||
// PodResourceClaim.Name. Pod validation will reject the pod if the
|
||||
// concatenated name is not valid for a ResourceClaim (e.g. too long).
|
||||
//
|
||||
// An existing ResourceClaim with that name that is not owned by the
|
||||
// pod will not be used for the pod to avoid using an unrelated
|
||||
// resource by mistake. Scheduling and pod startup are then blocked
|
||||
// until the unrelated ResourceClaim is removed.
|
||||
//
|
||||
// This field is immutable and no changes will be made to the
|
||||
// corresponding ResourceClaim by the control plane after creating the
|
||||
// ResourceClaim.
|
||||
ResourceClaimTemplateName *string
|
||||
}
|
||||
|
||||
// OSName is the set of OS'es that can be used in OS.
|
||||
|
@ -309,7 +309,7 @@ func ValidateRuntimeClassName(name string, fldPath *field.Path) field.ErrorList
|
||||
// validateOverhead can be used to check whether the given Overhead is valid.
|
||||
func validateOverhead(overhead core.ResourceList, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
// reuse the ResourceRequirements validation logic
|
||||
return ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead}, fldPath, opts)
|
||||
return ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead}, nil, fldPath, opts)
|
||||
}
|
||||
|
||||
// Validates that given value is not negative.
|
||||
@ -1621,12 +1621,12 @@ func validateEphemeralVolumeSource(ephemeral *core.EphemeralVolumeSource, fldPat
|
||||
// ValidatePersistentVolumeClaimTemplate verifies that the embedded object meta and spec are valid.
|
||||
// Checking of the object data is very minimal because only labels and annotations are used.
|
||||
func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList {
|
||||
allErrs := validatePersistentVolumeClaimTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata"))
|
||||
allErrs := ValidateTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata"))
|
||||
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"), opts)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validatePersistentVolumeClaimTemplateObjectMeta(objMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
|
||||
func ValidateTemplateObjectMeta(objMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := apimachineryvalidation.ValidateAnnotations(objMeta.Annotations, fldPath.Child("annotations"))
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(objMeta.Labels, fldPath.Child("labels"))...)
|
||||
// All other fields are not supported and thus must not be set
|
||||
@ -1634,11 +1634,11 @@ func validatePersistentVolumeClaimTemplateObjectMeta(objMeta *metav1.ObjectMeta,
|
||||
// but then adding a new one to ObjectMeta wouldn't be checked
|
||||
// unless this code gets updated. Instead, we ensure that
|
||||
// only allowed fields are set via reflection.
|
||||
allErrs = append(allErrs, validateFieldAllowList(*objMeta, allowedPVCTemplateObjectMetaFields, "cannot be set for an ephemeral volume", fldPath)...)
|
||||
allErrs = append(allErrs, validateFieldAllowList(*objMeta, allowedTemplateObjectMetaFields, "cannot be set", fldPath)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var allowedPVCTemplateObjectMetaFields = map[string]bool{
|
||||
var allowedTemplateObjectMetaFields = map[string]bool{
|
||||
"Annotations": true,
|
||||
"Labels": true,
|
||||
}
|
||||
@ -2768,6 +2768,54 @@ func ValidateVolumeDevices(devices []core.VolumeDevice, volmounts map[string]str
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validatePodResourceClaims(claims []core.PodResourceClaim, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
podClaimNames := sets.NewString()
|
||||
for i, claim := range claims {
|
||||
allErrs = append(allErrs, validatePodResourceClaim(claim, &podClaimNames, fldPath.Index(i))...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// gatherPodResourceClaimNames returns a set of all non-empty
|
||||
// PodResourceClaim.Name values. Validation that those names are valid is
|
||||
// handled by validatePodResourceClaims.
|
||||
func gatherPodResourceClaimNames(claims []core.PodResourceClaim) sets.String {
|
||||
podClaimNames := sets.String{}
|
||||
for _, claim := range claims {
|
||||
if claim.Name != "" {
|
||||
podClaimNames.Insert(claim.Name)
|
||||
}
|
||||
}
|
||||
return podClaimNames
|
||||
}
|
||||
|
||||
func validatePodResourceClaim(claim core.PodResourceClaim, podClaimNames *sets.String, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if claim.Name == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
} else if podClaimNames.Has(claim.Name) {
|
||||
allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), claim.Name))
|
||||
} else {
|
||||
allErrs = append(allErrs, ValidateDNS1123Label(claim.Name, fldPath.Child("name"))...)
|
||||
podClaimNames.Insert(claim.Name)
|
||||
}
|
||||
allErrs = append(allErrs, validatePodResourceClaimSource(claim.Source, fldPath.Child("source"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validatePodResourceClaimSource(claimSource core.ClaimSource, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if claimSource.ResourceClaimName != nil && claimSource.ResourceClaimTemplateName != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, claimSource, "at most one of `resourceClaimName` or `resourceClaimTemplateName` may be specified"))
|
||||
}
|
||||
if claimSource.ResourceClaimName == nil && claimSource.ResourceClaimTemplateName == nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, claimSource, "must specify one of: `resourceClaimName`, `resourceClaimTemplateName`"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@ -2990,8 +3038,8 @@ func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.Error
|
||||
|
||||
// validateEphemeralContainers is called by pod spec and template validation to validate the list of ephemeral containers.
|
||||
// Note that this is called for pod template even though ephemeral containers aren't allowed in pod templates.
|
||||
func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer, containers, initContainers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer, containers, initContainers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.String, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
if len(ephemeralContainers) == 0 {
|
||||
return allErrs
|
||||
@ -3011,7 +3059,7 @@ func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer,
|
||||
idxPath := fldPath.Index(i)
|
||||
|
||||
c := (*core.Container)(&ec.EphemeralContainerCommon)
|
||||
allErrs = append(allErrs, validateContainerCommon(c, volumes, idxPath, opts)...)
|
||||
allErrs = append(allErrs, validateContainerCommon(c, volumes, podClaimNames, idxPath, opts)...)
|
||||
// Ephemeral containers don't need looser constraints for pod templates, so it's convenient to apply both validations
|
||||
// here where we've already converted EphemeralContainerCommon to Container.
|
||||
allErrs = append(allErrs, validateContainerOnlyForPod(c, idxPath)...)
|
||||
@ -3049,7 +3097,7 @@ func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer,
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateFieldAcceptList checks that only allowed fields are set.
|
||||
// ValidateFieldAcceptList checks that only allowed fields are set.
|
||||
// The value must be a struct (not a pointer to a struct!).
|
||||
func validateFieldAllowList(value interface{}, allowedFields map[string]bool, errorText string, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
@ -3073,7 +3121,7 @@ func validateFieldAllowList(value interface{}, allowedFields map[string]bool, er
|
||||
}
|
||||
|
||||
// validateInitContainers is called by pod spec and template validation to validate the list of init containers
|
||||
func validateInitContainers(containers []core.Container, regularContainers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
func validateInitContainers(containers []core.Container, regularContainers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.String, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
allNames := sets.String{}
|
||||
@ -3084,7 +3132,7 @@ func validateInitContainers(containers []core.Container, regularContainers []cor
|
||||
idxPath := fldPath.Index(i)
|
||||
|
||||
// Apply the validation common to all container types
|
||||
allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, idxPath, opts)...)
|
||||
allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, podClaimNames, idxPath, opts)...)
|
||||
|
||||
// Names must be unique within regular and init containers. Collisions with ephemeral containers
|
||||
// will be detected by validateEphemeralContainers().
|
||||
@ -3117,8 +3165,8 @@ func validateInitContainers(containers []core.Container, regularContainers []cor
|
||||
|
||||
// validateContainerCommon applies validation common to all container types. It's called by regular, init, and ephemeral
|
||||
// container list validation to require a properly formatted name, image, etc.
|
||||
func validateContainerCommon(ctr *core.Container, volumes map[string]core.VolumeSource, path *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
func validateContainerCommon(ctr *core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.String, path *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
|
||||
namePath := path.Child("name")
|
||||
if len(ctr.Name) == 0 {
|
||||
@ -3154,7 +3202,7 @@ func validateContainerCommon(ctr *core.Container, volumes map[string]core.Volume
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, ctr, path.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, path.Child("volumeDevices"))...)
|
||||
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, path.Child("imagePullPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, path.Child("resources"), opts)...)
|
||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, podClaimNames, path.Child("resources"), opts)...)
|
||||
allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, path.Child("securityContext"))...)
|
||||
return allErrs
|
||||
}
|
||||
@ -3207,7 +3255,7 @@ func validateHostUsers(spec *core.PodSpec, fldPath *field.Path) field.ErrorList
|
||||
}
|
||||
|
||||
// validateContainers is called by pod spec and template validation to validate the list of regular containers.
|
||||
func validateContainers(containers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
func validateContainers(containers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.String, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(containers) == 0 {
|
||||
@ -3219,7 +3267,7 @@ func validateContainers(containers []core.Container, volumes map[string]core.Vol
|
||||
path := fldPath.Index(i)
|
||||
|
||||
// Apply validation common to all containers
|
||||
allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, path, opts)...)
|
||||
allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, podClaimNames, path, opts)...)
|
||||
|
||||
// Container names must be unique within the list of regular containers.
|
||||
// Collisions with init or ephemeral container names will be detected by the init or ephemeral
|
||||
@ -3697,9 +3745,11 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
|
||||
|
||||
vols, vErrs := ValidateVolumes(spec.Volumes, podMeta, fldPath.Child("volumes"), opts)
|
||||
allErrs = append(allErrs, vErrs...)
|
||||
allErrs = append(allErrs, validateContainers(spec.Containers, vols, fldPath.Child("containers"), opts)...)
|
||||
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"), opts)...)
|
||||
allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, fldPath.Child("ephemeralContainers"), opts)...)
|
||||
podClaimNames := gatherPodResourceClaimNames(spec.ResourceClaims)
|
||||
allErrs = append(allErrs, validatePodResourceClaims(spec.ResourceClaims, fldPath.Child("resourceClaims"))...)
|
||||
allErrs = append(allErrs, validateContainers(spec.Containers, vols, podClaimNames, fldPath.Child("containers"), opts)...)
|
||||
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, podClaimNames, fldPath.Child("initContainers"), opts)...)
|
||||
allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, podClaimNames, fldPath.Child("ephemeralContainers"), opts)...)
|
||||
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
|
||||
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
||||
@ -5856,7 +5906,7 @@ func validateBasicResource(quantity resource.Quantity, fldPath *field.Path) fiel
|
||||
}
|
||||
|
||||
// Validates resource requirement spec.
|
||||
func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
func ValidateResourceRequirements(requirements *core.ResourceRequirements, podClaimNames sets.String, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
limPath := fldPath.Child("limits")
|
||||
reqPath := fldPath.Child("requests")
|
||||
@ -5919,6 +5969,42 @@ func ValidateResourceRequirements(requirements *core.ResourceRequirements, fldPa
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "HugePages require cpu or memory"))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateResourceClaimNames(requirements.Claims, podClaimNames, fldPath.Child("claims"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateResourceClaimNames checks that the names in
|
||||
// ResourceRequirements.Claims have a corresponding entry in
|
||||
// PodSpec.ResourceClaims.
|
||||
func validateResourceClaimNames(claims []core.ResourceClaim, podClaimNames sets.String, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
names := sets.String{}
|
||||
for i, claim := range claims {
|
||||
name := claim.Name
|
||||
if name == "" {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Index(i), ""))
|
||||
} else {
|
||||
if names.Has(name) {
|
||||
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), name))
|
||||
} else {
|
||||
names.Insert(name)
|
||||
}
|
||||
if !podClaimNames.Has(name) {
|
||||
// field.NotFound doesn't accept an
|
||||
// explanation. Adding one here is more
|
||||
// user-friendly.
|
||||
error := field.NotFound(fldPath.Index(i), name)
|
||||
error.Detail = "must be one of the names in pod.spec.resourceClaims"
|
||||
if len(podClaimNames) == 0 {
|
||||
error.Detail += " which is empty"
|
||||
} else {
|
||||
error.Detail += ": " + strings.Join(podClaimNames.List(), ", ")
|
||||
}
|
||||
allErrs = append(allErrs, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@ -5371,7 +5371,7 @@ func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
|
||||
resource.BinarySI),
|
||||
},
|
||||
}
|
||||
if errs := ValidateResourceRequirements(&containerLimitCase, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 {
|
||||
if errs := ValidateResourceRequirements(&containerLimitCase, nil, field.NewPath("resources"), PodValidationOptions{}); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
@ -6841,7 +6841,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
|
||||
},
|
||||
},
|
||||
} {
|
||||
if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, field.NewPath("ephemeralContainers"), PodValidationOptions{}); len(errs) != 0 {
|
||||
if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{}); len(errs) != 0 {
|
||||
t.Errorf("expected success for '%s' but got errors: %v", title, errs)
|
||||
}
|
||||
}
|
||||
@ -7123,7 +7123,7 @@ func TestValidateEphemeralContainers(t *testing.T) {
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
|
||||
errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, field.NewPath("ephemeralContainers"), PodValidationOptions{})
|
||||
errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, nil, field.NewPath("ephemeralContainers"), PodValidationOptions{})
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error but received none")
|
||||
}
|
||||
@ -7402,7 +7402,7 @@ func TestValidateContainers(t *testing.T) {
|
||||
TerminationMessagePolicy: "File",
|
||||
},
|
||||
}
|
||||
if errs := validateContainers(successCase, volumeDevices, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
|
||||
if errs := validateContainers(successCase, volumeDevices, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
@ -8026,7 +8026,7 @@ func TestValidateContainers(t *testing.T) {
|
||||
}
|
||||
for _, tc := range errorCases {
|
||||
t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
|
||||
errs := validateContainers(tc.containers, volumeDevices, field.NewPath("containers"), PodValidationOptions{})
|
||||
errs := validateContainers(tc.containers, volumeDevices, nil, field.NewPath("containers"), PodValidationOptions{})
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error but received none")
|
||||
}
|
||||
@ -8076,7 +8076,7 @@ func TestValidateInitContainers(t *testing.T) {
|
||||
TerminationMessagePolicy: "File",
|
||||
},
|
||||
}
|
||||
if errs := validateInitContainers(successCase, containers, volumeDevices, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
|
||||
if errs := validateInitContainers(successCase, containers, volumeDevices, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
@ -8268,7 +8268,7 @@ func TestValidateInitContainers(t *testing.T) {
|
||||
}
|
||||
for _, tc := range errorCases {
|
||||
t.Run(tc.title+"__@L"+tc.line, func(t *testing.T) {
|
||||
errs := validateInitContainers(tc.initContainers, containers, volumeDevices, field.NewPath("initContainers"), PodValidationOptions{})
|
||||
errs := validateInitContainers(tc.initContainers, containers, volumeDevices, nil, field.NewPath("initContainers"), PodValidationOptions{})
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("expected error but received none")
|
||||
}
|
||||
@ -18611,6 +18611,9 @@ func TestValidateOSFields(t *testing.T) {
|
||||
"Priority",
|
||||
"PriorityClassName",
|
||||
"ReadinessGates",
|
||||
"ResourceClaims[*].Name",
|
||||
"ResourceClaims[*].Source.ResourceClaimName",
|
||||
"ResourceClaims[*].Source.ResourceClaimTemplateName",
|
||||
"RestartPolicy",
|
||||
"RuntimeClassName",
|
||||
"SchedulerName",
|
||||
@ -20914,7 +20917,7 @@ func TestValidateResourceRequirements(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if errs := ValidateResourceRequirements(&tc.requirements, path, tc.opts); len(errs) != 0 {
|
||||
if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) != 0 {
|
||||
t.Errorf("unexpected errors: %v", errs)
|
||||
}
|
||||
})
|
||||
@ -20941,7 +20944,7 @@ func TestValidateResourceRequirements(t *testing.T) {
|
||||
|
||||
for _, tc := range errTests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if errs := ValidateResourceRequirements(&tc.requirements, path, tc.opts); len(errs) == 0 {
|
||||
if errs := ValidateResourceRequirements(&tc.requirements, nil, path, tc.opts); len(errs) == 0 {
|
||||
t.Error("expected errors")
|
||||
}
|
||||
})
|
||||
@ -21681,3 +21684,220 @@ func TestValidatePVSecretReference(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDynamicResourceAllocation(t *testing.T) {
|
||||
externalClaimName := "some-claim"
|
||||
externalClaimTemplateName := "some-claim-template"
|
||||
goodClaimSource := core.ClaimSource{
|
||||
ResourceClaimName: &externalClaimName,
|
||||
}
|
||||
|
||||
successCases := map[string]core.PodSpec{
|
||||
"resource claim reference": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: core.ClaimSource{
|
||||
ResourceClaimName: &externalClaimName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"resource claim template": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: core.ClaimSource{
|
||||
ResourceClaimTemplateName: &externalClaimTemplateName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"multiple claims": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "another-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
{
|
||||
Name: "another-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"init container": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
InitContainers: []core.Container{{Name: "ctr-init", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range successCases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
failureCases := map[string]core.PodSpec{
|
||||
"pod claim name with prefix": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "../my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"pod claim name with path": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my/claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"pod claim name empty": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"duplicate pod claim entries": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"resource claim source empty": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: core.ClaimSource{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"resource claim reference and template": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: core.ClaimSource{
|
||||
ResourceClaimName: &externalClaimName,
|
||||
ResourceClaimTemplateName: &externalClaimTemplateName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"claim not found": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "no-such-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"claim name empty": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: ""}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"pod claim name duplicates": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}, {Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"no claims defined": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
},
|
||||
"duplicate pod claim name": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
"ephemeral container don't support resource requirements": {
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}},
|
||||
EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr-ephemeral", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", Resources: core.ResourceRequirements{Claims: []core.ResourceClaim{{Name: "my-claim"}}}}, TargetContainerName: "ctr"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
ResourceClaims: []core.PodResourceClaim{
|
||||
{
|
||||
Name: "my-claim",
|
||||
Source: goodClaimSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range failureCases {
|
||||
if errs := ValidatePodSpec(&v, nil, field.NewPath("field"), PodValidationOptions{}); len(errs) == 0 {
|
||||
t.Errorf("expected failure for %q", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func ValidateRuntimeClassUpdate(new, old *node.RuntimeClass) field.ErrorList {
|
||||
|
||||
func validateOverhead(overhead *node.Overhead, fldPath *field.Path) field.ErrorList {
|
||||
// reuse the ResourceRequirements validation logic
|
||||
return corevalidation.ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead.PodFixed}, fldPath,
|
||||
return corevalidation.ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead.PodFixed}, nil, fldPath,
|
||||
corevalidation.PodValidationOptions{})
|
||||
}
|
||||
|
||||
|
@ -2314,6 +2314,26 @@ type ResourceRequirements struct {
|
||||
// More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
|
||||
// +optional
|
||||
Requests ResourceList `json:"requests,omitempty" protobuf:"bytes,2,rep,name=requests,casttype=ResourceList,castkey=ResourceName"`
|
||||
// Claims lists the names of resources, defined in spec.resourceClaims,
|
||||
// that are used by this container.
|
||||
//
|
||||
// This is an alpha field and requires enabling the
|
||||
// DynamicResourceAllocation feature gate.
|
||||
//
|
||||
// This field is immutable.
|
||||
//
|
||||
// +listType=set
|
||||
// +featureGate=DynamicResourceAllocation
|
||||
// +optional
|
||||
Claims []ResourceClaim `json:"claims,omitempty" protobuf:"bytes,3,opt,name=claims"`
|
||||
}
|
||||
|
||||
// ResourceClaim references one entry in PodSpec.ResourceClaims.
|
||||
type ResourceClaim struct {
|
||||
// Name must match the name of one entry in pod.spec.resourceClaims of
|
||||
// the Pod where this field is used. It makes that resource available
|
||||
// inside a container.
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
}
|
||||
|
||||
const (
|
||||
@ -3347,6 +3367,7 @@ type PodSpec struct {
|
||||
// - spec.containers[*].securityContext.runAsGroup
|
||||
// +optional
|
||||
OS *PodOS `json:"os,omitempty" protobuf:"bytes,36,opt,name=os"`
|
||||
|
||||
// Use the host's user namespace.
|
||||
// Optional: Default to true.
|
||||
// If set to true or not present, the pod will be run in the host user namespace, useful
|
||||
@ -3359,6 +3380,7 @@ type PodSpec struct {
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
HostUsers *bool `json:"hostUsers,omitempty" protobuf:"bytes,37,opt,name=hostUsers"`
|
||||
|
||||
// SchedulingGates is an opaque list of values that if specified will block scheduling the pod.
|
||||
// More info: https://git.k8s.io/enhancements/keps/sig-scheduling/3521-pod-scheduling-readiness.
|
||||
//
|
||||
@ -3369,6 +3391,65 @@ type PodSpec struct {
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
SchedulingGates []PodSchedulingGate `json:"schedulingGates,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,38,opt,name=schedulingGates"`
|
||||
// ResourceClaims defines which ResourceClaims must be allocated
|
||||
// and reserved before the Pod is allowed to start. The resources
|
||||
// will be made available to those containers which consume them
|
||||
// by name.
|
||||
//
|
||||
// This is an alpha field and requires enabling the
|
||||
// DynamicResourceAllocation feature gate.
|
||||
//
|
||||
// This field is immutable.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge,retainKeys
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=DynamicResourceAllocation
|
||||
// +optional
|
||||
ResourceClaims []PodResourceClaim `json:"resourceClaims,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,39,rep,name=resourceClaims"`
|
||||
}
|
||||
|
||||
// PodResourceClaim references exactly one ResourceClaim through a ClaimSource.
|
||||
// It adds a name to it that uniquely identifies the ResourceClaim inside the Pod.
|
||||
// Containers that need access to the ResourceClaim reference it with this name.
|
||||
type PodResourceClaim struct {
|
||||
// Name uniquely identifies this resource claim inside the pod.
|
||||
// This must be a DNS_LABEL.
|
||||
Name string `json:"name" protobuf:"bytes,1,name=name"`
|
||||
|
||||
// Source describes where to find the ResourceClaim.
|
||||
Source ClaimSource `json:"source,omitempty" protobuf:"bytes,2,name=source"`
|
||||
}
|
||||
|
||||
// ClaimSource describes a reference to a ResourceClaim.
|
||||
//
|
||||
// Exactly one of these fields should be set. Consumers of this type must
|
||||
// treat an empty object as if it has an unknown value.
|
||||
type ClaimSource struct {
|
||||
// ResourceClaimName is the name of a ResourceClaim object in the same
|
||||
// namespace as this pod.
|
||||
ResourceClaimName *string `json:"resourceClaimName,omitempty" protobuf:"bytes,1,opt,name=resourceClaimName"`
|
||||
|
||||
// ResourceClaimTemplateName is the name of a ResourceClaimTemplate
|
||||
// object in the same namespace as this pod.
|
||||
//
|
||||
// The template will be used to create a new ResourceClaim, which will
|
||||
// be bound to this pod. When this pod is deleted, the ResourceClaim
|
||||
// will also be deleted. The name of the ResourceClaim will be <pod
|
||||
// name>-<resource name>, where <resource name> is the
|
||||
// PodResourceClaim.Name. Pod validation will reject the pod if the
|
||||
// concatenated name is not valid for a ResourceClaim (e.g. too long).
|
||||
//
|
||||
// An existing ResourceClaim with that name that is not owned by the
|
||||
// pod will not be used for the pod to avoid using an unrelated
|
||||
// resource by mistake. Scheduling and pod startup are then blocked
|
||||
// until the unrelated ResourceClaim is removed.
|
||||
//
|
||||
// This field is immutable and no changes will be made to the
|
||||
// corresponding ResourceClaim by the control plane after creating the
|
||||
// ResourceClaim.
|
||||
ResourceClaimTemplateName *string `json:"resourceClaimTemplateName,omitempty" protobuf:"bytes,2,opt,name=resourceClaimTemplateName"`
|
||||
}
|
||||
|
||||
// OSName is the set of OS'es that can be used in OS.
|
||||
|
Loading…
Reference in New Issue
Block a user