From 1b8ad165889a5664b86030e37f5c9c72a3383b4e Mon Sep 17 00:00:00 2001 From: Lisa Guo Date: Wed, 20 Nov 2024 15:02:59 -0500 Subject: [PATCH] [BugFix] Set EKS.Cluster and K8s.Cluster without modifying global entity attribute map to prevent concurrent map writes (#1434) --- plugins/outputs/cloudwatch/convert_otel.go | 39 +-- .../outputs/cloudwatch/convert_otel_test.go | 250 ----------------- .../entityattributes/entityattributes.go | 56 +++- .../entityattributes/entityattributes_test.go | 257 ++++++++++++++++++ 4 files changed, 307 insertions(+), 295 deletions(-) create mode 100644 plugins/processors/awsentity/entityattributes/entityattributes_test.go diff --git a/plugins/outputs/cloudwatch/convert_otel.go b/plugins/outputs/cloudwatch/convert_otel.go index a318eaa265..a438bc0890 100644 --- a/plugins/outputs/cloudwatch/convert_otel.go +++ b/plugins/outputs/cloudwatch/convert_otel.go @@ -170,7 +170,7 @@ func ConvertOtelMetric(m pmetric.Metric, entity cloudwatch.Entity) []*aggregatio func ConvertOtelMetrics(m pmetric.Metrics) []*aggregationDatum { datums := make([]*aggregationDatum, 0, m.DataPointCount()) for i := 0; i < m.ResourceMetrics().Len(); i++ { - entity := fetchEntityFields(m.ResourceMetrics().At(i).Resource().Attributes()) + entity := entityattributes.CreateCloudWatchEntityFromAttributes(m.ResourceMetrics().At(i).Resource().Attributes()) scopeMetrics := m.ResourceMetrics().At(i).ScopeMetrics() for j := 0; j < scopeMetrics.Len(); j++ { metrics := scopeMetrics.At(j).Metrics() @@ -184,40 +184,3 @@ func ConvertOtelMetrics(m pmetric.Metrics) []*aggregationDatum { } return datums } - -func fetchEntityFields(resourceAttributes pcommon.Map) cloudwatch.Entity { - keyAttributesMap := map[string]*string{} - attributeMap := map[string]*string{} - platformType := "" - if platformTypeValue, ok := resourceAttributes.Get(entityattributes.AttributeEntityPlatformType); ok { - platformType = platformTypeValue.Str() - } - processEntityAttributes(entityattributes.GetKeyAttributeEntityShortNameMap(), keyAttributesMap, resourceAttributes) - processEntityAttributes(entityattributes.GetAttributeEntityShortNameMap(platformType), attributeMap, resourceAttributes) - removeEntityFields(resourceAttributes) - if _, ok := keyAttributesMap[entityattributes.AwsAccountId]; !ok { - return cloudwatch.Entity{} - } - return cloudwatch.Entity{ - KeyAttributes: keyAttributesMap, - Attributes: attributeMap, - } -} - -// processEntityAttributes fetches the fields with entity prefix and creates an entity to be sent at the PutMetricData call. -func processEntityAttributes(entityMap map[string]string, targetMap map[string]*string, mutableResourceAttributes pcommon.Map) { - for entityField, shortName := range entityMap { - if val, ok := mutableResourceAttributes.Get(entityField); ok { - if strVal := val.Str(); strVal != "" { - targetMap[shortName] = aws.String(strVal) - } - } - } -} - -// removeEntityFields so that it is not tagged as a dimension, and reduces the size of the PMD payload. -func removeEntityFields(mutableResourceAttributes pcommon.Map) { - mutableResourceAttributes.RemoveIf(func(s string, _ pcommon.Value) bool { - return strings.HasPrefix(s, entityattributes.AWSEntityPrefix) - }) -} diff --git a/plugins/outputs/cloudwatch/convert_otel_test.go b/plugins/outputs/cloudwatch/convert_otel_test.go index 458d72de3a..1525fab186 100644 --- a/plugins/outputs/cloudwatch/convert_otel_test.go +++ b/plugins/outputs/cloudwatch/convert_otel_test.go @@ -242,256 +242,6 @@ func TestConvertOtelMetrics_Entity(t *testing.T) { } -func TestProcessAndRemoveEntityAttributes(t *testing.T) { - testCases := []struct { - name string - resourceAttributes map[string]any - wantedAttributes map[string]*string - leftoverAttributes map[string]any - }{ - { - name: "key_attributes", - resourceAttributes: map[string]any{ - entityattributes.AttributeEntityServiceName: "my-service", - entityattributes.AttributeEntityDeploymentEnvironment: "my-environment", - }, - wantedAttributes: map[string]*string{ - entityattributes.ServiceName: aws.String("my-service"), - entityattributes.DeploymentEnvironment: aws.String("my-environment"), - }, - leftoverAttributes: make(map[string]any), - }, - { - name: "non-key_attributes", - resourceAttributes: map[string]any{ - entityattributes.AttributeEntityCluster: "my-cluster", - entityattributes.AttributeEntityNamespace: "my-namespace", - entityattributes.AttributeEntityNode: "my-node", - entityattributes.AttributeEntityWorkload: "my-workload", - entityattributes.AttributeEntityPlatformType: "AWS::EKS", - }, - wantedAttributes: map[string]*string{ - entityattributes.EksCluster: aws.String("my-cluster"), - entityattributes.NamespaceField: aws.String("my-namespace"), - entityattributes.Node: aws.String("my-node"), - entityattributes.Workload: aws.String("my-workload"), - entityattributes.Platform: aws.String("AWS::EKS"), - }, - leftoverAttributes: make(map[string]any), - }, - { - name: "key_and_non_key_attributes", - resourceAttributes: map[string]any{ - entityattributes.AttributeEntityServiceName: "my-service", - entityattributes.AttributeEntityDeploymentEnvironment: "my-environment", - entityattributes.AttributeEntityCluster: "my-cluster", - entityattributes.AttributeEntityNamespace: "my-namespace", - entityattributes.AttributeEntityNode: "my-node", - entityattributes.AttributeEntityWorkload: "my-workload", - entityattributes.AttributeEntityPlatformType: "K8s", - }, - wantedAttributes: map[string]*string{ - entityattributes.ServiceName: aws.String("my-service"), - entityattributes.DeploymentEnvironment: aws.String("my-environment"), - entityattributes.K8sCluster: aws.String("my-cluster"), - entityattributes.NamespaceField: aws.String("my-namespace"), - entityattributes.Node: aws.String("my-node"), - entityattributes.Workload: aws.String("my-workload"), - entityattributes.Platform: aws.String("K8s"), - }, - leftoverAttributes: make(map[string]any), - }, - { - name: "key_and_non_key_attributes_plus_extras", - resourceAttributes: map[string]any{ - "extra_attribute": "extra_value", - entityattributes.AttributeEntityServiceName: "my-service", - entityattributes.AttributeEntityDeploymentEnvironment: "my-environment", - entityattributes.AttributeEntityCluster: "my-cluster", - entityattributes.AttributeEntityNamespace: "my-namespace", - entityattributes.AttributeEntityNode: "my-node", - entityattributes.AttributeEntityWorkload: "my-workload", - entityattributes.AttributeEntityPlatformType: "K8s", - }, - wantedAttributes: map[string]*string{ - entityattributes.ServiceName: aws.String("my-service"), - entityattributes.DeploymentEnvironment: aws.String("my-environment"), - entityattributes.K8sCluster: aws.String("my-cluster"), - entityattributes.NamespaceField: aws.String("my-namespace"), - entityattributes.Node: aws.String("my-node"), - entityattributes.Workload: aws.String("my-workload"), - entityattributes.Platform: aws.String("K8s"), - }, - leftoverAttributes: map[string]any{ - "extra_attribute": "extra_value", - }, - }, - { - name: "key_and_non_key_attributes_plus_unsupported_entity_field", - resourceAttributes: map[string]any{ - entityattributes.AWSEntityPrefix + "not.real.values": "unsupported", - entityattributes.AttributeEntityServiceName: "my-service", - entityattributes.AttributeEntityDeploymentEnvironment: "my-environment", - entityattributes.AttributeEntityCluster: "my-cluster", - entityattributes.AttributeEntityNamespace: "my-namespace", - entityattributes.AttributeEntityNode: "my-node", - entityattributes.AttributeEntityWorkload: "my-workload", - entityattributes.AttributeEntityPlatformType: "AWS::EKS", - }, - wantedAttributes: map[string]*string{ - entityattributes.ServiceName: aws.String("my-service"), - entityattributes.DeploymentEnvironment: aws.String("my-environment"), - entityattributes.EksCluster: aws.String("my-cluster"), - entityattributes.NamespaceField: aws.String("my-namespace"), - entityattributes.Node: aws.String("my-node"), - entityattributes.Workload: aws.String("my-workload"), - entityattributes.Platform: aws.String("AWS::EKS"), - }, - leftoverAttributes: map[string]any{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - attrs := pcommon.NewMap() - err := attrs.FromRaw(tc.resourceAttributes) - - // resetting fields for current test case - entityAttrMap := []map[string]string{entityattributes.GetKeyAttributeEntityShortNameMap()} - platformType := "" - if platformTypeValue, ok := attrs.Get(entityattributes.AttributeEntityPlatformType); ok { - platformType = platformTypeValue.Str() - } - if platformType != "" { - delete(entityattributes.GetAttributeEntityShortNameMap(platformType), entityattributes.AttributeEntityCluster) - entityAttrMap = append(entityAttrMap, entityattributes.GetAttributeEntityShortNameMap(platformType)) - } - assert.Nil(t, err) - targetMap := make(map[string]*string) - for _, entityMap := range entityAttrMap { - processEntityAttributes(entityMap, targetMap, attrs) - } - removeEntityFields(attrs) - assert.Equal(t, tc.leftoverAttributes, attrs.AsRaw()) - assert.Equal(t, tc.wantedAttributes, targetMap) - }) - } -} - -func TestFetchEntityFields_WithoutAccountID(t *testing.T) { - resourceMetrics := pmetric.NewResourceMetrics() - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityType, "Service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityDeploymentEnvironment, "my-environment") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityServiceName, "my-service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityNode, "my-node") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityCluster, "my-cluster") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityNamespace, "my-namespace") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityWorkload, "my-workload") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityPlatformType, "AWS::EKS") - assert.Equal(t, 8, resourceMetrics.Resource().Attributes().Len()) - - expectedEntity := cloudwatch.Entity{ - KeyAttributes: nil, - Attributes: nil, - } - entity := fetchEntityFields(resourceMetrics.Resource().Attributes()) - assert.Equal(t, 0, resourceMetrics.Resource().Attributes().Len()) - assert.Equal(t, expectedEntity, entity) -} - -func TestFetchEntityFields_WithAccountID(t *testing.T) { - resourceMetrics := pmetric.NewResourceMetrics() - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityType, "Service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityDeploymentEnvironment, "my-environment") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityServiceName, "my-service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityNode, "my-node") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityCluster, "my-cluster") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityNamespace, "my-namespace") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityWorkload, "my-workload") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityPlatformType, "AWS::EKS") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityAwsAccountId, "123456789") - assert.Equal(t, 9, resourceMetrics.Resource().Attributes().Len()) - - expectedEntity := cloudwatch.Entity{ - KeyAttributes: map[string]*string{ - entityattributes.EntityType: aws.String(entityattributes.Service), - entityattributes.ServiceName: aws.String("my-service"), - entityattributes.DeploymentEnvironment: aws.String("my-environment"), - entityattributes.AwsAccountId: aws.String("123456789"), - }, - Attributes: map[string]*string{ - entityattributes.Node: aws.String("my-node"), - entityattributes.EksCluster: aws.String("my-cluster"), - entityattributes.NamespaceField: aws.String("my-namespace"), - entityattributes.Workload: aws.String("my-workload"), - entityattributes.Platform: aws.String("AWS::EKS"), - }, - } - entity := fetchEntityFields(resourceMetrics.Resource().Attributes()) - assert.Equal(t, 0, resourceMetrics.Resource().Attributes().Len()) - assert.Equal(t, expectedEntity, entity) -} - -func TestFetchEntityFieldsOnK8s(t *testing.T) { - entityMap := entityattributes.GetAttributeEntityShortNameMap("") - delete(entityMap, entityattributes.AttributeEntityCluster) - resourceMetrics := pmetric.NewResourceMetrics() - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityType, "Service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityDeploymentEnvironment, "my-environment") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityServiceName, "my-service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityNode, "my-node") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityCluster, "my-cluster") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityNamespace, "my-namespace") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityWorkload, "my-workload") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityPlatformType, "K8s") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityAwsAccountId, "123456789") - assert.Equal(t, 9, resourceMetrics.Resource().Attributes().Len()) - - expectedEntity := cloudwatch.Entity{ - KeyAttributes: map[string]*string{ - entityattributes.EntityType: aws.String(entityattributes.Service), - entityattributes.ServiceName: aws.String("my-service"), - entityattributes.DeploymentEnvironment: aws.String("my-environment"), - entityattributes.AwsAccountId: aws.String("123456789"), - }, - Attributes: map[string]*string{ - entityattributes.Node: aws.String("my-node"), - entityattributes.K8sCluster: aws.String("my-cluster"), - entityattributes.NamespaceField: aws.String("my-namespace"), - entityattributes.Workload: aws.String("my-workload"), - entityattributes.Platform: aws.String("K8s"), - }, - } - entity := fetchEntityFields(resourceMetrics.Resource().Attributes()) - assert.Equal(t, 0, resourceMetrics.Resource().Attributes().Len()) - assert.Equal(t, expectedEntity, entity) -} - -func TestFetchEntityFieldsOnEc2(t *testing.T) { - resourceMetrics := pmetric.NewResourceMetrics() - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityType, "Service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityDeploymentEnvironment, "my-environment") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityServiceName, "my-service") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityPlatformType, "AWS::EC2") - resourceMetrics.Resource().Attributes().PutStr(entityattributes.AttributeEntityAwsAccountId, "123456789") - assert.Equal(t, 5, resourceMetrics.Resource().Attributes().Len()) - - expectedEntity := cloudwatch.Entity{ - KeyAttributes: map[string]*string{ - entityattributes.EntityType: aws.String(entityattributes.Service), - entityattributes.ServiceName: aws.String("my-service"), - entityattributes.DeploymentEnvironment: aws.String("my-environment"), - entityattributes.AwsAccountId: aws.String("123456789"), - }, - Attributes: map[string]*string{ - entityattributes.Platform: aws.String("AWS::EC2"), - }, - } - entity := fetchEntityFields(resourceMetrics.Resource().Attributes()) - assert.Equal(t, 0, resourceMetrics.Resource().Attributes().Len()) - assert.Equal(t, expectedEntity, entity) -} - func TestInvalidMetric(t *testing.T) { m := pmetric.NewMetric() m.SetName("name") diff --git a/plugins/processors/awsentity/entityattributes/entityattributes.go b/plugins/processors/awsentity/entityattributes/entityattributes.go index 9638af83a5..4588695c41 100644 --- a/plugins/processors/awsentity/entityattributes/entityattributes.go +++ b/plugins/processors/awsentity/entityattributes/entityattributes.go @@ -3,6 +3,15 @@ package entityattributes +import ( + "strings" + + "github.com/aws/aws-sdk-go/aws" + "go.opentelemetry.io/collector/pdata/pcommon" + + "github.com/aws/amazon-cloudwatch-agent/sdk/service/cloudwatch" +) + const ( // The following are the possible values for EntityType config options @@ -80,16 +89,42 @@ var attributeEntityToShortNameMap = map[string]string{ AttributeEntityServiceNameSource: ServiceNameSource, } -func GetKeyAttributeEntityShortNameMap() map[string]string { - return keyAttributeEntityToShortNameMap +func CreateCloudWatchEntityFromAttributes(resourceAttributes pcommon.Map) cloudwatch.Entity { + keyAttributesMap := map[string]*string{} + attributeMap := map[string]*string{} + + // Process KeyAttributes and return empty entity if AwsAccountId is not found + processEntityAttributes(keyAttributeEntityToShortNameMap, keyAttributesMap, resourceAttributes) + if _, ok := keyAttributesMap[AwsAccountId]; !ok { + return cloudwatch.Entity{} + } + + // Process Attributes and add cluster attribute if on EKS/K8s + processEntityAttributes(attributeEntityToShortNameMap, attributeMap, resourceAttributes) + if platformTypeValue, ok := resourceAttributes.Get(AttributeEntityPlatformType); ok { + platformType := clusterType(platformTypeValue.Str()) + if clusterNameValue, ok := resourceAttributes.Get(AttributeEntityCluster); ok { + attributeMap[platformType] = aws.String(clusterNameValue.Str()) + } + } + + // Remove entity fields from attributes and return the entity + removeEntityFields(resourceAttributes) + return cloudwatch.Entity{ + KeyAttributes: keyAttributesMap, + Attributes: attributeMap, + } } -// Cluster attribute prefix could be either EKS or K8s. We set the field once at runtime. -func GetAttributeEntityShortNameMap(platformType string) map[string]string { - if _, ok := attributeEntityToShortNameMap[AttributeEntityCluster]; !ok { - attributeEntityToShortNameMap[AttributeEntityCluster] = clusterType(platformType) +// processEntityAttributes fetches the fields with entity prefix and creates an entity to be sent at the PutMetricData call. +func processEntityAttributes(entityMap map[string]string, targetMap map[string]*string, incomingResourceAttributes pcommon.Map) { + for entityField, shortName := range entityMap { + if val, ok := incomingResourceAttributes.Get(entityField); ok { + if strVal := val.Str(); strVal != "" { + targetMap[shortName] = aws.String(strVal) + } + } } - return attributeEntityToShortNameMap } func clusterType(platformType string) string { @@ -100,3 +135,10 @@ func clusterType(platformType string) string { } return "" } + +// removeEntityFields so that it is not tagged as a dimension, and reduces the size of the PMD payload. +func removeEntityFields(mutableResourceAttributes pcommon.Map) { + mutableResourceAttributes.RemoveIf(func(s string, _ pcommon.Value) bool { + return strings.HasPrefix(s, AWSEntityPrefix) + }) +} diff --git a/plugins/processors/awsentity/entityattributes/entityattributes_test.go b/plugins/processors/awsentity/entityattributes/entityattributes_test.go new file mode 100644 index 0000000000..8e5cca9db0 --- /dev/null +++ b/plugins/processors/awsentity/entityattributes/entityattributes_test.go @@ -0,0 +1,257 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package entityattributes + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + + "github.com/aws/amazon-cloudwatch-agent/sdk/service/cloudwatch" +) + +func TestProcessAndRemoveEntityAttributes(t *testing.T) { + testCases := []struct { + name string + resourceAttributes map[string]any + wantedAttributes map[string]*string + leftoverAttributes map[string]any + }{ + { + name: "key_attributes", + resourceAttributes: map[string]any{ + AttributeEntityServiceName: "my-service", + AttributeEntityDeploymentEnvironment: "my-environment", + }, + wantedAttributes: map[string]*string{ + ServiceName: aws.String("my-service"), + DeploymentEnvironment: aws.String("my-environment"), + }, + leftoverAttributes: make(map[string]any), + }, + { + name: "non-key_attributes", + resourceAttributes: map[string]any{ + AttributeEntityNamespace: "my-namespace", + AttributeEntityNode: "my-node", + AttributeEntityWorkload: "my-workload", + AttributeEntityPlatformType: "AWS::EKS", + }, + wantedAttributes: map[string]*string{ + NamespaceField: aws.String("my-namespace"), + Node: aws.String("my-node"), + Workload: aws.String("my-workload"), + Platform: aws.String("AWS::EKS"), + }, + leftoverAttributes: make(map[string]any), + }, + { + name: "key_and_non_key_attributes", + resourceAttributes: map[string]any{ + AttributeEntityServiceName: "my-service", + AttributeEntityDeploymentEnvironment: "my-environment", + AttributeEntityNamespace: "my-namespace", + AttributeEntityNode: "my-node", + AttributeEntityWorkload: "my-workload", + AttributeEntityPlatformType: "K8s", + }, + wantedAttributes: map[string]*string{ + ServiceName: aws.String("my-service"), + DeploymentEnvironment: aws.String("my-environment"), + NamespaceField: aws.String("my-namespace"), + Node: aws.String("my-node"), + Workload: aws.String("my-workload"), + Platform: aws.String("K8s"), + }, + leftoverAttributes: make(map[string]any), + }, + { + name: "key_and_non_key_attributes_plus_extras", + resourceAttributes: map[string]any{ + "extra_attribute": "extra_value", + AttributeEntityServiceName: "my-service", + AttributeEntityDeploymentEnvironment: "my-environment", + AttributeEntityNamespace: "my-namespace", + AttributeEntityNode: "my-node", + AttributeEntityWorkload: "my-workload", + AttributeEntityPlatformType: "K8s", + }, + wantedAttributes: map[string]*string{ + ServiceName: aws.String("my-service"), + DeploymentEnvironment: aws.String("my-environment"), + NamespaceField: aws.String("my-namespace"), + Node: aws.String("my-node"), + Workload: aws.String("my-workload"), + Platform: aws.String("K8s"), + }, + leftoverAttributes: map[string]any{ + "extra_attribute": "extra_value", + }, + }, + { + name: "key_and_non_key_attributes_plus_unsupported_entity_field", + resourceAttributes: map[string]any{ + AWSEntityPrefix + "not.real.values": "unsupported", + AttributeEntityServiceName: "my-service", + AttributeEntityDeploymentEnvironment: "my-environment", + AttributeEntityNamespace: "my-namespace", + AttributeEntityNode: "my-node", + AttributeEntityWorkload: "my-workload", + AttributeEntityPlatformType: "AWS::EKS", + }, + wantedAttributes: map[string]*string{ + ServiceName: aws.String("my-service"), + DeploymentEnvironment: aws.String("my-environment"), + NamespaceField: aws.String("my-namespace"), + Node: aws.String("my-node"), + Workload: aws.String("my-workload"), + Platform: aws.String("AWS::EKS"), + }, + leftoverAttributes: map[string]any{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + attrs := pcommon.NewMap() + err := attrs.FromRaw(tc.resourceAttributes) + + // resetting fields for current test case + entityAttrMap := []map[string]string{keyAttributeEntityToShortNameMap} + platformType := "" + if platformTypeValue, ok := attrs.Get(AttributeEntityPlatformType); ok { + platformType = platformTypeValue.Str() + } + if platformType != "" { + delete(attributeEntityToShortNameMap, AttributeEntityCluster) + entityAttrMap = append(entityAttrMap, attributeEntityToShortNameMap) + } + assert.Nil(t, err) + targetMap := make(map[string]*string) + for _, entityMap := range entityAttrMap { + processEntityAttributes(entityMap, targetMap, attrs) + } + removeEntityFields(attrs) + assert.Equal(t, tc.leftoverAttributes, attrs.AsRaw()) + assert.Equal(t, tc.wantedAttributes, targetMap) + }) + } +} + +func TestCreateCloudWatchEntityFromAttributes_WithoutAccountID(t *testing.T) { + resourceMetrics := pmetric.NewResourceMetrics() + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityType, "Service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityDeploymentEnvironment, "my-environment") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityServiceName, "my-service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityNode, "my-node") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityCluster, "my-cluster") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityNamespace, "my-namespace") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityWorkload, "my-workload") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityPlatformType, "AWS::EKS") + assert.Equal(t, 8, resourceMetrics.Resource().Attributes().Len()) + + expectedEntity := cloudwatch.Entity{ + KeyAttributes: nil, + Attributes: nil, + } + entity := CreateCloudWatchEntityFromAttributes(resourceMetrics.Resource().Attributes()) + assert.Equal(t, 8, resourceMetrics.Resource().Attributes().Len()) + assert.Equal(t, expectedEntity, entity) +} + +func TestCreateCloudWatchEntityFromAttributes_WithAccountID(t *testing.T) { + resourceMetrics := pmetric.NewResourceMetrics() + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityType, "Service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityDeploymentEnvironment, "my-environment") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityServiceName, "my-service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityNode, "my-node") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityCluster, "my-cluster") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityNamespace, "my-namespace") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityWorkload, "my-workload") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityPlatformType, "AWS::EKS") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityAwsAccountId, "123456789") + assert.Equal(t, 9, resourceMetrics.Resource().Attributes().Len()) + + expectedEntity := cloudwatch.Entity{ + KeyAttributes: map[string]*string{ + EntityType: aws.String(Service), + ServiceName: aws.String("my-service"), + DeploymentEnvironment: aws.String("my-environment"), + AwsAccountId: aws.String("123456789"), + }, + Attributes: map[string]*string{ + Node: aws.String("my-node"), + EksCluster: aws.String("my-cluster"), + NamespaceField: aws.String("my-namespace"), + Workload: aws.String("my-workload"), + Platform: aws.String("AWS::EKS"), + }, + } + entity := CreateCloudWatchEntityFromAttributes(resourceMetrics.Resource().Attributes()) + assert.Equal(t, 0, resourceMetrics.Resource().Attributes().Len()) + assert.Equal(t, expectedEntity, entity) +} + +func TestCreateCloudWatchEntityFromAttributesOnK8s(t *testing.T) { + entityMap := attributeEntityToShortNameMap + delete(entityMap, AttributeEntityCluster) + resourceMetrics := pmetric.NewResourceMetrics() + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityType, "Service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityDeploymentEnvironment, "my-environment") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityServiceName, "my-service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityNode, "my-node") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityCluster, "my-cluster") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityNamespace, "my-namespace") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityWorkload, "my-workload") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityPlatformType, "K8s") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityAwsAccountId, "123456789") + assert.Equal(t, 9, resourceMetrics.Resource().Attributes().Len()) + + expectedEntity := cloudwatch.Entity{ + KeyAttributes: map[string]*string{ + EntityType: aws.String(Service), + ServiceName: aws.String("my-service"), + DeploymentEnvironment: aws.String("my-environment"), + AwsAccountId: aws.String("123456789"), + }, + Attributes: map[string]*string{ + Node: aws.String("my-node"), + K8sCluster: aws.String("my-cluster"), + NamespaceField: aws.String("my-namespace"), + Workload: aws.String("my-workload"), + Platform: aws.String("K8s"), + }, + } + entity := CreateCloudWatchEntityFromAttributes(resourceMetrics.Resource().Attributes()) + assert.Equal(t, 0, resourceMetrics.Resource().Attributes().Len()) + assert.Equal(t, expectedEntity, entity) +} + +func TestCreateCloudWatchEntityFromAttributesOnEc2(t *testing.T) { + resourceMetrics := pmetric.NewResourceMetrics() + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityType, "Service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityDeploymentEnvironment, "my-environment") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityServiceName, "my-service") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityPlatformType, "AWS::EC2") + resourceMetrics.Resource().Attributes().PutStr(AttributeEntityAwsAccountId, "123456789") + assert.Equal(t, 5, resourceMetrics.Resource().Attributes().Len()) + + expectedEntity := cloudwatch.Entity{ + KeyAttributes: map[string]*string{ + EntityType: aws.String(Service), + ServiceName: aws.String("my-service"), + DeploymentEnvironment: aws.String("my-environment"), + AwsAccountId: aws.String("123456789"), + }, + Attributes: map[string]*string{ + Platform: aws.String("AWS::EC2"), + }, + } + entity := CreateCloudWatchEntityFromAttributes(resourceMetrics.Resource().Attributes()) + assert.Equal(t, 0, resourceMetrics.Resource().Attributes().Len()) + assert.Equal(t, expectedEntity, entity) +}