Skip to content

Commit a764a94

Browse files
bacherflChrsMark
authored andcommitted
[receiver/k8sobjects] ensure the k8s.namespace.name attribute is set for objects retrieved using the watch mode (open-telemetry#36432)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description This PR ensures that the structure of log records generated by the k8sobjects receiver is the same, regardless of the mode (`watch` or `pull`) being used. This also solves the issue of the `k8s.namespace.name` attribute not being set for objects retrieved with `watch` mode. <!-- Issue number (e.g. open-telemetry#1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes open-telemetry#36352 <!--Describe what testing was performed and which tests were added.--> #### Testing Added unit tests and adapted e2e tests --------- Signed-off-by: Florian Bacher <[email protected]> Co-authored-by: Christos Markou <[email protected]>
1 parent f1fcd0f commit a764a94

File tree

6 files changed

+136
-29
lines changed

6 files changed

+136
-29
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: bug_fix
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: k8sobjectsreceiver
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: ensure the `k8s.namespace.name` attribute is set for objects retrieved using the `watch` mode
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [36352]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

receiver/k8sobjectsreceiver/e2e_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ func TestE2E(t *testing.T) {
145145
}, time.Duration(tc.timeoutMinutes)*time.Minute, 1*time.Second,
146146
"Timeout: failed to receive logs in %d minutes", tc.timeoutMinutes)
147147

148+
// golden.WriteLogs(t, expectedFile, logsConsumer.AllLogs()[0])
149+
148150
require.NoErrorf(t, plogtest.CompareLogs(expected, logsConsumer.AllLogs()[0],
149151
plogtest.IgnoreObservedTimestamp(),
150152
plogtest.IgnoreResourceLogsOrder(),

receiver/k8sobjectsreceiver/testdata/e2e/expected/watch_events.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
resourceLogs:
2-
- resource: {}
2+
- resource:
3+
attributes:
4+
- key: k8s.namespace.name
5+
value:
6+
stringValue: default
37
scopeLogs:
48
- scope: {}
59
logRecords:

receiver/k8sobjectsreceiver/testdata/e2e/expected/watch_events_core.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
resourceLogs:
2-
- resource: {}
2+
- resource:
3+
attributes:
4+
- key: k8s.namespace.name
5+
value:
6+
stringValue: default
37
scopeLogs:
48
- scope: {}
59
logRecords:

receiver/k8sobjectsreceiver/unstructured_to_logdata.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@ func unstructuredListToLogData(event *unstructured.UnstructuredList, observedAt
5151
namespaceResourceMap := make(map[string]plog.LogRecordSlice)
5252

5353
for _, e := range event.Items {
54-
logSlice, ok := namespaceResourceMap[e.GetNamespace()]
54+
logSlice, ok := namespaceResourceMap[getNamespace(e)]
5555
if !ok {
5656
rl := resourceLogs.AppendEmpty()
5757
resourceAttrs := rl.Resource().Attributes()
58-
if namespace := e.GetNamespace(); namespace != "" {
58+
if namespace := getNamespace(e); namespace != "" {
5959
resourceAttrs.PutStr(semconv.AttributeK8SNamespaceName, namespace)
6060
}
6161
sl := rl.ScopeLogs().AppendEmpty()
6262
logSlice = sl.LogRecords()
63-
namespaceResourceMap[e.GetNamespace()] = logSlice
63+
namespaceResourceMap[getNamespace(e)] = logSlice
6464
}
6565
record := logSlice.AppendEmpty()
6666
record.SetObservedTimestamp(pcommon.NewTimestampFromTime(observedAt))
@@ -79,3 +79,15 @@ func unstructuredListToLogData(event *unstructured.UnstructuredList, observedAt
7979
}
8080
return out
8181
}
82+
83+
func getNamespace(e unstructured.Unstructured) string {
84+
// first, try to use the GetNamespace() method, which checks for the metadata.namespace property
85+
if namespace := e.GetNamespace(); namespace != "" {
86+
return namespace
87+
}
88+
// try to look up namespace in object.metadata.namespace (for objects reported via watch mode)
89+
if namespace, ok, _ := unstructured.NestedString(e.Object, "object", "metadata", "namespace"); ok {
90+
return namespace
91+
}
92+
return ""
93+
}

receiver/k8sobjectsreceiver/unstructured_to_logdata_test.go

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"time"
1010

1111
"github.com/stretchr/testify/assert"
12-
"github.com/stretchr/testify/require"
1312
semconv "go.opentelemetry.io/collector/semconv/v1.27.0"
1413
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1514
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -91,7 +90,7 @@ func TestUnstructuredListToLogData(t *testing.T) {
9190
assert.Equal(t, 3, logRecords.Len())
9291
})
9392

94-
t.Run("Test event.name in watch events", func(t *testing.T) {
93+
t.Run("Test event observed timestamp is present", func(t *testing.T) {
9594
config := &K8sObjectsConfig{
9695
gvr: &schema.GroupVersionResource{
9796
Group: "",
@@ -112,7 +111,8 @@ func TestUnstructuredListToLogData(t *testing.T) {
112111
},
113112
}
114113

115-
logs, err := watchObjectsToLogData(event, time.Now(), config)
114+
observedAt := time.Now()
115+
logs, err := watchObjectsToLogData(event, observedAt, config)
116116
assert.NoError(t, err)
117117

118118
assert.Equal(t, 1, logs.LogRecordCount())
@@ -123,47 +123,105 @@ func TestUnstructuredListToLogData(t *testing.T) {
123123
logRecords := rl.ScopeLogs().At(0).LogRecords()
124124
assert.Equal(t, 1, rl.ScopeLogs().Len())
125125
assert.Equal(t, 1, logRecords.Len())
126-
127-
attrs := logRecords.At(0).Attributes()
128-
eventName, ok := attrs.Get("event.name")
129-
require.True(t, ok)
130-
assert.EqualValues(t, "generic-name", eventName.AsRaw())
126+
assert.Positive(t, logRecords.At(0).ObservedTimestamp().AsTime().Unix())
127+
assert.Equal(t, logRecords.At(0).ObservedTimestamp().AsTime().Unix(), observedAt.Unix())
131128
})
132129

133-
t.Run("Test event observed timestamp is present", func(t *testing.T) {
130+
t.Run("Test pull and watch objects both contain k8s.namespace.name", func(t *testing.T) {
131+
observedTimestamp := time.Now()
134132
config := &K8sObjectsConfig{
135133
gvr: &schema.GroupVersionResource{
136134
Group: "",
137135
Version: "v1",
138136
Resource: "events",
139137
},
140138
}
141-
event := &watch.Event{
139+
watchedEvent := &watch.Event{
142140
Type: watch.Added,
143141
Object: &unstructured.Unstructured{
144142
Object: map[string]any{
145143
"kind": "Event",
146144
"apiVersion": "v1",
147145
"metadata": map[string]any{
148-
"name": "generic-name",
146+
"name": "generic-name",
147+
"namespace": "my-namespace",
149148
},
150149
},
151150
},
152151
}
153152

154-
observedAt := time.Now()
155-
logs, err := watchObjectsToLogData(event, observedAt, config)
156-
assert.NoError(t, err)
157-
158-
assert.Equal(t, 1, logs.LogRecordCount())
153+
pulledEvent := &unstructured.UnstructuredList{
154+
Items: []unstructured.Unstructured{{
155+
Object: map[string]any{
156+
"kind": "Event",
157+
"apiVersion": "v1",
158+
"metadata": map[string]any{
159+
"name": "generic-name",
160+
"namespace": "my-namespace",
161+
},
162+
},
163+
}},
164+
}
159165

160-
resourceLogs := logs.ResourceLogs()
161-
assert.Equal(t, 1, resourceLogs.Len())
162-
rl := resourceLogs.At(0)
163-
logRecords := rl.ScopeLogs().At(0).LogRecords()
164-
assert.Equal(t, 1, rl.ScopeLogs().Len())
165-
assert.Equal(t, 1, logRecords.Len())
166-
assert.Positive(t, logRecords.At(0).ObservedTimestamp().AsTime().Unix())
167-
assert.Equal(t, logRecords.At(0).ObservedTimestamp().AsTime().Unix(), observedAt.Unix())
166+
logEntryFromWatchEvent, err := watchObjectsToLogData(watchedEvent, observedTimestamp, config)
167+
assert.NoError(t, err)
168+
assert.NotNil(t, logEntryFromWatchEvent)
169+
170+
// verify the event.type, event.domain and k8s.resource.name attributes have been added
171+
172+
watchEventResourceAttrs := logEntryFromWatchEvent.ResourceLogs().At(0).Resource().Attributes()
173+
k8sNamespace, ok := watchEventResourceAttrs.Get(semconv.AttributeK8SNamespaceName)
174+
assert.True(t, ok)
175+
assert.Equal(t,
176+
"my-namespace",
177+
k8sNamespace.Str(),
178+
)
179+
180+
watchEvenLogRecordtAttrs := logEntryFromWatchEvent.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes()
181+
eventType, ok := watchEvenLogRecordtAttrs.Get("event.name")
182+
assert.True(t, ok)
183+
assert.Equal(
184+
t,
185+
"generic-name",
186+
eventType.AsString(),
187+
)
188+
189+
eventDomain, ok := watchEvenLogRecordtAttrs.Get("event.domain")
190+
assert.True(t, ok)
191+
assert.Equal(
192+
t,
193+
"k8s",
194+
eventDomain.AsString(),
195+
)
196+
197+
k8sResourceName, ok := watchEvenLogRecordtAttrs.Get("k8s.resource.name")
198+
assert.True(t, ok)
199+
assert.Equal(
200+
t,
201+
"events",
202+
k8sResourceName.AsString(),
203+
)
204+
205+
logEntryFromPulledEvent := unstructuredListToLogData(pulledEvent, observedTimestamp, config)
206+
assert.NotNil(t, logEntryFromPulledEvent)
207+
208+
pullEventResourceAttrs := logEntryFromPulledEvent.ResourceLogs().At(0).Resource().Attributes()
209+
k8sNamespace, ok = pullEventResourceAttrs.Get(semconv.AttributeK8SNamespaceName)
210+
assert.True(t, ok)
211+
assert.Equal(
212+
t,
213+
"my-namespace",
214+
k8sNamespace.Str(),
215+
)
216+
217+
pullEventLogRecordAttrs := logEntryFromPulledEvent.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes()
218+
219+
k8sResourceName, ok = pullEventLogRecordAttrs.Get("k8s.resource.name")
220+
assert.True(t, ok)
221+
assert.Equal(
222+
t,
223+
"events",
224+
k8sResourceName.AsString(),
225+
)
168226
})
169227
}

0 commit comments

Comments
 (0)