Skip to content

Commit f4d36dd

Browse files
committed
Add WatchCondition concept to the PLEG
1 parent 07a9ab8 commit f4d36dd

File tree

4 files changed

+284
-18
lines changed

4 files changed

+284
-18
lines changed

pkg/kubelet/pleg/evented.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,7 @@ func (e *EventedPLEG) updateLatencyMetric(event *runtimeapi.ContainerEventRespon
430430
func (e *EventedPLEG) UpdateCache(pod *kubecontainer.Pod, pid types.UID) (error, bool) {
431431
return fmt.Errorf("not implemented"), false
432432
}
433+
434+
func (e *EventedPLEG) SetPodWatchCondition(podUID types.UID, conditionKey string, condition WatchCondition) {
435+
e.genericPleg.SetPodWatchCondition(podUID, conditionKey, condition)
436+
}

pkg/kubelet/pleg/generic.go

Lines changed: 109 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ type GenericPLEG struct {
8080
podCacheMutex sync.Mutex
8181
// logger is used for contextual logging
8282
logger klog.Logger
83+
// watchConditions tracks pod watch conditions, guarded by watchConditionsLock
84+
// watchConditions is a map of pod UID -> condition key -> condition
85+
watchConditions map[types.UID]map[string]versionedWatchCondition
86+
watchConditionsLock sync.Mutex
87+
}
88+
89+
type versionedWatchCondition struct {
90+
key string
91+
condition WatchCondition
92+
version uint32
8393
}
8494

8595
// plegContainerState has a one-to-one mapping to the
@@ -125,13 +135,14 @@ func NewGenericPLEG(logger klog.Logger, runtime kubecontainer.Runtime, eventChan
125135
panic("cache cannot be nil")
126136
}
127137
return &GenericPLEG{
128-
logger: logger,
129-
relistDuration: relistDuration,
130-
runtime: runtime,
131-
eventChannel: eventChannel,
132-
podRecords: make(podRecords),
133-
cache: cache,
134-
clock: clock,
138+
logger: logger,
139+
relistDuration: relistDuration,
140+
runtime: runtime,
141+
eventChannel: eventChannel,
142+
podRecords: make(podRecords),
143+
cache: cache,
144+
clock: clock,
145+
watchConditions: make(map[types.UID]map[string]versionedWatchCondition),
135146
}
136147
}
137148

@@ -252,6 +263,7 @@ func (g *GenericPLEG) Relist() {
252263
// update running pod and container count
253264
updateRunningPodAndContainerMetrics(pods)
254265
g.podRecords.setCurrent(pods)
266+
g.cleanupOrphanedWatchConditions()
255267

256268
needsReinspection := make(map[types.UID]*kubecontainer.Pod)
257269

@@ -267,9 +279,10 @@ func (g *GenericPLEG) Relist() {
267279
events = append(events, containerEvents...)
268280
}
269281

282+
watchConditions := g.getPodWatchConditions(pid)
270283
_, reinspect := g.podsToReinspect[pid]
271284

272-
if len(events) == 0 && !reinspect {
285+
if len(events) == 0 && len(watchConditions) == 0 && !reinspect {
273286
// Nothing else needed for this pod.
274287
continue
275288
}
@@ -283,7 +296,8 @@ func (g *GenericPLEG) Relist() {
283296
// inspecting the pod and getting the PodStatus to update the cache
284297
// serially may take a while. We should be aware of this and
285298
// parallelize if needed.
286-
if err, updated := g.updateCache(ctx, pod, pid); err != nil {
299+
status, updated, err := g.updateCache(ctx, pod, pid)
300+
if err != nil {
287301
// Rely on updateCache calling GetPodStatus to log the actual error.
288302
g.logger.V(4).Error(err, "PLEG: Ignoring events for pod", "pod", klog.KRef(pod.Namespace, pod.Name))
289303

@@ -299,6 +313,14 @@ func (g *GenericPLEG) Relist() {
299313
}
300314
}
301315

316+
var completedConditions []versionedWatchCondition
317+
for _, condition := range watchConditions {
318+
if condition.condition(status) {
319+
completedConditions = append(completedConditions, condition)
320+
}
321+
}
322+
g.completeWatchConditions(pid, completedConditions)
323+
302324
// Update the internal storage and send out the events.
303325
g.podRecords.update(pid)
304326

@@ -320,8 +342,6 @@ func (g *GenericPLEG) Relist() {
320342
if events[i].Type == ContainerDied {
321343
// Fill up containerExitCode map for ContainerDied event when first time appeared
322344
if len(containerExitCode) == 0 && pod != nil {
323-
// Get updated podStatus
324-
status, err := g.cache.Get(pod.ID)
325345
if err == nil {
326346
for _, containerStatus := range status.ContainerStatuses {
327347
containerExitCode[containerStatus.ID.ID] = containerStatus.ExitCode
@@ -410,13 +430,13 @@ func (g *GenericPLEG) getPodIPs(pid types.UID, status *kubecontainer.PodStatus)
410430
// updateCache tries to update the pod status in the kubelet cache and returns true if the
411431
// pod status was actually updated in the cache. It will return false if the pod status
412432
// was ignored by the cache.
413-
func (g *GenericPLEG) updateCache(ctx context.Context, pod *kubecontainer.Pod, pid types.UID) (error, bool) {
433+
func (g *GenericPLEG) updateCache(ctx context.Context, pod *kubecontainer.Pod, pid types.UID) (*kubecontainer.PodStatus, bool, error) {
414434
if pod == nil {
415435
// The pod is missing in the current relist. This means that
416436
// the pod has no visible (active or inactive) containers.
417437
g.logger.V(4).Info("PLEG: Delete status for pod", "podUID", string(pid))
418438
g.cache.Delete(pid)
419-
return nil, true
439+
return nil, true, nil
420440
}
421441

422442
g.podCacheMutex.Lock()
@@ -460,22 +480,93 @@ func (g *GenericPLEG) updateCache(ctx context.Context, pod *kubecontainer.Pod, p
460480
timestamp = status.TimeStamp
461481
}
462482

463-
return err, g.cache.Set(pod.ID, status, err, timestamp)
483+
return status, g.cache.Set(pod.ID, status, err, timestamp), err
464484
}
465485

466486
func (g *GenericPLEG) UpdateCache(pod *kubecontainer.Pod, pid types.UID) (error, bool) {
467487
ctx := context.Background()
468488
if pod == nil {
469489
return fmt.Errorf("pod cannot be nil"), false
470490
}
471-
return g.updateCache(ctx, pod, pid)
491+
_, updated, err := g.updateCache(ctx, pod, pid)
492+
return err, updated
493+
}
494+
495+
func (g *GenericPLEG) SetPodWatchCondition(podUID types.UID, conditionKey string, condition WatchCondition) {
496+
g.watchConditionsLock.Lock()
497+
defer g.watchConditionsLock.Unlock()
498+
499+
conditions, ok := g.watchConditions[podUID]
500+
if !ok {
501+
if condition == nil {
502+
return // Condition isn't set, nothing to do.
503+
}
504+
conditions = make(map[string]versionedWatchCondition)
505+
}
506+
507+
versioned, found := conditions[conditionKey]
508+
if found {
509+
versioned.version++
510+
versioned.condition = condition
511+
conditions[conditionKey] = versioned
512+
} else if condition != nil {
513+
conditions[conditionKey] = versionedWatchCondition{
514+
key: conditionKey,
515+
condition: condition,
516+
}
517+
}
518+
519+
g.watchConditions[podUID] = conditions
472520
}
473521

474-
func updateEvents(eventsByPodID map[types.UID][]*PodLifecycleEvent, e *PodLifecycleEvent) {
475-
if e == nil {
522+
// getPodWatchConditions returns a list of the active watch conditions for the pod.
523+
func (g *GenericPLEG) getPodWatchConditions(podUID types.UID) []versionedWatchCondition {
524+
g.watchConditionsLock.Lock()
525+
defer g.watchConditionsLock.Unlock()
526+
527+
conditions, ok := g.watchConditions[podUID]
528+
if !ok {
529+
return nil
530+
}
531+
532+
filtered := make([]versionedWatchCondition, 0, len(conditions))
533+
for _, condition := range conditions {
534+
filtered = append(filtered, condition)
535+
}
536+
return filtered
537+
}
538+
539+
// completeWatchConditions clears the completed watch conditions.
540+
func (g *GenericPLEG) completeWatchConditions(podUID types.UID, completedConditions []versionedWatchCondition) {
541+
g.watchConditionsLock.Lock()
542+
defer g.watchConditionsLock.Unlock()
543+
544+
conditions, ok := g.watchConditions[podUID]
545+
if !ok {
546+
// Pod was deleted, nothing to do.
476547
return
477548
}
478-
eventsByPodID[e.ID] = append(eventsByPodID[e.ID], e)
549+
550+
for _, completed := range completedConditions {
551+
condition := conditions[completed.key]
552+
// Only clear the condition if it has not been updated.
553+
if condition.version == completed.version {
554+
delete(conditions, completed.key)
555+
}
556+
}
557+
g.watchConditions[podUID] = conditions
558+
}
559+
560+
func (g *GenericPLEG) cleanupOrphanedWatchConditions() {
561+
g.watchConditionsLock.Lock()
562+
defer g.watchConditionsLock.Unlock()
563+
564+
for podUID := range g.watchConditions {
565+
if g.podRecords.getCurrent(podUID) == nil {
566+
// Pod was deleted, remove it from the watch conditions.
567+
delete(g.watchConditions, podUID)
568+
}
569+
}
479570
}
480571

481572
func getContainerState(pod *kubecontainer.Pod, cid *kubecontainer.ContainerID) plegContainerState {

pkg/kubelet/pleg/generic_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,3 +736,154 @@ kubelet_running_pods 2
736736
})
737737
}
738738
}
739+
740+
func TestWatchConditions(t *testing.T) {
741+
pods := []*containertest.FakePod{{
742+
Pod: &kubecontainer.Pod{
743+
Name: "running-pod",
744+
ID: "running",
745+
Sandboxes: []*kubecontainer.Container{
746+
createTestContainer("s", kubecontainer.ContainerStateRunning),
747+
},
748+
Containers: []*kubecontainer.Container{
749+
createTestContainer("c", kubecontainer.ContainerStateRunning),
750+
},
751+
},
752+
}, {
753+
Pod: &kubecontainer.Pod{
754+
Name: "terminating-pod",
755+
ID: "terminating",
756+
Sandboxes: []*kubecontainer.Container{
757+
createTestContainer("s", kubecontainer.ContainerStateExited),
758+
},
759+
},
760+
}, {
761+
Pod: &kubecontainer.Pod{
762+
Name: "reinspect-pod",
763+
ID: "reinspect",
764+
Sandboxes: []*kubecontainer.Container{
765+
createTestContainer("s", kubecontainer.ContainerStateRunning),
766+
},
767+
},
768+
}}
769+
initialPods := append(pods, &containertest.FakePod{Pod: &kubecontainer.Pod{
770+
Name: "terminated-pod",
771+
ID: "terminated",
772+
Sandboxes: []*kubecontainer.Container{
773+
createTestContainer("s", kubecontainer.ContainerStateExited),
774+
},
775+
}})
776+
777+
alwaysComplete := func(_ *kubecontainer.PodStatus) bool {
778+
return true
779+
}
780+
neverComplete := func(_ *kubecontainer.PodStatus) bool {
781+
return false
782+
}
783+
784+
var pleg *GenericPLEG
785+
var updatingCond WatchCondition
786+
// updatingCond always completes, but updates the condition first.
787+
updatingCond = func(_ *kubecontainer.PodStatus) bool {
788+
pleg.SetPodWatchCondition("running", "updating", updatingCond)
789+
return true
790+
}
791+
792+
testCases := []struct {
793+
name string
794+
podUID types.UID
795+
watchConditions map[string]WatchCondition
796+
expectEvaluated bool // Whether the watch conditions should be evaluated
797+
expectRemoved bool // Whether podUID should be present in the watch conditions map
798+
expectWatchConditions map[string]versionedWatchCondition // The expected watch conditions for the podUIDa (only key & version checked)
799+
}{{
800+
name: "no watch conditions",
801+
podUID: "running",
802+
}, {
803+
name: "running pod with conditions",
804+
podUID: "running",
805+
watchConditions: map[string]WatchCondition{
806+
"completing": alwaysComplete,
807+
"watching": neverComplete,
808+
"updating": updatingCond,
809+
},
810+
expectEvaluated: true,
811+
expectWatchConditions: map[string]versionedWatchCondition{
812+
"watching": {version: 0},
813+
"updating": {version: 1},
814+
},
815+
}, {
816+
name: "non-existant pod",
817+
podUID: "non-existant",
818+
watchConditions: map[string]WatchCondition{
819+
"watching": neverComplete,
820+
},
821+
expectEvaluated: false,
822+
expectRemoved: true,
823+
}, {
824+
name: "terminated pod",
825+
podUID: "terminated",
826+
watchConditions: map[string]WatchCondition{
827+
"watching": neverComplete,
828+
},
829+
expectEvaluated: false,
830+
expectRemoved: true,
831+
}, {
832+
name: "reinspecting pod",
833+
podUID: "reinspect",
834+
watchConditions: map[string]WatchCondition{
835+
"watching": neverComplete,
836+
},
837+
expectEvaluated: true,
838+
expectWatchConditions: map[string]versionedWatchCondition{
839+
"watching": {version: 0},
840+
},
841+
}}
842+
843+
for _, test := range testCases {
844+
t.Run(test.name, func(t *testing.T) {
845+
testPleg := newTestGenericPLEG()
846+
pleg = testPleg.pleg
847+
runtime := testPleg.runtime
848+
runtime.AllPodList = initialPods
849+
pleg.Relist() // Setup initial pod records.
850+
851+
runtime.AllPodList = pods // Doesn't have "terminated" pod.
852+
pleg.podsToReinspect["reinspect"] = nil
853+
854+
var evaluatedConditions []string
855+
for key, condition := range test.watchConditions {
856+
wrappedCondition := func(status *kubecontainer.PodStatus) bool {
857+
if !test.expectEvaluated {
858+
assert.Fail(t, "conditions should not be evaluated")
859+
} else {
860+
evaluatedConditions = append(evaluatedConditions, key)
861+
}
862+
return condition(status)
863+
}
864+
pleg.SetPodWatchCondition(test.podUID, key, wrappedCondition)
865+
}
866+
pleg.Relist()
867+
868+
if test.expectEvaluated {
869+
assert.Len(t, evaluatedConditions, len(test.watchConditions), "all conditions should be evaluated")
870+
}
871+
872+
if test.expectRemoved {
873+
assert.NotContains(t, pleg.watchConditions, test.podUID, "Pod should be removed from watch conditions")
874+
} else {
875+
actualConditions := pleg.watchConditions[test.podUID]
876+
assert.Len(t, actualConditions, len(test.expectWatchConditions), "expected number of conditions")
877+
for key, expected := range test.expectWatchConditions {
878+
if !assert.Contains(t, actualConditions, key) {
879+
continue
880+
}
881+
actual := actualConditions[key]
882+
assert.Equal(t, key, actual.key)
883+
assert.Equal(t, expected.version, actual.version)
884+
}
885+
}
886+
887+
})
888+
}
889+
}

0 commit comments

Comments
 (0)