port setNodePIDPressureCondition to Setter abstraction, add test

This commit is contained in:
Michael Taufen 2018-06-25 18:48:27 -07:00
parent b26e4dfa7f
commit e0b6ae219f
3 changed files with 204 additions and 57 deletions

View File

@ -724,62 +724,6 @@ func (kl *Kubelet) setNodeReadyCondition(node *v1.Node) {
}
}
// setNodePIDPressureCondition for the node.
// TODO: this needs to move somewhere centralized...
func (kl *Kubelet) setNodePIDPressureCondition(node *v1.Node) {
currentTime := metav1.NewTime(kl.clock.Now())
var condition *v1.NodeCondition
// Check if NodePIDPressure condition already exists and if it does, just pick it up for update.
for i := range node.Status.Conditions {
if node.Status.Conditions[i].Type == v1.NodePIDPressure {
condition = &node.Status.Conditions[i]
}
}
newCondition := false
// If the NodePIDPressure condition doesn't exist, create one
if condition == nil {
condition = &v1.NodeCondition{
Type: v1.NodePIDPressure,
Status: v1.ConditionUnknown,
}
// cannot be appended to node.Status.Conditions here because it gets
// copied to the slice. So if we append to the slice here none of the
// updates we make below are reflected in the slice.
newCondition = true
}
// Update the heartbeat time
condition.LastHeartbeatTime = currentTime
// Note: The conditions below take care of the case when a new NodePIDPressure condition is
// created and as well as the case when the condition already exists. When a new condition
// is created its status is set to v1.ConditionUnknown which matches either
// condition.Status != v1.ConditionTrue or
// condition.Status != v1.ConditionFalse in the conditions below depending on whether
// the kubelet is under PID pressure or not.
if kl.evictionManager.IsUnderPIDPressure() {
if condition.Status != v1.ConditionTrue {
condition.Status = v1.ConditionTrue
condition.Reason = "KubeletHasInsufficientPID"
condition.Message = "kubelet has insufficient PID available"
condition.LastTransitionTime = currentTime
kl.recordNodeStatusEvent(v1.EventTypeNormal, "NodeHasInsufficientPID")
}
} else if condition.Status != v1.ConditionFalse {
condition.Status = v1.ConditionFalse
condition.Reason = "KubeletHasSufficientPID"
condition.Message = "kubelet has sufficient PID available"
condition.LastTransitionTime = currentTime
kl.recordNodeStatusEvent(v1.EventTypeNormal, "NodeHasSufficientPID")
}
if newCondition {
node.Status.Conditions = append(node.Status.Conditions, *condition)
}
}
// record if node schedulable change.
func (kl *Kubelet) recordNodeSchedulableEvent(node *v1.Node) {
kl.lastNodeUnschedulableLock.Lock()
@ -848,7 +792,7 @@ func (kl *Kubelet) defaultNodeStatusFuncs() []func(*v1.Node) error {
nodestatus.OutOfDiskCondition(kl.clock.Now, kl.recordNodeStatusEvent),
nodestatus.MemoryPressureCondition(kl.clock.Now, kl.evictionManager.IsUnderMemoryPressure, kl.recordNodeStatusEvent),
nodestatus.DiskPressureCondition(kl.clock.Now, kl.evictionManager.IsUnderDiskPressure, kl.recordNodeStatusEvent),
withoutError(kl.setNodePIDPressureCondition),
nodestatus.PIDPressureCondition(kl.clock.Now, kl.evictionManager.IsUnderPIDPressure, kl.recordNodeStatusEvent),
withoutError(kl.setNodeReadyCondition),
withoutError(kl.setNodeVolumesInUseStatus),
withoutError(kl.recordNodeSchedulableEvent),

View File

@ -203,6 +203,67 @@ func MemoryPressureCondition(nowFunc func() time.Time, // typically Kubelet.cloc
}
}
// PIDPressureCondition returns a Setter that updates the v1.NodePIDPressure condition on the node.
func PIDPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock.Now
pressureFunc func() bool, // typically Kubelet.evictionManager.IsUnderPIDPressure
recordEventFunc func(eventType, event string), // typically Kubelet.recordNodeStatusEvent
) Setter {
return func(node *v1.Node) error {
currentTime := metav1.NewTime(nowFunc())
var condition *v1.NodeCondition
// Check if NodePIDPressure condition already exists and if it does, just pick it up for update.
for i := range node.Status.Conditions {
if node.Status.Conditions[i].Type == v1.NodePIDPressure {
condition = &node.Status.Conditions[i]
}
}
newCondition := false
// If the NodePIDPressure condition doesn't exist, create one
if condition == nil {
condition = &v1.NodeCondition{
Type: v1.NodePIDPressure,
Status: v1.ConditionUnknown,
}
// cannot be appended to node.Status.Conditions here because it gets
// copied to the slice. So if we append to the slice here none of the
// updates we make below are reflected in the slice.
newCondition = true
}
// Update the heartbeat time
condition.LastHeartbeatTime = currentTime
// Note: The conditions below take care of the case when a new NodePIDPressure condition is
// created and as well as the case when the condition already exists. When a new condition
// is created its status is set to v1.ConditionUnknown which matches either
// condition.Status != v1.ConditionTrue or
// condition.Status != v1.ConditionFalse in the conditions below depending on whether
// the kubelet is under PID pressure or not.
if pressureFunc() {
if condition.Status != v1.ConditionTrue {
condition.Status = v1.ConditionTrue
condition.Reason = "KubeletHasInsufficientPID"
condition.Message = "kubelet has insufficient PID available"
condition.LastTransitionTime = currentTime
recordEventFunc(v1.EventTypeNormal, "NodeHasInsufficientPID")
}
} else if condition.Status != v1.ConditionFalse {
condition.Status = v1.ConditionFalse
condition.Reason = "KubeletHasSufficientPID"
condition.Message = "kubelet has sufficient PID available"
condition.LastTransitionTime = currentTime
recordEventFunc(v1.EventTypeNormal, "NodeHasSufficientPID")
}
if newCondition {
node.Status.Conditions = append(node.Status.Conditions, *condition)
}
return nil
}
}
// DiskPressureCondition returns a Setter that updates the v1.NodeDiskPressure condition on the node.
func DiskPressureCondition(nowFunc func() time.Time, // typically Kubelet.clock.Now
pressureFunc func() bool, // typically Kubelet.evictionManager.IsUnderDiskPressure

View File

@ -306,6 +306,127 @@ func TestMemoryPressureCondition(t *testing.T) {
}
}
func TestPIDPressureCondition(t *testing.T) {
now := time.Now()
before := now.Add(-time.Second)
nowFunc := func() time.Time { return now }
cases := []struct {
desc string
node *v1.Node
pressure bool
expectConditions []v1.NodeCondition
expectEvents []testEvent
}{
{
desc: "new, no pressure",
node: &v1.Node{},
pressure: false,
expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)},
expectEvents: []testEvent{
{
eventType: v1.EventTypeNormal,
event: "NodeHasSufficientPID",
},
},
},
{
desc: "new, pressure",
node: &v1.Node{},
pressure: true,
expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)},
expectEvents: []testEvent{
{
eventType: v1.EventTypeNormal,
event: "NodeHasInsufficientPID",
},
},
},
{
desc: "transition to pressure",
node: &v1.Node{
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)},
},
},
pressure: true,
expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)},
expectEvents: []testEvent{
{
eventType: v1.EventTypeNormal,
event: "NodeHasInsufficientPID",
},
},
},
{
desc: "transition to no pressure",
node: &v1.Node{
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)},
},
},
pressure: false,
expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)},
expectEvents: []testEvent{
{
eventType: v1.EventTypeNormal,
event: "NodeHasSufficientPID",
},
},
},
{
desc: "pressure, no transition",
node: &v1.Node{
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)},
},
},
pressure: true,
expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, now)},
expectEvents: []testEvent{},
},
{
desc: "no pressure, no transition",
node: &v1.Node{
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)},
},
},
pressure: false,
expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, now)},
expectEvents: []testEvent{},
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
events := []testEvent{}
recordEventFunc := func(eventType, event string) {
events = append(events, testEvent{
eventType: eventType,
event: event,
})
}
pressureFunc := func() bool {
return tc.pressure
}
// construct setter
setter := PIDPressureCondition(nowFunc, pressureFunc, recordEventFunc)
// call setter on node
if err := setter(tc.node); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// check expected condition
assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
"Diff: %s", diff.ObjectDiff(tc.expectConditions, tc.node.Status.Conditions))
// check expected events
require.Equal(t, len(tc.expectEvents), len(events))
for i := range tc.expectEvents {
assert.Equal(t, tc.expectEvents[i], events[i])
}
})
}
}
func TestDiskPressureCondition(t *testing.T) {
now := time.Now()
before := now.Add(-time.Second)
@ -469,6 +590,27 @@ func makeMemoryPressureCondition(pressure bool, transition, heartbeat time.Time)
}
}
func makePIDPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
if pressure {
return &v1.NodeCondition{
Type: v1.NodePIDPressure,
Status: v1.ConditionTrue,
Reason: "KubeletHasInsufficientPID",
Message: "kubelet has insufficient PID available",
LastTransitionTime: metav1.NewTime(transition),
LastHeartbeatTime: metav1.NewTime(heartbeat),
}
}
return &v1.NodeCondition{
Type: v1.NodePIDPressure,
Status: v1.ConditionFalse,
Reason: "KubeletHasSufficientPID",
Message: "kubelet has sufficient PID available",
LastTransitionTime: metav1.NewTime(transition),
LastHeartbeatTime: metav1.NewTime(heartbeat),
}
}
func makeDiskPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
if pressure {
return &v1.NodeCondition{