Merge pull request #133390 from SergeyKanzhelev/automated-cherry-pick-of-#132040-upstream-release-1.33

Automated cherry pick of #132040: Make nodeports scheduling plugin restartable initContainer aware
This commit is contained in:
Kubernetes Prow Robot
2025-10-10 14:31:01 -07:00
committed by GitHub
9 changed files with 1084 additions and 157 deletions

View File

@@ -50,9 +50,9 @@ const (
ErrReason = "node(s) didn't have free ports for the requested pod ports"
)
type preFilterState []*v1.ContainerPort
type preFilterState []v1.ContainerPort
// Clone the prefilter state.
// Clone the prefilter state.getContainerPorts(pod)
func (s preFilterState) Clone() framework.StateData {
// The state is not impacted by adding/removing existing pods, hence we don't need to make a deep copy.
return s
@@ -63,28 +63,9 @@ func (pl *NodePorts) Name() string {
return Name
}
// getContainerPorts returns the used host ports of Pods: if 'port' was used, a 'port:true' pair
// will be in the result; but it does not resolve port conflict.
func getContainerPorts(pods ...*v1.Pod) []*v1.ContainerPort {
ports := []*v1.ContainerPort{}
for _, pod := range pods {
for j := range pod.Spec.Containers {
container := &pod.Spec.Containers[j]
for k := range container.Ports {
// Only return ports with a host port specified.
if container.Ports[k].HostPort <= 0 {
continue
}
ports = append(ports, &container.Ports[k])
}
}
}
return ports
}
// PreFilter invoked at the prefilter extension point.
func (pl *NodePorts) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
s := getContainerPorts(pod)
s := util.GetHostPorts(pod)
// Skip if a pod has no ports.
if len(s) == 0 {
return nil, framework.NewStatus(framework.Skip)
@@ -148,24 +129,19 @@ func (pl *NodePorts) isSchedulableAfterPodDeleted(logger klog.Logger, pod *v1.Po
return framework.QueueSkip, nil
}
// Get the used host ports of the deleted pod.
usedPorts := make(framework.HostPortInfo)
for _, container := range deletedPod.Spec.Containers {
for _, podPort := range container.Ports {
if podPort.HostPort > 0 {
usedPorts.Add(podPort.HostIP, string(podPort.Protocol), podPort.HostPort)
}
}
}
// If the deleted pod doesn't use any host ports, it doesn't make the target pod schedulable.
if len(usedPorts) == 0 {
ports := util.GetHostPorts(deletedPod)
if len(ports) == 0 {
return framework.QueueSkip, nil
}
// Construct a fake NodeInfo that only has the deleted Pod.
// If we can schedule `pod` to this fake node, it means that `pod` and the deleted pod don't have any common port(s).
// So, deleting that pod couldn't make `pod` schedulable.
usedPorts := make(framework.HostPortInfo, len(ports))
for _, p := range ports {
usedPorts.Add(p.HostIP, string(p.Protocol), p.HostPort)
}
nodeInfo := framework.NodeInfo{UsedPorts: usedPorts}
if Fits(pod, &nodeInfo) {
logger.V(4).Info("the deleted pod and the target pod don't have any common port(s), returning QueueSkip as deleting this Pod won't make the Pod schedulable", "pod", klog.KObj(pod), "deletedPod", klog.KObj(deletedPod))
@@ -193,10 +169,10 @@ func (pl *NodePorts) Filter(ctx context.Context, cycleState *framework.CycleStat
// Fits checks if the pod fits the node.
func Fits(pod *v1.Pod, nodeInfo *framework.NodeInfo) bool {
return fitsPorts(getContainerPorts(pod), nodeInfo)
return fitsPorts(util.GetHostPorts(pod), nodeInfo)
}
func fitsPorts(wantPorts []*v1.ContainerPort, nodeInfo *framework.NodeInfo) bool {
func fitsPorts(wantPorts []v1.ContainerPort, nodeInfo *framework.NodeInfo) bool {
// try to see whether existingPorts and wantPorts will conflict or not
existingPorts := nodeInfo.UsedPorts
for _, cp := range wantPorts {

View File

@@ -17,7 +17,6 @@ limitations under the License.
package nodeports
import (
"fmt"
"strconv"
"strings"
"testing"
@@ -142,6 +141,34 @@ func TestNodePorts(t *testing.T) {
name: "UDP hostPort conflict due to 0.0.0.0 hostIP",
wantFilterStatus: framework.NewStatus(framework.Unschedulable, ErrReason),
},
{
pod: st.MakePod().
InitContainerPort(false /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
}).Obj(),
nodeInfo: framework.NewNodeInfo(
newPod("m1", "TCP/0.0.0.0/8001")),
name: "non-sidecar initContainer using hostPort",
wantPreFilterStatus: framework.NewStatus(framework.Skip),
},
{
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
}).Obj(),
nodeInfo: framework.NewNodeInfo(
newPod("m1", "TCP/0.0.0.0/8001")),
name: "TCP hostPort conflict from sidecar initContainer",
wantFilterStatus: framework.NewStatus(framework.Unschedulable, ErrReason),
},
}
for _, test := range tests {
@@ -188,120 +215,6 @@ func TestPreFilterDisabled(t *testing.T) {
}
}
func TestGetContainerPorts(t *testing.T) {
tests := []struct {
pod1 *v1.Pod
pod2 *v1.Pod
expected []*v1.ContainerPort
}{
{
pod1: st.MakePod().ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8003,
HostPort: 8003,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8004,
HostPort: 8004,
Protocol: v1.ProtocolTCP,
}}).ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8005,
Protocol: v1.ProtocolTCP,
},
}).Obj(),
pod2: st.MakePod().ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8011,
HostPort: 8011,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8012,
HostPort: 8012,
Protocol: v1.ProtocolTCP,
}}).ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8013,
HostPort: 8013,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8014,
HostPort: 8014,
Protocol: v1.ProtocolTCP,
}}).ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8015,
Protocol: v1.ProtocolTCP,
},
}).Obj(),
expected: []*v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8003,
HostPort: 8003,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8004,
HostPort: 8004,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8011,
HostPort: 8011,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8012,
HostPort: 8012,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8013,
HostPort: 8013,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8014,
HostPort: 8014,
Protocol: v1.ProtocolTCP,
},
},
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) {
result := getContainerPorts(test.pod1, test.pod2)
if diff := cmp.Diff(test.expected, result); diff != "" {
t.Errorf("container ports: container ports does not match (-want,+got): %s", diff)
}
})
}
}
func Test_isSchedulableAfterPodDeleted(t *testing.T) {
podWithHostPort := st.MakePod().HostPort(8080)

View File

@@ -1164,13 +1164,11 @@ func (pi *PodInfo) calculateResource() podResource {
// updateUsedPorts updates the UsedPorts of NodeInfo.
func (n *NodeInfo) updateUsedPorts(pod *v1.Pod, add bool) {
for _, container := range pod.Spec.Containers {
for _, podPort := range container.Ports {
if add {
n.UsedPorts.Add(podPort.HostIP, string(podPort.Protocol), podPort.HostPort)
} else {
n.UsedPorts.Remove(podPort.HostIP, string(podPort.Protocol), podPort.HostPort)
}
for _, port := range schedutil.GetHostPorts(pod) {
if add {
n.UsedPorts.Add(port.HostIP, string(port.Protocol), port.HostPort)
} else {
n.UsedPorts.Remove(port.HostIP, string(port.Protocol), port.HostPort)
}
}
}

View File

@@ -2160,3 +2160,353 @@ func TestNodeInfoKMetadata(t *testing.T) {
tCtx.Fatalf("unexpected output:\n%s", output)
}
}
func TestUpdateUsedPorts_PodAdd(t *testing.T) {
testCases := []struct {
ports HostPortInfo
pod *v1.Pod
want HostPortInfo
}{
{
ports: HostPortInfo{},
pod: nil,
want: HostPortInfo{},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: nil,
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{},
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{},
},
{
ports: HostPortInfo{},
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
ProtocolPort{"TCP", 8002}: struct{}{},
},
},
},
{
ports: HostPortInfo{},
pod: st.MakePod().
InitContainerPort(false /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
InitContainerPort(false /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{},
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
ProtocolPort{"TCP", 8002}: struct{}{},
},
},
},
}
for _, tc := range testCases {
ni := NodeInfo{UsedPorts: tc.ports}
ni.updateUsedPorts(tc.pod, true)
if diff := cmp.Diff(tc.want, ni.UsedPorts); diff != "" {
t.Errorf("updateUsedPorts() unexpected diff (-want, +got):\n%s", diff)
}
}
}
func TestUpdateUsedPorts_PodRemove(t *testing.T) {
testCases := []struct {
ports HostPortInfo
pod *v1.Pod
want HostPortInfo
}{
{
ports: HostPortInfo{},
pod: nil,
want: HostPortInfo{},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: nil,
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{},
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
ProtocolPort{"TCP", 8002}: struct{}{},
},
},
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
InitContainerPort(false /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
{
ports: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
ProtocolPort{"TCP", 8002}: struct{}{},
},
},
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: HostPortInfo{
"0.0.0.0": {
ProtocolPort{"TCP", 8001}: struct{}{},
},
},
},
}
for _, tc := range testCases {
ni := NodeInfo{UsedPorts: tc.ports}
ni.updateUsedPorts(tc.pod, false)
if diff := cmp.Diff(tc.want, ni.UsedPorts); diff != "" {
t.Errorf("updateUsedPorts() unexpected diff (-want, +got):\n%s", diff)
}
}
}

View File

@@ -503,6 +503,20 @@ func (p *PodWrapper) ContainerPort(ports []v1.ContainerPort) *PodWrapper {
return p
}
// InitContainerPort creates an initContainer with ports valued `ports`,
// and injects into the inner pod.
func (p *PodWrapper) InitContainerPort(sidecar bool, ports []v1.ContainerPort) *PodWrapper {
c := MakeContainer().
Name("init-container").
Image("pause").
ContainerPort(ports)
if sidecar {
c.RestartPolicy(v1.ContainerRestartPolicyAlways)
}
p.Spec.InitContainers = append(p.Spec.InitContainers, c.Obj())
return p
}
// PVC creates a Volume with a PVC and injects into the inner pod.
func (p *PodWrapper) PVC(name string) *PodWrapper {
p.Spec.Volumes = append(p.Spec.Volumes, v1.Volume{

View File

@@ -188,3 +188,39 @@ func As[T any](oldObj, newobj interface{}) (T, T, error) {
}
return oldTyped, newTyped, nil
}
// GetHostPorts returns the used host ports of pod containers and
// initContainers with restartPolicy: Always.
func GetHostPorts(pod *v1.Pod) []v1.ContainerPort {
var ports []v1.ContainerPort
if pod == nil {
return ports
}
hostPort := func(p v1.ContainerPort) bool {
return p.HostPort > 0
}
for _, c := range pod.Spec.InitContainers {
// Only consider initContainers that will be running the entire
// duration of the Pod.
if c.RestartPolicy == nil || *c.RestartPolicy != v1.ContainerRestartPolicyAlways {
continue
}
for _, p := range c.Ports {
if !hostPort(p) {
continue
}
ports = append(ports, p)
}
}
for _, c := range pod.Spec.Containers {
for _, p := range c.Ports {
if !hostPort(p) {
continue
}
ports = append(ports, p)
}
}
return ports
}

View File

@@ -35,6 +35,7 @@ import (
"k8s.io/klog/v2"
"k8s.io/klog/v2/ktesting"
extenderv1 "k8s.io/kube-scheduler/extender/v1"
st "k8s.io/kubernetes/pkg/scheduler/testing"
)
func TestGetPodFullName(t *testing.T) {
@@ -546,3 +547,232 @@ func Test_As_KMetadata(t *testing.T) {
})
}
}
func TestGetHostPorts(t *testing.T) {
tests := []struct {
pod *v1.Pod
want []v1.ContainerPort
}{
{
pod: nil,
want: nil,
},
{
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: nil,
},
{
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: nil,
},
{
pod: st.MakePod().
InitContainerPort(false /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: nil,
},
{
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: []v1.ContainerPort{{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}},
},
{
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}}).
Obj(),
want: []v1.ContainerPort{{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
}},
},
{
pod: st.MakePod().
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
}}).
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8003,
HostPort: 8003,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8004,
HostPort: 8004,
Protocol: v1.ProtocolTCP,
}}).
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8005,
Protocol: v1.ProtocolTCP,
},
}).Obj(),
want: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8003,
HostPort: 8003,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8004,
HostPort: 8004,
Protocol: v1.ProtocolTCP,
},
},
},
{
pod: st.MakePod().
InitContainerPort(false /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8002,
Protocol: v1.ProtocolTCP,
},
}).
InitContainerPort(false /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8003,
HostPort: 8003,
Protocol: v1.ProtocolTCP,
},
}).
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8004,
HostPort: 8004,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8005,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8006,
HostPort: 8006,
Protocol: v1.ProtocolTCP,
},
}).
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8007,
HostPort: 8007,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8008,
Protocol: v1.ProtocolTCP,
},
}).Obj(),
want: []v1.ContainerPort{
{
ContainerPort: 8004,
HostPort: 8004,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8006,
HostPort: 8006,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8007,
HostPort: 8007,
Protocol: v1.ProtocolTCP,
},
},
},
{
pod: st.MakePod().
InitContainerPort(true /* sidecar */, []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 9001,
Protocol: v1.ProtocolTCP,
},
}).
ContainerPort([]v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 9002,
Protocol: v1.ProtocolTCP,
},
}).Obj(),
want: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 9001,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 8002,
HostPort: 9002,
Protocol: v1.ProtocolTCP,
},
},
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) {
result := GetHostPorts(test.pod)
if diff := cmp.Diff(test.want, result); diff != "" {
t.Errorf("GetHostPorts() unexpected diff (-want,+got): %s", diff)
}
})
}
}

View File

@@ -638,3 +638,389 @@ func TestNodeEvents(t *testing.T) {
}
}
func TestHostPorts(t *testing.T) {
type podPorts struct {
container []v1.ContainerPort
restartableInitContainer []v1.ContainerPort
nonRestartableInitContainer []v1.ContainerPort
}
// Tests run with one node for scheduling. The first pod must always be scheduable.
tests := []struct {
name string
firstPorts podPorts
secondPorts podPorts
wantSecondScheduled bool
}{
{
name: "Containers using same non-host port",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "non-restartable initContainer and container using same non-host port",
firstPorts: podPorts{
nonRestartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "restartable initContainer and container using same non-host port",
firstPorts: podPorts{
restartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "container and non-restartable initContainer using same non-host port",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
nonRestartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "container and restartable initContainer using same non-host port",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
restartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "containers using same host port",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: false,
},
{
name: "non-restartable initContainer and container using same host port",
firstPorts: podPorts{
nonRestartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "restartable initContainer and container using same host port",
firstPorts: podPorts{
restartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: false,
},
{
name: "container and non-restartable initContainer using same host port",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
nonRestartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "container and restartable initContainer using same host port",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
restartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: false,
},
{
name: "containers using different host ports",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "non-restartable initContainer and container using different host ports",
firstPorts: podPorts{
nonRestartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "restartable initContainer and container using different host ports",
firstPorts: podPorts{
restartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "container and non-restartable initContainer using different host ports",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
nonRestartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
{
name: "container and restartable initContainer using different host ports",
firstPorts: podPorts{
container: []v1.ContainerPort{
{
ContainerPort: 8001,
HostPort: 8001,
Protocol: v1.ProtocolTCP,
},
},
},
secondPorts: podPorts{
restartableInitContainer: []v1.ContainerPort{
{
ContainerPort: 8002,
HostPort: 8002,
Protocol: v1.ProtocolTCP,
},
},
},
wantSecondScheduled: true,
},
}
testCtx := testutils.InitTestSchedulerWithNS(t, "conflicting-host-ports")
node, err := testutils.CreateNode(testCtx.ClientSet, st.MakeNode().
Name("conflicting-host-ports-node").Obj())
if err != nil {
t.Fatalf("Failed to create %s: %v", node.Name, err)
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p1, err := testutils.CreatePausePod(testCtx.ClientSet, testutils.InitPausePod(&testutils.PausePodConfig{
Name: "p1",
Namespace: testCtx.NS.Name,
ContainerPorts: tc.firstPorts.container,
RestartableInitContainerPorts: tc.firstPorts.restartableInitContainer,
NonRestartableInitContainerPorts: tc.firstPorts.nonRestartableInitContainer,
}))
if err != nil {
t.Fatalf("Failed to create Pod p1: %v", err)
}
err = testutils.WaitForPodToScheduleWithTimeout(testCtx.Ctx, testCtx.ClientSet, p1, 5*time.Second)
if err != nil {
t.Errorf("Pod %s didn't schedule: %v", p1.Name, err)
}
p2, err := testutils.CreatePausePod(testCtx.ClientSet, testutils.InitPausePod(&testutils.PausePodConfig{
Name: "p2",
Namespace: testCtx.NS.Name,
ContainerPorts: tc.secondPorts.container,
RestartableInitContainerPorts: tc.secondPorts.restartableInitContainer,
NonRestartableInitContainerPorts: tc.secondPorts.nonRestartableInitContainer,
}))
if err != nil {
t.Fatalf("Failed to create Pod p2: %v", err)
}
if tc.wantSecondScheduled {
err = testutils.WaitForPodToScheduleWithTimeout(testCtx.Ctx, testCtx.ClientSet, p2, 5*time.Second)
if err != nil {
t.Errorf("Pod %s didn't schedule: %v", p2.Name, err)
}
} else {
if err := testutils.WaitForPodUnschedulable(testCtx.Ctx, testCtx.ClientSet, p2); err != nil {
t.Errorf("Pod %v got scheduled: %v", p2.Name, err)
}
}
testutils.CleanupPods(testCtx.Ctx, testCtx.ClientSet, t, []*v1.Pod{p1, p2})
})
}
}

View File

@@ -814,6 +814,9 @@ type PausePodConfig struct {
PreemptionPolicy *v1.PreemptionPolicy
PriorityClassName string
Volumes []v1.Volume
ContainerPorts []v1.ContainerPort
RestartableInitContainerPorts []v1.ContainerPort
NonRestartableInitContainerPorts []v1.ContainerPort
}
// InitPausePod initializes a pod API object from the given config. It is used
@@ -833,6 +836,7 @@ func InitPausePod(conf *PausePodConfig) *v1.Pod {
{
Name: conf.Name,
Image: imageutils.GetPauseImageName(),
Ports: conf.ContainerPorts,
},
},
Tolerations: conf.Tolerations,
@@ -847,6 +851,26 @@ func InitPausePod(conf *PausePodConfig) *v1.Pod {
if conf.Resources != nil {
pod.Spec.Containers[0].Resources = *conf.Resources
}
if conf.RestartableInitContainerPorts != nil {
var containerRestartPolicyAlways = v1.ContainerRestartPolicyAlways
pod.Spec.InitContainers = []v1.Container{
{
Name: conf.Name + "-init",
Image: imageutils.GetPauseImageName(),
RestartPolicy: &containerRestartPolicyAlways,
Ports: conf.RestartableInitContainerPorts,
},
}
}
if conf.NonRestartableInitContainerPorts != nil {
pod.Spec.InitContainers = []v1.Container{
{
Name: conf.Name + "-init",
Image: imageutils.GetPauseImageName(),
Ports: conf.NonRestartableInitContainerPorts,
},
}
}
return pod
}