Skip to content

Commit b131c34

Browse files
authored
[chore] OTEL-2552 Part 2.0 Datadog Extension - internal/payload (#39633)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description adds internal/payload package for Datadog Extension to define otel_collector payload structure for infrastructure backend and helper methods <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> <!--Describe what testing was performed and which tests were added.--> #### Testing unit tests for new methods <!--Describe the documentation added.--> #### Documentation not planning to add user-facing documentation for individual submodules; chloggen will be added README.md will be updated accordingly for main extension once that PR is proposed <!--Please delete paragraphs that you did not use before submitting.-->
1 parent bb09cbf commit b131c34

File tree

6 files changed

+395
-0
lines changed

6 files changed

+395
-0
lines changed

extension/datadogextension/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/extension/datad
33
go 1.23.0
44

55
require (
6+
github.com/DataDog/datadog-agent/pkg/serializer v0.64.3
67
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/datadog v0.124.1
78
github.com/stretchr/testify v1.10.0
89
go.opentelemetry.io/collector/component v1.30.1-0.20250424234037-ac7c0f2f4cd8

extension/datadogextension/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package payload // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/datadogextension/internal/payload"
5+
6+
import (
7+
"encoding/json"
8+
)
9+
10+
// ModuleInfoJSON holds data on all modules in the collector
11+
// It is built to make checking module info quicker when building active/configured components list
12+
// (don't need to iterate through a whole list of modules, just do key/value pair in map)
13+
type ModuleInfoJSON struct {
14+
components map[string]CollectorModule
15+
}
16+
17+
func NewModuleInfoJSON() *ModuleInfoJSON {
18+
return &ModuleInfoJSON{
19+
components: make(map[string]CollectorModule),
20+
}
21+
}
22+
23+
func (m *ModuleInfoJSON) getKey(typeStr, kindStr string) string {
24+
return typeStr + ":" + kindStr
25+
}
26+
27+
func (m *ModuleInfoJSON) AddComponent(comp CollectorModule) {
28+
key := m.getKey(comp.Type, comp.Kind)
29+
m.components[key] = comp
30+
// We don't ever expect two go modules to have the same type and kind
31+
// as collector would not be able to distinguish between them for configuration
32+
// and service/pipeline purposes.
33+
}
34+
35+
func (m *ModuleInfoJSON) GetComponent(typeStr, kindStr string) (CollectorModule, bool) {
36+
key := m.getKey(typeStr, kindStr)
37+
comp, ok := m.components[key]
38+
return comp, ok
39+
}
40+
41+
func (m *ModuleInfoJSON) AddComponents(components []CollectorModule) {
42+
for _, comp := range components {
43+
m.AddComponent(comp)
44+
}
45+
}
46+
47+
func (m *ModuleInfoJSON) MarshalJSON() ([]byte, error) {
48+
alias := struct {
49+
Components []CollectorModule `json:"full_components"`
50+
}{
51+
Components: make([]CollectorModule, 0, len(m.components)),
52+
}
53+
for _, comp := range m.components {
54+
alias.Components = append(alias.Components, comp)
55+
}
56+
return json.Marshal(alias)
57+
}
58+
59+
func (m *ModuleInfoJSON) GetFullComponentsList() []CollectorModule {
60+
fullComponents := make([]CollectorModule, 0, len(m.components))
61+
for _, comp := range m.components {
62+
fullComponents = append(fullComponents, comp)
63+
}
64+
return fullComponents
65+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package payload // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/datadogextension/internal/payload"
5+
6+
import (
7+
"encoding/json"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestNewModuleInfoJSON(t *testing.T) {
14+
modInfo := NewModuleInfoJSON()
15+
assert.NotNil(t, modInfo)
16+
assert.NotNil(t, modInfo.components)
17+
assert.Empty(t, modInfo.components)
18+
}
19+
20+
func TestAddComponent(t *testing.T) {
21+
modInfo := NewModuleInfoJSON()
22+
comp := CollectorModule{
23+
Type: "receiver",
24+
Kind: "otlp",
25+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpreceiver",
26+
Version: "v0.30.0",
27+
Configured: true,
28+
}
29+
modInfo.AddComponent(comp)
30+
assert.Len(t, modInfo.components, 1)
31+
key := modInfo.getKey(comp.Type, comp.Kind)
32+
assert.Equal(t, comp, modInfo.components[key])
33+
}
34+
35+
func TestGetComponent(t *testing.T) {
36+
modInfo := NewModuleInfoJSON()
37+
comp := CollectorModule{
38+
Type: "receiver",
39+
Kind: "otlp",
40+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpreceiver",
41+
Version: "v0.30.0",
42+
Configured: true,
43+
}
44+
modInfo.AddComponent(comp)
45+
retrievedComp, ok := modInfo.GetComponent("receiver", "otlp")
46+
assert.True(t, ok)
47+
assert.Equal(t, comp, retrievedComp)
48+
49+
_, ok = modInfo.GetComponent("processor", "batch")
50+
assert.False(t, ok)
51+
}
52+
53+
func TestModuleInfoJSON_MarshalJSON(t *testing.T) {
54+
modInfo := NewModuleInfoJSON()
55+
comp1 := CollectorModule{
56+
Type: "receiver",
57+
Kind: "otlp",
58+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpreceiver",
59+
Version: "v0.30.0",
60+
Configured: true,
61+
}
62+
comp2 := CollectorModule{
63+
Type: "processor",
64+
Kind: "batch",
65+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/processor/batchprocessor",
66+
Version: "v0.30.0",
67+
Configured: true,
68+
}
69+
modInfo.AddComponent(comp1)
70+
modInfo.AddComponent(comp2)
71+
72+
jsonData, err := json.Marshal(modInfo)
73+
assert.NoError(t, err)
74+
75+
var actualJSON map[string]any
76+
err = json.Unmarshal(jsonData, &actualJSON)
77+
assert.NoError(t, err)
78+
79+
expectedJSON := `{
80+
"full_components": [
81+
{
82+
"type": "receiver",
83+
"kind": "otlp",
84+
"gomod": "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpreceiver",
85+
"version": "v0.30.0",
86+
"configured": true
87+
},
88+
{
89+
"type": "processor",
90+
"kind": "batch",
91+
"gomod": "github.com/open-telemetry/opentelemetry-collector-contrib/processor/batchprocessor",
92+
"version": "v0.30.0",
93+
"configured": true
94+
}
95+
]
96+
}`
97+
var expectedJSONMap map[string]any
98+
err = json.Unmarshal([]byte(expectedJSON), &expectedJSONMap)
99+
assert.NoError(t, err)
100+
101+
assert.ElementsMatch(t, expectedJSONMap["full_components"], actualJSON["full_components"])
102+
}
103+
104+
func TestAddComponents(t *testing.T) {
105+
modInfo := NewModuleInfoJSON()
106+
comp1 := CollectorModule{
107+
Type: "receiver",
108+
Kind: "otlp",
109+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpreceiver",
110+
Version: "v0.30.0",
111+
Configured: true,
112+
}
113+
comp2 := CollectorModule{
114+
Type: "processor",
115+
Kind: "batch",
116+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/processor/batchprocessor",
117+
Version: "v0.30.0",
118+
Configured: true,
119+
}
120+
modInfo.AddComponents([]CollectorModule{comp1, comp2})
121+
122+
assert.Len(t, modInfo.components, 2)
123+
key1 := modInfo.getKey(comp1.Type, comp1.Kind)
124+
key2 := modInfo.getKey(comp2.Type, comp2.Kind)
125+
assert.Equal(t, comp1, modInfo.components[key1])
126+
assert.Equal(t, comp2, modInfo.components[key2])
127+
}
128+
129+
func TestGetFullComponentsList(t *testing.T) {
130+
modInfo := NewModuleInfoJSON()
131+
comp1 := CollectorModule{
132+
Type: "receiver",
133+
Kind: "otlp",
134+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otlpreceiver",
135+
Version: "v0.30.0",
136+
Configured: true,
137+
}
138+
comp2 := CollectorModule{
139+
Type: "processor",
140+
Kind: "batch",
141+
Gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/processor/batchprocessor",
142+
Version: "v0.30.0",
143+
Configured: true,
144+
}
145+
modInfo.AddComponents([]CollectorModule{comp1, comp2})
146+
147+
fullComponentsList := modInfo.GetFullComponentsList()
148+
assert.Len(t, fullComponentsList, 2)
149+
assert.Contains(t, fullComponentsList, comp1)
150+
assert.Contains(t, fullComponentsList, comp2)
151+
}

extension/datadogextension/internal/payload/payload.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,101 @@
33

44
// package payload will define the metadata payload schemas to be forwarded to Datadog backend
55
package payload // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/datadogextension/internal/payload"
6+
7+
import (
8+
"encoding/json"
9+
"errors"
10+
11+
"github.com/DataDog/datadog-agent/pkg/serializer/marshaler"
12+
)
13+
14+
const (
15+
payloadSplitErr = "could not split otel collector payload any more, payload is too big for intake"
16+
)
17+
18+
// CustomBuildInfo is a struct that duplicates the fields of component.BuildInfo with custom JSON tags
19+
type CustomBuildInfo struct {
20+
Command string `json:"command"`
21+
Description string `json:"description"`
22+
Version string `json:"version"`
23+
}
24+
25+
type OtelCollector struct {
26+
HostKey string `json:"host_key"`
27+
Hostname string `json:"hostname"`
28+
HostnameSource string `json:"hostname_source"`
29+
CollectorID string `json:"collector_id"`
30+
CollectorVersion string `json:"collector_version"`
31+
ConfigSite string `json:"config_site"`
32+
APIKeyUUID string `json:"api_key_uuid"`
33+
FullComponents []CollectorModule `json:"full_components"`
34+
ActiveComponents []ServiceComponent `json:"active_components"`
35+
BuildInfo CustomBuildInfo `json:"build_info"`
36+
FullConfiguration string `json:"full_configuration"` // JSON passed as string
37+
HealthStatus string `json:"health_status"` // JSON passed as string
38+
}
39+
40+
type CollectorModule struct {
41+
Type string `json:"type"`
42+
Kind string `json:"kind"`
43+
Gomod string `json:"gomod"`
44+
Version string `json:"version"`
45+
Configured bool `json:"configured"`
46+
}
47+
48+
type ServiceComponent struct {
49+
ID string `json:"id"`
50+
Name string `json:"name"`
51+
Type string `json:"type"`
52+
Kind string `json:"kind"`
53+
Pipeline string `json:"pipeline"`
54+
Gomod string `json:"gomod"`
55+
Version string `json:"version"`
56+
ComponentStatus string `json:"component_status"`
57+
}
58+
59+
// Explicitly implement JSONMarshaler interface from datadog-agent
60+
var (
61+
_ marshaler.JSONMarshaler = (*OtelCollectorPayload)(nil)
62+
)
63+
64+
type OtelCollectorPayload struct {
65+
Hostname string `json:"hostname"`
66+
Timestamp int64 `json:"timestamp"`
67+
Metadata OtelCollector `json:"otel_collector"`
68+
UUID string `json:"uuid"`
69+
}
70+
71+
// MarshalJSON serializes a OtelCollectorPayload to JSON
72+
func (p *OtelCollectorPayload) MarshalJSON() ([]byte, error) {
73+
type collectorPayloadAlias OtelCollectorPayload
74+
return json.Marshal((*collectorPayloadAlias)(p))
75+
}
76+
77+
// SplitPayload implements marshaler.AbstractMarshaler#SplitPayload.
78+
func (p *OtelCollectorPayload) SplitPayload(_ int) ([]marshaler.AbstractMarshaler, error) {
79+
return nil, errors.New(payloadSplitErr)
80+
}
81+
82+
// PrepareOtelCollectorPayload takes metadata from various config values and prepares an OtelCollector payload
83+
func PrepareOtelCollectorPayload(
84+
hostname,
85+
hostnameSource,
86+
extensionUUID,
87+
version,
88+
site,
89+
fullConfig string,
90+
buildInfo CustomBuildInfo,
91+
) OtelCollector {
92+
return OtelCollector{
93+
HostKey: "",
94+
Hostname: hostname,
95+
HostnameSource: hostnameSource,
96+
CollectorID: hostname + "-" + extensionUUID,
97+
CollectorVersion: version,
98+
ConfigSite: site,
99+
APIKeyUUID: "",
100+
BuildInfo: buildInfo,
101+
FullConfiguration: fullConfig,
102+
}
103+
}

0 commit comments

Comments
 (0)