If ResourceRequirements changed, always mark a proposed resize

This commit is contained in:
Tim Allclair 2024-11-01 13:57:02 -07:00
parent bf4f1832e2
commit 99dcf07e21
2 changed files with 199 additions and 370 deletions

View File

@ -1297,26 +1297,17 @@ func MarkPodProposedForResize(oldPod, newPod *api.Pod) {
} }
for i, c := range newPod.Spec.Containers { for i, c := range newPod.Spec.Containers {
if c.Name != oldPod.Spec.Containers[i].Name {
return // Update is invalid (container mismatch): let validation handle it.
}
if c.Resources.Requests == nil { if c.Resources.Requests == nil {
continue continue
} }
if cmp.Equal(oldPod.Spec.Containers[i].Resources, c.Resources) { if cmp.Equal(oldPod.Spec.Containers[i].Resources, c.Resources) {
continue continue
} }
findContainerStatus := func(css []api.ContainerStatus, cName string) (api.ContainerStatus, bool) { newPod.Status.Resize = api.PodResizeStatusProposed
for i := range css { return
if css[i].Name == cName {
return css[i], true
}
}
return api.ContainerStatus{}, false
}
if cs, ok := findContainerStatus(newPod.Status.ContainerStatuses, c.Name); ok {
if !cmp.Equal(c.Resources.Requests, cs.AllocatedResources) {
newPod.Status.Resize = api.PodResizeStatusProposed
break
}
}
} }
} }

View File

@ -2806,439 +2806,277 @@ func TestDropSidecarContainers(t *testing.T) {
func TestMarkPodProposedForResize(t *testing.T) { func TestMarkPodProposedForResize(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
newPod *api.Pod newPodSpec api.PodSpec
oldPod *api.Pod oldPodSpec api.PodSpec
expectedPod *api.Pod expectProposedResize bool
}{ }{
{ {
desc: "nil requests", desc: "nil requests",
newPod: &api.Pod{ newPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image",
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
},
}, },
}, },
}, },
oldPod: &api.Pod{ oldPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image",
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
},
},
},
},
expectedPod: &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "c1",
Image: "image",
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
},
}, },
}, },
}, },
expectProposedResize: false,
}, },
{ {
desc: "resources unchanged", desc: "resources unchanged",
newPod: &api.Pod{ newPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
}, },
}, },
}, },
}, },
oldPod: &api.Pod{ oldPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
},
},
},
},
expectedPod: &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "c1",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
}, },
}, },
}, },
}, },
expectProposedResize: false,
}, },
{ {
desc: "resize desired", desc: "requests resized",
newPod: &api.Pod{ newPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
{
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
},
}, },
}, },
}, {
Status: api.PodStatus{ Name: "c2",
ContainerStatuses: []api.ContainerStatus{ Image: "image",
{ Resources: api.ResourceRequirements{
Name: "c1", Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Image: "image", Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
{
Name: "c2",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
}, },
}, },
}, },
}, },
oldPod: &api.Pod{ oldPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
{
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
},
}, },
}, },
}, {
Status: api.PodStatus{ Name: "c2",
ContainerStatuses: []api.ContainerStatus{ Image: "image",
{ Resources: api.ResourceRequirements{
Name: "c1", Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Image: "image", Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
{
Name: "c2",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
}, },
}, },
}, },
}, },
expectedPod: &api.Pod{ expectProposedResize: true,
Spec: api.PodSpec{ },
Containers: []api.Container{ {
{ desc: "limits resized",
Name: "c1", newPodSpec: api.PodSpec{
Image: "image", Containers: []api.Container{
Resources: api.ResourceRequirements{ {
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Name: "c1",
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, Image: "image",
}, Resources: api.ResourceRequirements{
}, Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
{ Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
},
}, },
}, },
}, {
Status: api.PodStatus{ Name: "c2",
Resize: api.PodResizeStatusProposed, Image: "image",
ContainerStatuses: []api.ContainerStatus{ Resources: api.ResourceRequirements{
{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Name: "c1", Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
{
Name: "c2",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
}, },
}, },
}, },
}, },
oldPodSpec: api.PodSpec{
Containers: []api.Container{
{
Name: "c1",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
{
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("500m")},
},
},
},
},
expectProposedResize: true,
}, },
{ {
desc: "the number of containers in the pod has increased; no action should be taken.", desc: "the number of containers in the pod has increased; no action should be taken.",
newPod: &api.Pod{ newPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
{
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
},
}, },
}, },
}, {
Status: api.PodStatus{ Name: "c2",
ContainerStatuses: []api.ContainerStatus{ Image: "image",
{ Resources: api.ResourceRequirements{
Name: "c1", Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Image: "image", Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
{
Name: "c2",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
}, },
}, },
}, },
}, },
oldPod: &api.Pod{ oldPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
},
},
},
expectedPod: &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "c1",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
{
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
{
Name: "c2",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
}, },
}, },
}, },
}, },
expectProposedResize: false,
}, },
{ {
desc: "the number of containers in the pod has decreased; no action should be taken.", desc: "the number of containers in the pod has decreased; no action should be taken.",
newPod: &api.Pod{ newPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
},
},
Status: api.PodStatus{
ContainerStatuses: []api.ContainerStatus{
{
Name: "c1",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
}, },
}, },
}, },
}, },
oldPod: &api.Pod{ oldPodSpec: api.PodSpec{
Spec: api.PodSpec{ Containers: []api.Container{
Containers: []api.Container{ {
{ Name: "c1",
Name: "c1", Image: "image",
Image: "image", Resources: api.ResourceRequirements{
Resources: api.ResourceRequirements{ Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
{
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
},
}, },
}, },
}, {
Status: api.PodStatus{ Name: "c2",
ContainerStatuses: []api.ContainerStatus{ Image: "image",
{ Resources: api.ResourceRequirements{
Name: "c1", Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
Image: "image", Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
{
Name: "c2",
Image: "image",
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
}, },
}, },
}, },
}, },
expectedPod: &api.Pod{ expectProposedResize: false,
Spec: api.PodSpec{ },
Containers: []api.Container{ {
{ desc: "containers reordered",
Name: "c1", newPodSpec: api.PodSpec{
Image: "image", Containers: []api.Container{
Resources: api.ResourceRequirements{ {
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, Name: "c1",
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, Image: "image",
}, Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
}, },
}, },
}, {
Status: api.PodStatus{ Name: "c2",
ContainerStatuses: []api.ContainerStatus{ Image: "image",
{ Resources: api.ResourceRequirements{
Name: "c1", Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Image: "image", Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
AllocatedResources: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
}, },
}, },
}, },
}, },
oldPodSpec: api.PodSpec{
Containers: []api.Container{
{
Name: "c2",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
},
{
Name: "c1",
Image: "image",
Resources: api.ResourceRequirements{
Requests: api.ResourceList{api.ResourceCPU: resource.MustParse("300m")},
Limits: api.ResourceList{api.ResourceCPU: resource.MustParse("400m")},
},
},
},
},
expectProposedResize: false,
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
MarkPodProposedForResize(tc.oldPod, tc.newPod) newPod := &api.Pod{Spec: tc.newPodSpec}
if diff := cmp.Diff(tc.expectedPod, tc.newPod); diff != "" { newPodUnchanged := newPod.DeepCopy()
t.Errorf("unexpected pod spec (-want, +got):\n%s", diff) oldPod := &api.Pod{Spec: tc.oldPodSpec}
MarkPodProposedForResize(oldPod, newPod)
if tc.expectProposedResize {
assert.Equal(t, api.PodResizeStatusProposed, newPod.Status.Resize)
} else {
assert.Equal(t, api.PodResizeStatus(""), newPod.Status.Resize)
} }
newPod.Status.Resize = newPodUnchanged.Status.Resize // Only field that might have changed.
assert.Equal(t, newPodUnchanged, newPod, "No fields other than .status.resize should be modified")
}) })
} }
} }