Skip to content

Commit

Permalink
api: add resource claims to core API
Browse files Browse the repository at this point in the history
The resource.k8s.io/ClaimTemplate only gets referenced by name, therefore the
changes to the core API are limited.
  • Loading branch information
pohly committed Nov 10, 2022
1 parent 155d498 commit 7d11b42
Show file tree
Hide file tree
Showing 7 changed files with 687 additions and 30 deletions.
36 changes: 36 additions & 0 deletions pkg/api/pod/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
159 changes: 159 additions & 0 deletions pkg/api/pod/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
75 changes: 75 additions & 0 deletions pkg/apis/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 7d11b42

Please sign in to comment.