Skip to content

Commit ede0558

Browse files
hwchenHowell ChenXiaoning Ding
authored
Suppress extraneous updates of node status runtime readiness with no status change (#160)
* frugal node status runtime readiness updates * refactored runtime service readiness setter to make it consistent with other setters * initialize runtime condition only if runtime status is probed * initialize runtime conditions with unknown readiness * when initializing, the order of runtime conditions matters - vm first, container next Co-authored-by: Howell Chen <[email protected]> Co-authored-by: Xiaoning Ding <[email protected]>
1 parent 096725b commit ede0558

File tree

2 files changed

+154
-69
lines changed

2 files changed

+154
-69
lines changed

pkg/kubelet/nodestatus/setters.go

Lines changed: 56 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -536,99 +536,86 @@ func RuntimeServiceCondition(nowFunc func() time.Time, // typically Kubelet.cloc
536536
) Setter {
537537
return func(node *v1.Node) error {
538538
currentTime := metav1.NewTime(nowFunc())
539-
var containerRuntimeCondition *v1.NodeCondition
540-
var vmRuntimeCondition *v1.NodeCondition
541-
542-
// Check if node runtime ready condition already exists and if it does, just pick it up for update.
543-
for i := range node.Status.Conditions {
544-
if node.Status.Conditions[i].Type == v1.NodeContainerRuntimeReady {
545-
containerRuntimeCondition = &node.Status.Conditions[i]
546-
}
547-
if node.Status.Conditions[i].Type == v1.NodeVmRuntimeReady {
548-
vmRuntimeCondition = &node.Status.Conditions[i]
549-
}
550-
}
551-
552-
newContainerCondition := false
553-
newVmCondition := false
554-
555-
// If the NodeMemoryPressure condition doesn't exist, create one
556-
if vmRuntimeCondition == nil {
557-
vmRuntimeCondition = &v1.NodeCondition{
558-
Type: v1.NodeVmRuntimeReady,
559-
Status: v1.ConditionUnknown,
560-
}
561-
newVmCondition = true
562-
}
563-
564-
if containerRuntimeCondition == nil {
565-
containerRuntimeCondition = &v1.NodeCondition{
566-
Type: v1.NodeContainerRuntimeReady,
567-
Status: v1.ConditionUnknown,
568-
}
569-
newContainerCondition = true
570-
}
571-
572-
// Update the heartbeat time
573-
containerRuntimeCondition.LastHeartbeatTime = currentTime
574-
vmRuntimeCondition.LastHeartbeatTime = currentTime
575-
576539
runtimeStatuses, err := runtimeServiceStateFunc()
577540
if err != nil {
578541
return err
579542
}
580543

544+
// todo: we may need to lower log level to reduce unimportant logging data in production
581545
klog.Infof("runtime service status map: %v", runtimeStatuses)
582546

583-
// get the runtime status by workload types
547+
// get the runtime status of container & vm
548+
var containerRuntimeStatus, vmRuntimeStatus map[string]bool
584549
for workloadType, runtimeServicesStatus := range runtimeStatuses {
585-
klog.Infof("runtime service [%s] map: [%v]", workloadType, runtimeServicesStatus)
586550
switch {
587551
case workloadType == "container":
588-
containerRuntimeCondition = getCurrentRuntimeReadiness(containerRuntimeCondition, workloadType, runtimeServicesStatus,
589-
recordEventFunc, currentTime)
590-
552+
containerRuntimeStatus = runtimeServicesStatus
591553
case workloadType == "vm":
592-
vmRuntimeCondition = getCurrentRuntimeReadiness(vmRuntimeCondition, workloadType, runtimeServicesStatus,
593-
recordEventFunc, currentTime)
554+
vmRuntimeStatus = runtimeServicesStatus
594555
}
595556
}
596557

597-
if newVmCondition {
598-
vmRuntimeCondition.LastTransitionTime = currentTime
599-
node.Status.Conditions = append(node.Status.Conditions, *vmRuntimeCondition)
600-
}
601-
if newContainerCondition {
602-
containerRuntimeCondition.LastTransitionTime = currentTime
603-
node.Status.Conditions = append(node.Status.Conditions, *containerRuntimeCondition)
558+
subConditionSetter := func(node *v1.Node, conditionType v1.NodeConditionType, workloadType string, runtimeStatus map[string]bool) {
559+
var condition *v1.NodeCondition
560+
561+
// Check if node runtime ready condition already exists and if it does, just pick it up for update.
562+
for i := range node.Status.Conditions {
563+
if node.Status.Conditions[i].Type == conditionType {
564+
condition = &node.Status.Conditions[i]
565+
}
566+
}
567+
568+
newCondition := false
569+
// If the specified condition doesn't exist, create one
570+
if condition == nil {
571+
condition = &v1.NodeCondition{
572+
Type: conditionType,
573+
Status: v1.ConditionUnknown,
574+
LastTransitionTime: currentTime,
575+
}
576+
577+
newCondition = true
578+
}
579+
580+
// Update the heartbeat time
581+
condition.LastHeartbeatTime = currentTime
582+
583+
if runtimeStatus != nil {
584+
if runtimeIsReady(runtimeStatus) {
585+
if condition.Status != v1.ConditionTrue {
586+
condition.Status = v1.ConditionTrue
587+
condition.Reason = fmt.Sprintf("At least one %s runtime is ready", workloadType)
588+
condition.LastTransitionTime = currentTime
589+
recordEventFunc(v1.EventTypeNormal, fmt.Sprintf("%s is ready", conditionType))
590+
}
591+
} else if condition.Status != v1.ConditionFalse {
592+
condition.Status = v1.ConditionFalse
593+
condition.Status = v1.ConditionFalse
594+
condition.Reason = fmt.Sprintf("None of %s runtime is ready", workloadType)
595+
condition.LastTransitionTime = currentTime
596+
recordEventFunc(v1.EventTypeNormal, fmt.Sprintf("%s is not ready", conditionType))
597+
}
598+
}
599+
600+
if newCondition {
601+
node.Status.Conditions = append(node.Status.Conditions, *condition)
602+
}
604603
}
604+
605+
subConditionSetter(node, v1.NodeVmRuntimeReady, "vm", vmRuntimeStatus)
606+
subConditionSetter(node, v1.NodeContainerRuntimeReady, "container", containerRuntimeStatus)
605607
return nil
606608
}
607609
}
608610

609-
func getCurrentRuntimeReadiness(runtimeCondition *v1.NodeCondition, workloadType string,
610-
runtimeServiceStatus map[string]bool, recordEventFunc func(eventType, event string),
611-
currentTime metav1.Time) *v1.NodeCondition {
612-
statusSet := false
611+
func runtimeIsReady(runtimeServiceStatus map[string]bool) bool {
613612
for _, status := range runtimeServiceStatus {
614613
if status == true {
615-
runtimeCondition.Status = v1.ConditionTrue
616-
runtimeCondition.Reason = fmt.Sprintf("At least one %s runtime is ready", workloadType)
617-
recordEventFunc(v1.EventTypeNormal, fmt.Sprintf("%s is ready", runtimeCondition.Type))
618-
statusSet = true
619-
break
614+
return true
620615
}
621616
}
622617

623-
if statusSet != true {
624-
runtimeCondition.Status = v1.ConditionFalse
625-
runtimeCondition.Reason = fmt.Sprintf("None of %s runtime is ready", workloadType)
626-
recordEventFunc(v1.EventTypeNormal, fmt.Sprintf("%s is not ready", runtimeCondition.Type))
627-
}
628-
629-
runtimeCondition.LastTransitionTime = currentTime
630-
631-
return runtimeCondition
618+
return false
632619
}
633620

634621
// MemoryPressureCondition returns a Setter that updates the v1.NodeMemoryPressure condition on the node.

pkg/kubelet/nodestatus/setters_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,93 @@ const (
5252
testKubeletHostname = "127.0.0.1"
5353
)
5454

55+
56+
57+
func TestRuntimeServiceCondition(t *testing.T) {
58+
zeroTime := time.Time{}
59+
checkTime := time.Date(2019, 10, 24, 0, 0, 0, 0, time.UTC)
60+
recordEventFunc := func(eventType, event string) {}
61+
nowFunc := func() time.Time { return checkTime }
62+
63+
cases := []struct{
64+
desc string
65+
node *v1.Node
66+
runtimeServiceStateFunc func() (map[string]map[string]bool, error)
67+
expectedConditions []v1.NodeCondition
68+
}{
69+
{
70+
desc: "fresh empty node status should get unknown conditions",
71+
node: &v1.Node{
72+
Status: v1.NodeStatus{
73+
Conditions: []v1.NodeCondition{
74+
},
75+
},
76+
},
77+
runtimeServiceStateFunc: func() (map[string]map[string]bool, error) {
78+
return map[string] map[string]bool{
79+
}, nil
80+
},
81+
expectedConditions: []v1.NodeCondition{
82+
makeRuntimeServiceCondition("VmRuntimeReady", v1.ConditionUnknown, "", "", checkTime, checkTime),
83+
makeRuntimeServiceCondition("ContainerRuntimeReady", v1.ConditionUnknown, "", "", checkTime, checkTime),
84+
},
85+
},
86+
{
87+
desc: "condition should keep the same except for LastHeartbeatTime",
88+
node: &v1.Node{
89+
Status: v1.NodeStatus{
90+
Conditions: []v1.NodeCondition{
91+
makeRuntimeServiceCondition("VmRuntimeReady", v1.ConditionFalse, "test runtime turned off", "", zeroTime, zeroTime),
92+
makeRuntimeServiceCondition("ContainerRuntimeReady", v1.ConditionTrue, "test runtime turned on", "", zeroTime, zeroTime),
93+
},
94+
},
95+
},
96+
runtimeServiceStateFunc: func() (map[string]map[string]bool, error) {
97+
return map[string] map[string]bool{
98+
"container": { "fake-container": true },
99+
"vm": {"fake-vm": false},
100+
}, nil
101+
},
102+
expectedConditions: []v1.NodeCondition{
103+
makeRuntimeServiceCondition("VmRuntimeReady", v1.ConditionFalse, "test runtime turned off", "", zeroTime, checkTime),
104+
makeRuntimeServiceCondition("ContainerRuntimeReady", v1.ConditionTrue, "test runtime turned on", "", zeroTime, checkTime),
105+
},
106+
},
107+
{
108+
desc: "condition should all change",
109+
node: &v1.Node{
110+
Status: v1.NodeStatus{
111+
Conditions: []v1.NodeCondition{
112+
makeRuntimeServiceCondition("ContainerRuntimeReady", v1.ConditionTrue, "test runtime turned on", "", zeroTime, zeroTime),
113+
makeRuntimeServiceCondition("VmRuntimeReady", v1.ConditionFalse, "test runtime turned off", "", zeroTime, zeroTime),
114+
},
115+
},
116+
},
117+
runtimeServiceStateFunc: func() (map[string]map[string]bool, error) {
118+
return map[string] map[string]bool{
119+
"container": { "fake-container": false },
120+
"vm": {"fake-vm": true},
121+
}, nil
122+
},
123+
expectedConditions: []v1.NodeCondition{
124+
makeRuntimeServiceCondition("ContainerRuntimeReady", v1.ConditionFalse, "None of container runtime is ready", "", checkTime, checkTime),
125+
makeRuntimeServiceCondition("VmRuntimeReady", v1.ConditionTrue, "At least one vm runtime is ready", "", checkTime, checkTime),
126+
},
127+
},
128+
}
129+
130+
for _, c := range cases {
131+
t.Run(c.desc, func(t *testing.T) {
132+
setter := RuntimeServiceCondition(nowFunc, c.runtimeServiceStateFunc, recordEventFunc)
133+
if err := setter(c.node); err != nil {
134+
t.Fatalf("unexpected error: #{err}")
135+
}
136+
137+
assert.True(t, apiequality.Semantic.DeepEqual(c.expectedConditions, c.node.Status.Conditions), "%s", diff.ObjectDiff(c.expectedConditions, c.node.Status.Conditions))
138+
})
139+
}
140+
}
141+
55142
// TODO(mtaufen): below is ported from the old kubelet_node_status_test.go code, potentially add more test coverage for NodeAddress setter in future
56143
func TestNodeAddress(t *testing.T) {
57144
cases := []struct {
@@ -1717,3 +1804,14 @@ func makeDiskPressureCondition(pressure bool, transition, heartbeat time.Time) *
17171804
LastHeartbeatTime: metav1.NewTime(heartbeat),
17181805
}
17191806
}
1807+
1808+
func makeRuntimeServiceCondition(typ v1.NodeConditionType, status v1.ConditionStatus, reason, message string, transition, heartbeat time.Time) v1.NodeCondition {
1809+
return v1.NodeCondition{
1810+
Type: typ,
1811+
Status: status,
1812+
Reason: reason,
1813+
Message: message,
1814+
LastTransitionTime: metav1.NewTime(transition),
1815+
LastHeartbeatTime: metav1.NewTime(heartbeat),
1816+
}
1817+
}

0 commit comments

Comments
 (0)