diff --git a/comp/core/tagger/impl/tagger.go b/comp/core/tagger/impl/tagger.go index 612fdca2bb9d39..1aed5a99467ad1 100644 --- a/comp/core/tagger/impl/tagger.go +++ b/comp/core/tagger/impl/tagger.go @@ -470,7 +470,7 @@ func (t *TaggerWrapper) EnrichTags(tb tagset.TagsAccumulator, originInfo taggert // Accumulate tags for pod UID if originInfo.ExternalData.PodUID != "" { if err := t.AccumulateTagsFor(types.NewEntityID(types.KubernetesPodUID, originInfo.ExternalData.PodUID), cardinality, tb); err != nil { - t.log.Tracef("Cannot get tags for entity %s: %s", originInfo.ContainerID, err) + t.log.Tracef("Cannot get tags for entity %s: %s", originInfo.ExternalData.PodUID, err) } } diff --git a/comp/core/tagger/origindetection/origindetection.go b/comp/core/tagger/origindetection/origindetection.go index 712792c54f298a..56c2188100e880 100644 --- a/comp/core/tagger/origindetection/origindetection.go +++ b/comp/core/tagger/origindetection/origindetection.go @@ -26,6 +26,14 @@ const ( // ProductOriginAPM is the ProductOrigin for APM. ProductOriginAPM ProductOrigin = iota + // Local Data Prefixes + // These prefixes are used to build the Local Data list. + + // LocalDataContainerIDPrefix is the prefix used for the Container ID sent in the Local Data list. + LocalDataContainerIDPrefix = "ci-" + // LocalDataInodePrefix is the prefix used for the Inode sent in the Local Data list. + LocalDataInodePrefix = "in-" + // External Data Prefixes // These prefixes are used to build the External Data Environment Variable. @@ -63,13 +71,49 @@ type ExternalData struct { // GenerateContainerIDFromExternalData generates a container ID from the external data. type GenerateContainerIDFromExternalData func(externalData ExternalData) (string, error) +// ParseLocalData parses the local data string into a LocalData struct. +func ParseLocalData(rawLocalData string) (LocalData, error) { + if rawLocalData == "" { + return LocalData{}, nil + } + + var localData LocalData + var parsingError error + + if strings.Contains(rawLocalData, ",") { + // The Local Data can contain a list. + items := strings.Split(rawLocalData, ",") + for _, item := range items { + if strings.HasPrefix(item, LocalDataContainerIDPrefix) { + localData.ContainerID = item[len(LocalDataContainerIDPrefix):] + } else if strings.HasPrefix(item, LocalDataInodePrefix) { + localData.Inode, parsingError = strconv.ParseUint(item[len(LocalDataInodePrefix):], 10, 64) + } + } + } else { + // The Local Data can contain a single value. + if strings.HasPrefix(rawLocalData, LocalDataContainerIDPrefix) { + localData.ContainerID = rawLocalData[len(LocalDataContainerIDPrefix):] + } else if strings.HasPrefix(rawLocalData, LocalDataInodePrefix) { + localData.Inode, parsingError = strconv.ParseUint(rawLocalData[len(LocalDataInodePrefix):], 10, 64) + } else { + // Container ID with old format: + localData.ContainerID = rawLocalData + } + } + + return localData, parsingError +} + // ParseExternalData parses the external data string into an ExternalData struct. func ParseExternalData(externalEnv string) (ExternalData, error) { if externalEnv == "" { return ExternalData{}, nil } + var externalData ExternalData var parsingError error + for _, item := range strings.Split(externalEnv, ",") { switch { case strings.HasPrefix(item, ExternalDataInitPrefix): @@ -80,5 +124,6 @@ func ParseExternalData(externalEnv string) (ExternalData, error) { externalData.PodUID = item[len(ExternalDataPodUIDPrefix):] } } + return externalData, parsingError } diff --git a/comp/core/tagger/origindetection/origindetection_test.go b/comp/core/tagger/origindetection/origindetection_test.go index a873093f6191b8..b38c82c89b35de 100644 --- a/comp/core/tagger/origindetection/origindetection_test.go +++ b/comp/core/tagger/origindetection/origindetection_test.go @@ -12,6 +12,65 @@ import ( "github.com/stretchr/testify/assert" ) +func TestParseLocalData(t *testing.T) { + tests := []struct { + name string + rawLocalData string + expected LocalData + expectError bool + }{ + { + name: "Empty string", + rawLocalData: "", + expected: LocalData{}, + expectError: false, + }, + { + name: "Single container ID", + rawLocalData: "ci-abc123", + expected: LocalData{ContainerID: "abc123"}, + expectError: false, + }, + { + name: "Single inode", + rawLocalData: "in-12345", + expected: LocalData{Inode: 12345}, + expectError: false, + }, + { + name: "Multiple values", + rawLocalData: "ci-abc123,in-12345", + expected: LocalData{ContainerID: "abc123", Inode: 12345}, + expectError: false, + }, + { + name: "Invalid inode", + rawLocalData: "in-invalid", + expected: LocalData{}, + expectError: true, + }, + { + name: "Old container format", + rawLocalData: "abc123", + expected: LocalData{ContainerID: "abc123"}, + expectError: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result, err := ParseLocalData(tc.rawLocalData) + + if tc.expectError { + assert.Error(t, err) + } else { + assert.Equal(t, tc.expected, result) + } + + }) + } +} + func TestParseExternalData(t *testing.T) { tests := []struct { name string diff --git a/comp/dogstatsd/server/enrich.go b/comp/dogstatsd/server/enrich.go index 5e5565ca75d42f..099be50cac0e3a 100644 --- a/comp/dogstatsd/server/enrich.go +++ b/comp/dogstatsd/server/enrich.go @@ -37,12 +37,13 @@ type enrichConfig struct { } // extractTagsMetadata returns tags (client tags + host tag) and information needed to query tagger (origins, cardinality). -func extractTagsMetadata(tags []string, originFromUDS string, originFromMsg []byte, externalData origindetection.ExternalData, conf enrichConfig) ([]string, string, taggertypes.OriginInfo, metrics.MetricSource) { +func extractTagsMetadata(tags []string, originFromUDS string, localData origindetection.LocalData, externalData origindetection.ExternalData, conf enrichConfig) ([]string, string, taggertypes.OriginInfo, metrics.MetricSource) { host := conf.defaultHostname metricSource := metrics.MetricSourceDogstatsd origin := taggertypes.OriginInfo{ ContainerIDFromSocket: originFromUDS, - ContainerID: string(originFromMsg), + ContainerID: localData.ContainerID, + LocalData: localData, ExternalData: externalData, ProductOrigin: origindetection.ProductOriginDogStatsD, } @@ -112,7 +113,7 @@ func tsToFloatForSamples(ts time.Time) float64 { func enrichMetricSample(dest []metrics.MetricSample, ddSample dogstatsdMetricSample, origin string, listenerID string, conf enrichConfig) []metrics.MetricSample { metricName := ddSample.name - tags, hostnameFromTags, extractedOrigin, metricSource := extractTagsMetadata(ddSample.tags, origin, ddSample.containerID, ddSample.externalData, conf) + tags, hostnameFromTags, extractedOrigin, metricSource := extractTagsMetadata(ddSample.tags, origin, ddSample.localData, ddSample.externalData, conf) if !isExcluded(metricName, conf.metricPrefix, conf.metricPrefixBlacklist) { metricName = conf.metricPrefix + metricName @@ -192,7 +193,7 @@ func enrichEventAlertType(dogstatsdAlertType alertType) metricsevent.AlertType { } func enrichEvent(event dogstatsdEvent, origin string, conf enrichConfig) *metricsevent.Event { - tags, hostnameFromTags, extractedOrigin, _ := extractTagsMetadata(event.tags, origin, event.containerID, event.externalData, conf) + tags, hostnameFromTags, extractedOrigin, _ := extractTagsMetadata(event.tags, origin, event.localData, event.externalData, conf) enrichedEvent := &metricsevent.Event{ Title: event.title, @@ -229,7 +230,7 @@ func enrichServiceCheckStatus(status serviceCheckStatus) servicecheck.ServiceChe } func enrichServiceCheck(serviceCheck dogstatsdServiceCheck, origin string, conf enrichConfig) *servicecheck.ServiceCheck { - tags, hostnameFromTags, extractedOrigin, _ := extractTagsMetadata(serviceCheck.tags, origin, serviceCheck.containerID, serviceCheck.externalData, conf) + tags, hostnameFromTags, extractedOrigin, _ := extractTagsMetadata(serviceCheck.tags, origin, serviceCheck.localData, serviceCheck.externalData, conf) enrichedServiceCheck := &servicecheck.ServiceCheck{ CheckName: serviceCheck.name, diff --git a/comp/dogstatsd/server/enrich_bench_test.go b/comp/dogstatsd/server/enrich_bench_test.go index bd0852429d5572..2d4626fd5b7432 100644 --- a/comp/dogstatsd/server/enrich_bench_test.go +++ b/comp/dogstatsd/server/enrich_bench_test.go @@ -35,7 +35,7 @@ func BenchmarkExtractTagsMetadata(b *testing.B) { sb.ResetTimer() for n := 0; n < sb.N; n++ { - tags, _, _, _ = extractTagsMetadata(baseTags, "", []byte{}, origindetection.ExternalData{}, conf) + tags, _, _, _ = extractTagsMetadata(baseTags, "", origindetection.LocalData{}, origindetection.ExternalData{}, conf) } }) } diff --git a/comp/dogstatsd/server/enrich_test.go b/comp/dogstatsd/server/enrich_test.go index 782c0ab69c1195..a9d0e2ba76e1e0 100644 --- a/comp/dogstatsd/server/enrich_test.go +++ b/comp/dogstatsd/server/enrich_test.go @@ -1113,6 +1113,7 @@ func TestEnrichTags(t *testing.T) { tags []string originFromUDS string originFromMsg []byte + localData origindetection.LocalData externalData origindetection.ExternalData conf enrichConfig } @@ -1128,6 +1129,7 @@ func TestEnrichTags(t *testing.T) { name: "empty tags, host=foo", args: args{ originFromUDS: "", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1144,6 +1146,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod"}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1160,6 +1163,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: nil, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1176,6 +1180,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "my-id")}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1192,6 +1197,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "none")}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1208,6 +1214,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "42")}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1224,6 +1231,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "42"), CardinalityTagPrefix + types.HighCardinalityString}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1240,6 +1248,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "42"), CardinalityTagPrefix + types.OrchestratorCardinalityString}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1256,6 +1265,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "42"), CardinalityTagPrefix + types.LowCardinalityString}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1272,6 +1282,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "42"), CardinalityTagPrefix + types.UnknownCardinalityString}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1288,6 +1299,7 @@ func TestEnrichTags(t *testing.T) { args: args{ tags: []string{"env:prod", fmt.Sprintf("%s%s", entityIDTagPrefix, "42"), CardinalityTagPrefix}, originFromUDS: "originID", + localData: origindetection.LocalData{}, externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", @@ -1305,14 +1317,24 @@ func TestEnrichTags(t *testing.T) { tags: []string{"env:prod", "dd.internal.entity_id:pod-uid"}, originFromUDS: "originID", originFromMsg: []byte("container-id"), - externalData: origindetection.ExternalData{}, + localData: origindetection.LocalData{ + ContainerID: "container-id", + }, + externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", }, }, - wantedTags: []string{"env:prod"}, - wantedHost: "foo", - wantedOrigin: taggertypes.OriginInfo{ContainerIDFromSocket: "originID", PodUID: "pod-uid", ContainerID: "container-id"}, + wantedTags: []string{"env:prod"}, + wantedHost: "foo", + wantedOrigin: taggertypes.OriginInfo{ + ContainerIDFromSocket: "originID", + PodUID: "pod-uid", + ContainerID: "container-id", + LocalData: origindetection.LocalData{ + ContainerID: "container-id", + }, + }, wantedMetricSource: metrics.MetricSourceDogstatsd, }, { @@ -1321,14 +1343,23 @@ func TestEnrichTags(t *testing.T) { tags: []string{"env:prod"}, originFromUDS: "originID", originFromMsg: []byte("container-id"), - externalData: origindetection.ExternalData{}, + localData: origindetection.LocalData{ + ContainerID: "container-id", + }, + externalData: origindetection.ExternalData{}, conf: enrichConfig{ defaultHostname: "foo", }, }, - wantedTags: []string{"env:prod"}, - wantedHost: "foo", - wantedOrigin: taggertypes.OriginInfo{ContainerIDFromSocket: "originID", ContainerID: "container-id"}, + wantedTags: []string{"env:prod"}, + wantedHost: "foo", + wantedOrigin: taggertypes.OriginInfo{ + ContainerIDFromSocket: "originID", + ContainerID: "container-id", + LocalData: origindetection.LocalData{ + ContainerID: "container-id", + }, + }, wantedMetricSource: metrics.MetricSourceDogstatsd, }, { @@ -1347,11 +1378,12 @@ func TestEnrichTags(t *testing.T) { }, wantedTags: []string{"env:prod"}, wantedHost: "foo", - wantedOrigin: taggertypes.OriginInfo{ExternalData: origindetection.ExternalData{ - Init: false, - ContainerName: "container_name", - PodUID: "pod_uid", - }}, + wantedOrigin: taggertypes.OriginInfo{ + ExternalData: origindetection.ExternalData{ + Init: false, + ContainerName: "container_name", + PodUID: "pod_uid", + }}, wantedMetricSource: metrics.MetricSourceDogstatsd, }, { @@ -1360,6 +1392,9 @@ func TestEnrichTags(t *testing.T) { tags: []string{"env:prod", "dd.internal.entity_id:pod-uid"}, originFromUDS: "originID", originFromMsg: []byte("container-id"), + localData: origindetection.LocalData{ + ContainerID: "container-id", + }, externalData: origindetection.ExternalData{ Init: false, ContainerName: "container_name", @@ -1375,6 +1410,9 @@ func TestEnrichTags(t *testing.T) { ContainerIDFromSocket: "originID", PodUID: "pod-uid", ContainerID: "container-id", + LocalData: origindetection.LocalData{ + ContainerID: "container-id", + }, ExternalData: origindetection.ExternalData{ Init: false, ContainerName: "container_name", @@ -1388,7 +1426,7 @@ func TestEnrichTags(t *testing.T) { tt.wantedOrigin.ProductOrigin = origindetection.ProductOriginDogStatsD t.Run(tt.name, func(t *testing.T) { - tags, host, origin, metricSource := extractTagsMetadata(tt.args.tags, tt.args.originFromUDS, tt.args.originFromMsg, tt.args.externalData, tt.args.conf) + tags, host, origin, metricSource := extractTagsMetadata(tt.args.tags, tt.args.originFromUDS, tt.args.localData, tt.args.externalData, tt.args.conf) assert.Equal(t, tt.wantedTags, tags) assert.Equal(t, tt.wantedHost, host) assert.Equal(t, tt.wantedOrigin, origin) @@ -1436,7 +1474,7 @@ func TestEnrichTagsWithJMXCheckName(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tags, _, _, metricSource := extractTagsMetadata(tt.tags, "", []byte{}, origindetection.ExternalData{}, enrichConfig{}) + tags, _, _, metricSource := extractTagsMetadata(tt.tags, "", origindetection.LocalData{}, origindetection.ExternalData{}, enrichConfig{}) assert.Equal(t, tt.wantedTags, tags) assert.Equal(t, tt.wantedMetricSource, metricSource) assert.NotContains(t, tags, tt.jmxCheckName) diff --git a/comp/dogstatsd/server/parse.go b/comp/dogstatsd/server/parse.go index 4b091e5710122a..e245d14e859b77 100644 --- a/comp/dogstatsd/server/parse.go +++ b/comp/dogstatsd/server/parse.go @@ -48,11 +48,6 @@ var ( // externalDataPrefix is the prefix for a common field which contains the external data for Origin Detection. externalDataPrefix = []byte("e:") - - // containerIDPrefix is the prefix for a notation holding the sender's container Inode in the containerIDField - containerIDPrefix = []byte("ci-") - // inodePrefix is the prefix for a notation holding the sender's container Inode in the containerIDField - inodePrefix = []byte("in-") ) // parser parses dogstatsd messages @@ -177,7 +172,7 @@ func (p *parser) parseMetricSample(message []byte) (dogstatsdMetricSample, error sampleRate := 1.0 var tags []string - var containerID []byte + var localData origindetection.LocalData var externalData origindetection.ExternalData var optionalField []byte var timestamp time.Time @@ -206,9 +201,17 @@ func (p *parser) parseMetricSample(message []byte) (dogstatsdMetricSample, error return dogstatsdMetricSample{}, fmt.Errorf("dogstatsd timestamp should be > 0") } timestamp = time.Unix(ts, 0) - // container ID + // local data case p.dsdOriginEnabled && bytes.HasPrefix(optionalField, localDataPrefix): - containerID = p.resolveContainerIDFromLocalData(optionalField) + rawLocalData := string(optionalField[len(localDataPrefix):]) + localData, err = origindetection.ParseLocalData(rawLocalData) + if err != nil { + return dogstatsdMetricSample{}, fmt.Errorf("failed to parse OriginInfo.LocalData %s: %v", rawLocalData, err) + } + // If the container ID is not set in the Local Data, we try to resolve it from the cgroupv2 inode. + if localData.ContainerID == "" { + localData.ContainerID = p.resolveContainerIDFromInode(localData.Inode) + } // external data case p.dsdOriginEnabled && bytes.HasPrefix(optionalField, externalDataPrefix): rawExternalData := string(optionalField[len(externalDataPrefix):]) @@ -227,7 +230,8 @@ func (p *parser) parseMetricSample(message []byte) (dogstatsdMetricSample, error metricType: metricType, sampleRate: sampleRate, tags: tags, - containerID: containerID, + containerID: localData.ContainerID, + localData: localData, externalData: externalData, ts: timestamp, }, nil @@ -270,66 +274,14 @@ func (p *parser) parseFloat64List(rawFloats []byte) ([]float64, error) { return values, nil } -// resolveContainerIDFromLocalData returns the container ID for the given Local Data. -// The Local Data is a list that can contain one or two (split by a ',') of either: -// * "ci-" for the container ID. -// * "in-" for the cgroupv2 inode. -// Possible values: -// * "" -// * "ci-" -// * "ci-,in-" -func (p *parser) resolveContainerIDFromLocalData(rawLocalData []byte) []byte { - // Remove prefix from Local Data - localData := rawLocalData[len(localDataPrefix):] - - var containerID []byte - var containerIDFromInode []byte - - if bytes.Contains(localData, []byte(",")) { - // The Local Data can contain a list - items := bytes.Split(localData, []byte{','}) - for _, item := range items { - if bytes.HasPrefix(item, containerIDPrefix) { - containerID = item[len(containerIDPrefix):] - } else if bytes.HasPrefix(item, inodePrefix) { - containerIDFromInode = p.resolveContainerIDFromInode(item[len(inodePrefix):]) - } - } - if containerID == nil { - containerID = containerIDFromInode - } - } else { - // The Local Data can contain a single value - if bytes.HasPrefix(localData, containerIDPrefix) { // Container ID with new format: ci- - containerID = localData[len(containerIDPrefix):] - } else if bytes.HasPrefix(localData, inodePrefix) { // Cgroupv2 inode format: in- - containerID = p.resolveContainerIDFromInode(localData[len(inodePrefix):]) - } else { // Container ID with old format: - containerID = localData - } - } - - if containerID == nil { - log.Debugf("Could not parse container ID from Local Data: %s", localData) - } - - return containerID -} - // resolveContainerIDFromInode returns the container ID for the given cgroupv2 inode. -func (p *parser) resolveContainerIDFromInode(inode []byte) []byte { - inodeField, err := strconv.ParseUint(string(inode), 10, 64) - if err != nil { - log.Debugf("Failed to parse inode from %s, got %v", inode, err) - return nil - } - - containerID, err := p.provider.GetMetaCollector().GetContainerIDForInode(inodeField, cacheValidity) +func (p *parser) resolveContainerIDFromInode(inode uint64) string { + containerID, err := p.provider.GetMetaCollector().GetContainerIDForInode(inode, cacheValidity) if err != nil { log.Debugf("Failed to get container ID, got %v", err) - return nil + return "" } - return []byte(containerID) + return containerID } // the std API does not have methods to do []byte => float parsing diff --git a/comp/dogstatsd/server/parse_events.go b/comp/dogstatsd/server/parse_events.go index 784844a9f90609..1d933f2350f957 100644 --- a/comp/dogstatsd/server/parse_events.go +++ b/comp/dogstatsd/server/parse_events.go @@ -40,7 +40,9 @@ type dogstatsdEvent struct { alertType alertType tags []string // containerID represents the container ID of the sender (optional). - containerID []byte + containerID string + // localData is used for Origin Detection + localData origindetection.LocalData // externalData is used for Origin Detection externalData origindetection.ExternalData } @@ -167,7 +169,12 @@ func (p *parser) applyEventOptionalField(event dogstatsdEvent, optionalField []b case bytes.HasPrefix(optionalField, eventTagsPrefix): newEvent.tags = p.parseTags(optionalField[len(eventTagsPrefix):]) case p.dsdOriginEnabled && bytes.HasPrefix(optionalField, localDataPrefix): - newEvent.containerID = p.resolveContainerIDFromLocalData(optionalField) + newEvent.localData, err = origindetection.ParseLocalData(string(optionalField[len(localDataPrefix):])) + // If the container ID is not set in the Local Data, we try to resolve it from the cgroupv2 inode. + if newEvent.localData.ContainerID == "" { + newEvent.localData.ContainerID = p.resolveContainerIDFromInode(newEvent.localData.Inode) + } + newEvent.containerID = newEvent.localData.ContainerID case p.dsdOriginEnabled && bytes.HasPrefix(optionalField, externalDataPrefix): newEvent.externalData, err = origindetection.ParseExternalData(string(optionalField[len(externalDataPrefix):])) } diff --git a/comp/dogstatsd/server/parse_metrics.go b/comp/dogstatsd/server/parse_metrics.go index adc5d80fce2704..2d8373882e5cdd 100644 --- a/comp/dogstatsd/server/parse_metrics.go +++ b/comp/dogstatsd/server/parse_metrics.go @@ -48,7 +48,9 @@ type dogstatsdMetricSample struct { sampleRate float64 tags []string // containerID represents the container ID of the sender (optional). - containerID []byte + containerID string + // localData is used for Origin Detection + localData origindetection.LocalData // externalData is used for Origin Detection externalData origindetection.ExternalData // timestamp read in the message if any diff --git a/comp/dogstatsd/server/parse_metrics_test.go b/comp/dogstatsd/server/parse_metrics_test.go index 4d6d80c71911c9..52007aeb9a8491 100644 --- a/comp/dogstatsd/server/parse_metrics_test.go +++ b/comp/dogstatsd/server/parse_metrics_test.go @@ -573,7 +573,7 @@ func TestParseContainerID(t *testing.T) { // Testing with a container ID sample, err := parseMetricSample(t, cfg, []byte("metric:1234|g|c:1234567890abcdef")) require.NoError(t, err) - assert.Equal(t, []byte("1234567890abcdef"), sample.containerID) + assert.Equal(t, "1234567890abcdef", sample.containerID) // Testing with an Inode deps := newServerDeps(t, fx.Replace(config.MockParams{Overrides: cfg})) @@ -590,5 +590,5 @@ func TestParseContainerID(t *testing.T) { sample, err = parseMetricSample(t, cfg, []byte("metric:1234|g|c:in-1234567890")) require.NoError(t, err) - assert.Equal(t, []byte("1234567890abcdef"), sample.containerID) + assert.Equal(t, "1234567890abcdef", sample.containerID) } diff --git a/comp/dogstatsd/server/parse_service_checks.go b/comp/dogstatsd/server/parse_service_checks.go index 2019c2687f7507..bfe8b9e351595d 100644 --- a/comp/dogstatsd/server/parse_service_checks.go +++ b/comp/dogstatsd/server/parse_service_checks.go @@ -31,7 +31,9 @@ type dogstatsdServiceCheck struct { message string tags []string // containerID represents the container ID of the sender (optional). - containerID []byte + containerID string + // localData is used for Origin Detection + localData origindetection.LocalData // externalData is used for Origin Detection externalData origindetection.ExternalData } @@ -101,7 +103,12 @@ func (p *parser) applyServiceCheckOptionalField(serviceCheck dogstatsdServiceChe case bytes.HasPrefix(optionalField, serviceCheckMessagePrefix): newServiceCheck.message = string(optionalField[len(serviceCheckMessagePrefix):]) case p.dsdOriginEnabled && bytes.HasPrefix(optionalField, localDataPrefix): - newServiceCheck.containerID = p.resolveContainerIDFromLocalData(optionalField) + newServiceCheck.localData, err = origindetection.ParseLocalData(string(optionalField[len(localDataPrefix):])) + // If the container ID is not set in the Local Data, we try to resolve it from the cgroupv2 inode. + if newServiceCheck.localData.ContainerID == "" { + newServiceCheck.localData.ContainerID = p.resolveContainerIDFromInode(newServiceCheck.localData.Inode) + } + newServiceCheck.containerID = newServiceCheck.localData.ContainerID case p.dsdOriginEnabled && bytes.HasPrefix(optionalField, externalDataPrefix): newServiceCheck.externalData, err = origindetection.ParseExternalData(string(optionalField[len(externalDataPrefix):])) } diff --git a/comp/dogstatsd/server/parse_test.go b/comp/dogstatsd/server/parse_test.go index 67dd3e706fb028..6e207e1efa689c 100644 --- a/comp/dogstatsd/server/parse_test.go +++ b/comp/dogstatsd/server/parse_test.go @@ -112,104 +112,22 @@ func TestUnsafeParseInt(t *testing.T) { assert.Equal(t, integer, unsafeInteger) } -func TestResolveContainerIDFromLocalData(t *testing.T) { - const ( - localDataPrefix = "c:" - containerIDPrefix = "ci-" - inodePrefix = "in-" - containerID = "abcdef" - containerInode = "4242" - ) - +func TestResolveContainerIDFromInode(t *testing.T) { deps := newServerDeps(t) stringInternerTelemetry := newSiTelemetry(false, deps.Telemetry) p := newParser(deps.Config, newFloat64ListPool(deps.Telemetry), 1, deps.WMeta, stringInternerTelemetry) - // Mock the provider to resolve the container ID from the inode mockProvider := mock.NewMetricsProvider() - containerInodeUint, _ := strconv.ParseUint(containerInode, 10, 64) mockProvider.RegisterMetaCollector(&mock.MetaCollector{ CIDFromInode: map[uint64]string{ - containerInodeUint: containerID, + uint64(1234): "abcdef", }, }) p.provider = mockProvider - tests := []struct { - name string - input []byte - expected []byte - }{ - { - name: "Empty LocalData", - input: []byte(localDataPrefix), - expected: []byte{}, - }, - { - name: "LocalData with new container ID", - input: []byte(localDataPrefix + containerIDPrefix + containerID), - expected: []byte(containerID), - }, - { - name: "LocalData with old container ID format", - input: []byte(localDataPrefix + containerID), - expected: []byte(containerID), - }, - { - name: "LocalData with inode", - input: []byte(localDataPrefix + inodePrefix + containerInode), - expected: []byte(containerID), - }, - { - name: "LocalData with invalid inode", - input: []byte(localDataPrefix + inodePrefix + "invalid"), - expected: []byte(nil), - }, - { - name: "LocalData as a list", - input: []byte(localDataPrefix + containerIDPrefix + containerID + "," + inodePrefix + containerInode), - expected: []byte(containerID), - }, - { - name: "LocalData as a list with only inode", - input: []byte(localDataPrefix + inodePrefix + containerInode), - expected: []byte(containerID), - }, - { - name: "LocalData as a list with only container ID", - input: []byte(localDataPrefix + containerIDPrefix + containerID), - expected: []byte(containerID), - }, - { - name: "LocalData as a list with only inode with trailing comma", - input: []byte(localDataPrefix + inodePrefix + containerInode + ","), - expected: []byte(containerID), - }, - { - name: "LocalData as a list with only container ID with trailing comma", - input: []byte(localDataPrefix + containerIDPrefix + containerID + ","), - expected: []byte(containerID), - }, - { - name: "LocalData as a list with only inode surrounded by commas", - input: []byte(localDataPrefix + "," + inodePrefix + containerInode + ","), // This is an invalid format, but we should still be able to extract the container ID - expected: []byte(containerID), - }, - { - name: "LocalData as a list with only inode surrounded by commas", - input: []byte(localDataPrefix + "," + containerIDPrefix + containerID + ","), // This is an invalid format, but we should still be able to extract the container ID - expected: []byte(containerID), - }, - { - name: "LocalData as an invalid list", - input: []byte(localDataPrefix + ","), - expected: []byte(nil), - }, - } + containerID := p.resolveContainerIDFromInode(uint64(1234)) + assert.Equal(t, "abcdef", containerID) - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expected, p.resolveContainerIDFromLocalData(tc.input)) - }) - } + unsetInode := p.resolveContainerIDFromInode(uint64(0)) + assert.Equal(t, "", unsetInode) } diff --git a/pkg/tagger/types/types.go b/pkg/tagger/types/types.go index 0d0dfe9a015319..4077bee1b561e1 100644 --- a/pkg/tagger/types/types.go +++ b/pkg/tagger/types/types.go @@ -13,6 +13,7 @@ type OriginInfo struct { ContainerIDFromSocket string // ContainerIDFromSocket is the origin resolved using Unix Domain Socket. PodUID string // PodUID is the origin resolved from the Kubernetes Pod UID. ContainerID string // ContainerID is the origin resolved from the container ID. + LocalData origindetection.LocalData // LocalData is the local data list. ExternalData origindetection.ExternalData // ExternalData is the external data list. Cardinality string // Cardinality is the cardinality of the resolved origin. ProductOrigin origindetection.ProductOrigin // ProductOrigin is the product that sent the origin information.