Skip to content

Commit 8fc5dc2

Browse files
authored
Merge branch 'main' into niharikag09/otel-url-template-resource-name
2 parents 560896a + eb013eb commit 8fc5dc2

4 files changed

Lines changed: 506 additions & 1 deletion

File tree

pkg/collector/corechecks/containerlifecycle/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ go_library(
99
"handler_container_creation.go",
1010
"handler_container_termination.go",
1111
"handler_pod_creation.go",
12+
"handler_pod_state.go",
1213
"handler_pod_termination.go",
1314
"handler_task_termination.go",
1415
"processor.go",
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
package containerlifecycle
7+
8+
import (
9+
"fmt"
10+
"sync"
11+
"time"
12+
13+
"github.com/DataDog/agent-payload/v5/contlcycle"
14+
15+
workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def"
16+
types "github.com/DataDog/datadog-agent/pkg/containerlifecycle"
17+
)
18+
19+
// podShadow is the last-observed state of a pod's phase and conditions
20+
type podShadow struct {
21+
mu sync.Mutex
22+
phase string
23+
conditions map[string]workloadmeta.KubernetesPodCondition
24+
}
25+
26+
// PodStateHandler handles pod phase and condition transition events.
27+
type PodStateHandler struct {
28+
mu sync.Mutex
29+
// shadow maps pod UID to its last-observed phase and conditions.
30+
shadow map[string]*podShadow
31+
}
32+
33+
// NewPodStateHandler returns an initialized PodStateHandler.
34+
func NewPodStateHandler() *PodStateHandler {
35+
return &PodStateHandler{shadow: make(map[string]*podShadow)}
36+
}
37+
38+
// String returns a human-readable name for the handler.
39+
func (h *PodStateHandler) String() string {
40+
return "PodStateHandler"
41+
}
42+
43+
// CanHandle reports whether this handler processes the given event.
44+
func (h *PodStateHandler) CanHandle(ev workloadmeta.Event, _ workloadmeta.Source) bool {
45+
return ev.Entity.GetID().Kind == workloadmeta.KindKubernetesPod
46+
}
47+
48+
// Handle builds a LifecycleEvent per detected phase or condition transition for the pod,
49+
// and clears the pod's shadow state once workloadmeta reports it as unset.
50+
func (h *PodStateHandler) Handle(ev workloadmeta.Event) ([]LifecycleEvent, error) {
51+
podUID := ev.Entity.GetID().ID
52+
53+
if ev.Type == workloadmeta.EventTypeUnset {
54+
h.mu.Lock()
55+
delete(h.shadow, podUID)
56+
h.mu.Unlock()
57+
return nil, nil
58+
}
59+
60+
pod, ok := ev.Entity.(*workloadmeta.KubernetesPod)
61+
if !ok {
62+
return nil, fmt.Errorf("expected *workloadmeta.KubernetesPod, got %T", ev.Entity)
63+
}
64+
65+
h.mu.Lock()
66+
shadow, ok := h.shadow[podUID]
67+
if !ok {
68+
shadow = &podShadow{conditions: make(map[string]workloadmeta.KubernetesPodCondition)}
69+
h.shadow[podUID] = shadow
70+
}
71+
h.mu.Unlock()
72+
73+
shadow.mu.Lock()
74+
defer shadow.mu.Unlock()
75+
76+
var transitions []*contlcycle.PodStateTransition
77+
78+
// If the pod phase has changed, add a transition event.
79+
if phaseChanged(shadow.phase, pod.Phase) {
80+
var lastObserved *contlcycle.PodStatusValue
81+
if shadow.phase != "" {
82+
lastObserved = &contlcycle.PodStatusValue{Value: &contlcycle.PodStatusValue_Phase{Phase: shadow.phase}}
83+
}
84+
85+
transitions = append(transitions, &contlcycle.PodStateTransition{
86+
Field: contlcycle.PodStatusField_POD_STATUS_FIELD_PHASE,
87+
LastObservedState: lastObserved,
88+
NewState: &contlcycle.PodStatusValue{Value: &contlcycle.PodStatusValue_Phase{Phase: pod.Phase}},
89+
TransitionTimestamp: time.Now().Unix(),
90+
Precision: contlcycle.Precision_PRECISION_APPROXIMATE,
91+
MissedIntermediate: contlcycle.MissedIntermediate_MISSED_INTERMEDIATE_UNKNOWABLE,
92+
})
93+
94+
shadow.phase = pod.Phase
95+
}
96+
97+
// If any condition has changed, add a transition event.
98+
for _, condition := range pod.Conditions {
99+
priorCondition, seen := shadow.conditions[condition.Type]
100+
if conditionUnchanged(seen, priorCondition, condition) {
101+
shadow.conditions[condition.Type] = condition
102+
continue
103+
}
104+
105+
missedIntermediate := conditionMissedIntermediate(seen, priorCondition, condition)
106+
107+
var lastObserved *contlcycle.PodStatusValue
108+
if seen {
109+
lastObserved = &contlcycle.PodStatusValue{Value: &contlcycle.PodStatusValue_Condition{
110+
Condition: conditionToModel(priorCondition),
111+
}}
112+
}
113+
114+
// If the condition has no last transition time, it's indicative of a bug.
115+
// We should probably still report it, but with PRECISION_APPROXIMATE.
116+
if condition.LastTransitionTime.IsZero() {
117+
transitions = append(transitions, &contlcycle.PodStateTransition{
118+
Field: contlcycle.PodStatusField_POD_STATUS_FIELD_CONDITION,
119+
LastObservedState: lastObserved,
120+
NewState: &contlcycle.PodStatusValue{Value: &contlcycle.PodStatusValue_Condition{Condition: conditionToModel(condition)}},
121+
TransitionTimestamp: time.Now().Unix(),
122+
Precision: contlcycle.Precision_PRECISION_APPROXIMATE,
123+
MissedIntermediate: contlcycle.MissedIntermediate_MISSED_INTERMEDIATE_UNKNOWABLE,
124+
})
125+
} else {
126+
transitions = append(transitions, &contlcycle.PodStateTransition{
127+
Field: contlcycle.PodStatusField_POD_STATUS_FIELD_CONDITION,
128+
LastObservedState: lastObserved,
129+
NewState: &contlcycle.PodStatusValue{Value: &contlcycle.PodStatusValue_Condition{
130+
Condition: conditionToModel(condition),
131+
}},
132+
TransitionTimestamp: condition.LastTransitionTime.Unix(),
133+
Precision: contlcycle.Precision_PRECISION_EXACT,
134+
MissedIntermediate: missedIntermediate,
135+
})
136+
}
137+
138+
shadow.conditions[condition.Type] = condition
139+
}
140+
141+
if len(transitions) == 0 {
142+
return nil, nil
143+
}
144+
145+
les := make([]LifecycleEvent, 0, len(transitions))
146+
for _, transition := range transitions {
147+
les = append(les, LifecycleEvent{
148+
ObjectKind: types.ObjectKindPod,
149+
ProtoEvent: &contlcycle.Event{
150+
EventType: contlcycle.Event_Transition,
151+
TypedEvent: &contlcycle.Event_Pod{Pod: &contlcycle.PodEvent{
152+
PodUID: podUID,
153+
Source: string(workloadmeta.SourceNodeOrchestrator),
154+
Transition: transition,
155+
}},
156+
},
157+
})
158+
}
159+
160+
return les, nil
161+
}
162+
163+
// phaseChanged reports whether current is a real, newly-observed phase value.
164+
func phaseChanged(prior, current string) bool {
165+
return current != "" && current != prior
166+
}
167+
168+
// conditionUnchanged reports whether a condition has already been observed and is identical to its prior value, barring reason changes.
169+
func conditionUnchanged(seen bool, prior, current workloadmeta.KubernetesPodCondition) bool {
170+
return seen && prior.Status == current.Status && prior.LastTransitionTime.Equal(current.LastTransitionTime)
171+
}
172+
173+
// conditionMissedIntermediate reports PROVEN when status is unchanged but LastTransitionTime advanced,
174+
// since that only happens on a real status flip.
175+
func conditionMissedIntermediate(seen bool, prior, current workloadmeta.KubernetesPodCondition) contlcycle.MissedIntermediate {
176+
if seen && prior.Status == current.Status &&
177+
!prior.LastTransitionTime.IsZero() && !current.LastTransitionTime.IsZero() &&
178+
current.LastTransitionTime.After(prior.LastTransitionTime) {
179+
return contlcycle.MissedIntermediate_MISSED_INTERMEDIATE_PROVEN
180+
}
181+
return contlcycle.MissedIntermediate_MISSED_INTERMEDIATE_UNKNOWABLE
182+
}
183+
184+
// conditionToModel converts a workloadmeta pod condition into its contlcycle proto representation.
185+
func conditionToModel(c workloadmeta.KubernetesPodCondition) *contlcycle.ConditionValue {
186+
return &contlcycle.ConditionValue{
187+
Type: c.Type,
188+
Status: c.Status,
189+
Reason: &c.Reason,
190+
}
191+
}

0 commit comments

Comments
 (0)