reset fields when the feature gate was not set

This commit is contained in:
Sergey Kanzhelev 2024-07-22 05:21:23 +00:00
parent 2253b53b58
commit 3790ee2fe8
3 changed files with 312 additions and 0 deletions

View File

@ -813,6 +813,17 @@ func dropDisabledPodStatusFields(podStatus, oldPodStatus *api.PodStatus, podSpec
}
}
if !utilfeature.DefaultFeatureGate.Enabled(features.ResourceHealthStatus) {
setAllocatedResourcesStatusToNil := func(csl []api.ContainerStatus) {
for i := range csl {
csl[i].AllocatedResourcesStatus = nil
}
}
setAllocatedResourcesStatusToNil(podStatus.ContainerStatuses)
setAllocatedResourcesStatusToNil(podStatus.InitContainerStatuses)
setAllocatedResourcesStatusToNil(podStatus.EphemeralContainerStatuses)
}
// drop ContainerStatus.User field to empty (disable SupplementalGroupsPolicy)
if !utilfeature.DefaultFeatureGate.Enabled(features.SupplementalGroupsPolicy) && !supplementalGroupsPolicyInUse(oldPodSpec) {
dropUserField := func(csl []api.ContainerStatus) {
@ -1161,6 +1172,22 @@ func rroInUse(podSpec *api.PodSpec) bool {
return inUse
}
func allocatedResourcesStatusInUse(podSpec *api.PodStatus) bool {
if podSpec == nil {
return false
}
inUse := func(csl []api.ContainerStatus) bool {
for _, cs := range csl {
if len(cs.AllocatedResourcesStatus) > 0 {
return true
}
}
return false
}
return inUse(podSpec.ContainerStatuses) || inUse(podSpec.InitContainerStatuses) || inUse(podSpec.EphemeralContainerStatuses)
}
func dropDisabledClusterTrustBundleProjection(podSpec, oldPodSpec *api.PodSpec) {
if utilfeature.DefaultFeatureGate.Enabled(features.ClusterTrustBundleProjection) {
return

View File

@ -5378,6 +5378,11 @@ func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions
allErrs = append(allErrs, validateContainerStatusUsers(newPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), newPod.Spec.OS)...)
allErrs = append(allErrs, validateContainerStatusUsers(newPod.Status.EphemeralContainerStatuses, fldPath.Child("ephemeralContainerStatuses"), newPod.Spec.OS)...)
allErrs = append(allErrs, validateContainerStatusAllocatedResourcesStatus(newPod.Status.ContainerStatuses, fldPath.Child("containerStatuses"), newPod.Spec.Containers)...)
allErrs = append(allErrs, validateContainerStatusAllocatedResourcesStatus(newPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), newPod.Spec.InitContainers)...)
// ephemeral containers are not allowed to have resources allocated
allErrs = append(allErrs, validateContainerStatusNoAllocatedResourcesStatus(newPod.Status.EphemeralContainerStatuses, fldPath.Child("ephemeralContainerStatuses"))...)
return allErrs
}
@ -8200,6 +8205,80 @@ func validateContainerStatusUsers(containerStatuses []core.ContainerStatus, fldP
return allErrors
}
func validateContainerStatusNoAllocatedResourcesStatus(containerStatuses []core.ContainerStatus, fldPath *field.Path) field.ErrorList {
allErrors := field.ErrorList{}
for i, containerStatus := range containerStatuses {
if containerStatus.AllocatedResourcesStatus == nil {
continue
} else {
allErrors = append(allErrors, field.Forbidden(fldPath.Index(i).Child("allocatedResourcesStatus"), "cannot be set for a container status"))
}
}
return allErrors
}
// validateContainerStatusAllocatedResourcesStatus iterate the allocated resources health and validate:
// - resourceName matches one of resources in container's resource requirements
// - resourceID is not empty and unique
func validateContainerStatusAllocatedResourcesStatus(containerStatuses []core.ContainerStatus, fldPath *field.Path, containers []core.Container) field.ErrorList {
allErrors := field.ErrorList{}
for i, containerStatus := range containerStatuses {
if containerStatus.AllocatedResourcesStatus == nil {
continue
}
allocatedResources := containerStatus.AllocatedResourcesStatus
for j, allocatedResource := range allocatedResources {
var container core.Container
containerFound := false
// get container by name
for _, c := range containers {
if c.Name == containerStatus.Name {
containerFound = true
container = c
break
}
}
// ignore missing container, see https://github.com/kubernetes/kubernetes/issues/124915
if containerFound {
found := false
// get container resources from the spec
containerResources := container.Resources
for resourceName := range containerResources.Requests {
if resourceName == allocatedResource.Name {
found = true
break
}
}
if !found {
allErrors = append(allErrors, field.Invalid(fldPath.Index(i).Child("allocatedResourcesStatus").Index(j).Child("name"), allocatedResource.Name, "must match one of the container's resource requirements"))
}
}
uniqueResources := sets.New[core.ResourceID]()
// check resource IDs are unique
for k, r := range allocatedResource.Resources {
if r.Health != core.ResourceHealthStatusHealthy && r.Health != core.ResourceHealthStatusUnhealthy && r.Health != core.ResourceHealthStatusUnknown {
allErrors = append(allErrors, field.Invalid(fldPath.Index(i).Child("allocatedResourcesStatus").Index(j).Child("resources").Index(k).Child("health"), r.Health, "must be one of Healthy, Unhealthy, Unknown"))
}
if uniqueResources.Has(r.ResourceID) {
allErrors = append(allErrors, field.Invalid(fldPath.Index(i).Child("allocatedResourcesStatus").Index(j).Child("resources").Index(k).Child("resourceID"), r.ResourceID, "must be unique"))
} else {
uniqueResources.Insert(r.ResourceID)
}
}
}
}
return allErrors
}
func validateLinuxContainerUser(linuxContainerUser *core.LinuxContainerUser, fldPath *field.Path) field.ErrorList {
allErrors := field.ErrorList{}
if linuxContainerUser == nil {

View File

@ -24423,3 +24423,209 @@ func TestValidatePodStatusUpdateWithSupplementalGroupsPolicy(t *testing.T) {
}
}
}
func TestValidateContainerStatusNoAllocatedResourcesStatus(t *testing.T) {
containerStatuses := []core.ContainerStatus{
{
Name: "container-1",
},
{
Name: "container-2",
AllocatedResourcesStatus: []core.ResourceStatus{
{
Name: "test.device/test",
Resources: nil,
},
},
},
{
Name: "container-3",
AllocatedResourcesStatus: []core.ResourceStatus{
{
Name: "test.device/test",
Resources: []core.ResourceHealth{
{
ResourceID: "resource-1",
Health: core.ResourceHealthStatusHealthy,
},
},
},
},
},
}
fldPath := field.NewPath("spec", "containers")
errs := validateContainerStatusNoAllocatedResourcesStatus(containerStatuses, fldPath)
assert.Equal(t, 2, len(errs))
assert.Equal(t, "spec.containers[1].allocatedResourcesStatus", errs[0].Field)
assert.Equal(t, "cannot be set for a container status", errs[0].Detail)
assert.Equal(t, "spec.containers[2].allocatedResourcesStatus", errs[1].Field)
assert.Equal(t, "cannot be set for a container status", errs[1].Detail)
}
func TestValidateContainerStatusAllocatedResourcesStatus(t *testing.T) {
fldPath := field.NewPath("spec", "containers")
testCases := map[string]struct {
containers []core.Container
containerStatuses []core.ContainerStatus
wantFieldErrors field.ErrorList
}{
"basic correct status": {
containers: []core.Container{
{
Name: "container-1",
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
"test.device/test": resource.MustParse("1"),
},
},
},
},
containerStatuses: []core.ContainerStatus{
{
Name: "container-1",
AllocatedResourcesStatus: []core.ResourceStatus{
{
Name: "test.device/test",
Resources: []core.ResourceHealth{
{
ResourceID: "resource-1",
Health: core.ResourceHealthStatusHealthy,
},
},
},
},
},
},
wantFieldErrors: field.ErrorList{},
},
"ignoring the missing container (see https://github.com/kubernetes/kubernetes/issues/124915)": {
containers: []core.Container{
{
Name: "container-2",
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
"test.device/test": resource.MustParse("1"),
},
},
},
},
containerStatuses: []core.ContainerStatus{
{
Name: "container-1",
AllocatedResourcesStatus: []core.ResourceStatus{
{
Name: "test.device/test",
Resources: []core.ResourceHealth{
{
ResourceID: "resource-1",
Health: core.ResourceHealthStatusHealthy,
},
},
},
},
},
},
wantFieldErrors: field.ErrorList{},
},
"allow nil": {
containers: []core.Container{
{
Name: "container-2",
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
"test.device/test": resource.MustParse("1"),
},
},
},
},
containerStatuses: []core.ContainerStatus{
{
Name: "container-1",
},
},
wantFieldErrors: field.ErrorList{},
},
"don't allow non-unique IDs": {
containers: []core.Container{
{
Name: "container-2",
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
"test.device/test": resource.MustParse("1"),
},
},
},
},
containerStatuses: []core.ContainerStatus{
{
Name: "container-1",
AllocatedResourcesStatus: []core.ResourceStatus{
{
Name: "test.device/test",
Resources: []core.ResourceHealth{
{
ResourceID: "resource-1",
Health: core.ResourceHealthStatusHealthy,
},
{
ResourceID: "resource-1",
Health: core.ResourceHealthStatusUnhealthy,
},
},
},
},
},
},
wantFieldErrors: field.ErrorList{
field.Invalid(fldPath.Index(0).Child("allocatedResourcesStatus").Index(0).Child("resources").Index(1).Child("resourceID"), core.ResourceID("resource-1"), "must be unique"),
},
},
"don't allow resources that are not in spec": {
containers: []core.Container{
{
Name: "container-1",
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
"test.device/test": resource.MustParse("1"),
},
},
},
},
containerStatuses: []core.ContainerStatus{
{
Name: "container-1",
AllocatedResourcesStatus: []core.ResourceStatus{
{
Name: "test.device/test",
Resources: []core.ResourceHealth{
{
ResourceID: "resource-1",
Health: core.ResourceHealthStatusHealthy,
},
},
},
{
Name: "test.device/test2",
Resources: []core.ResourceHealth{},
},
},
},
},
wantFieldErrors: field.ErrorList{
field.Invalid(fldPath.Index(0).Child("allocatedResourcesStatus").Index(1).Child("name"), core.ResourceName("test.device/test2"), "must match one of the container's resource requirements"),
},
},
}
for name, tt := range testCases {
t.Run(name, func(t *testing.T) {
errs := validateContainerStatusAllocatedResourcesStatus(tt.containerStatuses, fldPath, tt.containers)
if diff := cmp.Diff(tt.wantFieldErrors, errs); diff != "" {
t.Errorf("unexpected field errors (-want, +got):\n%s", diff)
}
})
}
}