From 21aa0517140ffb8c315dc126609e4a0d01eca7bc Mon Sep 17 00:00:00 2001 From: Varun C Date: Mon, 18 Nov 2024 19:53:16 +0000 Subject: [PATCH 01/25] update config files --- test/cloudwatchlogs/resources/config_log.json | 2 +- .../resources/config_log_service_name.json | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 test/cloudwatchlogs/resources/config_log_service_name.json diff --git a/test/cloudwatchlogs/resources/config_log.json b/test/cloudwatchlogs/resources/config_log.json index 0cb8fbb4f..37b963b94 100644 --- a/test/cloudwatchlogs/resources/config_log.json +++ b/test/cloudwatchlogs/resources/config_log.json @@ -8,7 +8,7 @@ "files": { "collect_list": [ { - "file_path": "/tmp/cwagent_log_test.log", + "file_path": "/tmp/cwagent_log_test.log*", "log_group_name": "{instance_id}", "log_stream_name": "{instance_id}", "timezone": "UTC" diff --git a/test/cloudwatchlogs/resources/config_log_service_name.json b/test/cloudwatchlogs/resources/config_log_service_name.json new file mode 100644 index 000000000..0354796a6 --- /dev/null +++ b/test/cloudwatchlogs/resources/config_log_service_name.json @@ -0,0 +1,22 @@ +{ + "agent": { + "run_as_user": "root", + "debug": true + }, + "logs": { + "logs_collected": { + "files": { + "collect_list": [ + { + "file_path": "/tmp/cwagent_log_test.log*", + "log_group_name": "{instance_id}", + "log_stream_name": "{instance_id}", + "timezone": "UTC", + "service.name": "service-in-config", + "deployment.environment": "environment-in-config" + } + ] + } + } + } +} From 734f8e0caf7382f4377e2e6aa2b8db0a73caa4ad Mon Sep 17 00:00:00 2001 From: Varun C Date: Mon, 18 Nov 2024 19:57:47 +0000 Subject: [PATCH 02/25] Add integ test for Logs agent --- test/cloudwatchlogs/publish_logs_test.go | 297 ++++++++++++++++++++++- 1 file changed, 296 insertions(+), 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 7a5ca8831..2e27c2d87 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -6,6 +6,8 @@ package cloudwatchlogs import ( + "context" + "errors" "fmt" "log" "os" @@ -15,7 +17,13 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + "github.com/aws/aws-sdk-go-v2/service/ec2" + ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/aws/amazon-cloudwatch-agent-test/environment" @@ -28,10 +36,21 @@ const ( logLineId1 = "foo" logLineId2 = "bar" logFilePath = "/tmp/cwagent_log_test.log" // TODO: not sure how well this will work on Windows - sleepForFlush = 20 * time.Second // default flush interval is 5 seconds + sleepForFlush = 120 * time.Second // default flush interval is 5 seconds + retryWaitTime = 30 * time.Second configPathAutoRemoval = "resources/config_auto_removal.json" standardLogGroupClass = "STANDARD" infrequentAccessLogGroupClass = "INFREQUENT_ACCESS" + cwlPerfEndpoint = "https://logs.us-west-2.amazonaws.com" + pdxRegionalCode = "us-west-2" + + entityType = "@entity.KeyAttributes.Type" + entityName = "@entity.KeyAttributes.Name" + entityEnvironment = "@entity.KeyAttributes.Environment" + entityPlatform = "@entity.Attributes.PlatformType" + entityInstanceId = "@entity.Attributes.EC2.InstanceId" + serviceName = "service.name" + deploymentName = "deployment.name" ) var ( @@ -70,6 +89,9 @@ var ( logGroupClass: types.LogGroupClassInfrequentAccess, }, } + rnf *types.ResourceNotFoundException + cwlClient *cloudwatchlogs.Client + ec2Client *ec2.Client ) type writeToCloudWatchTestInput struct { @@ -86,8 +108,29 @@ type cloudWatchLogGroupClassTestInput struct { logGroupClass types.LogGroupClass } +type expectedEntity struct { + entityType string + name string + environment string + platformType string + instanceId string +} + func init() { environment.RegisterEnvironmentMetaDataFlags() + awsCfg, err := config.LoadDefaultConfig( + context.Background(), + config.WithRegion(pdxRegionalCode), + ) + if err != nil { + fmt.Println("There was an error trying to load default config: ", err) + return + } + + cwlClient = cloudwatchlogs.NewFromConfig(awsCfg, func(o *cloudwatchlogs.Options) { + o.BaseEndpoint = aws.String(cwlPerfEndpoint) + }) + ec2Client = ec2.NewFromConfig(awsCfg) } // TestWriteLogsToCloudWatch writes N number of logs, and then validates that N logs @@ -139,6 +182,109 @@ func TestWriteLogsToCloudWatch(t *testing.T) { } } +// TestWriteLogsWithEntityInfo writes logs and validates that the +// log events are associated with entities from CloudWatch Logs +func TestWriteLogsWithEntityInfo(t *testing.T) { + instanceId := awsservice.GetInstanceId() + log.Printf("Found instance id %s", instanceId) + + // Define tags to create for EC2 test case + tagsToCreate := []ec2Types.Tag{ + { + Key: aws.String("service"), + Value: aws.String("service-test"), + }, + } + + testCases := map[string]struct { + agentConfigPath string + iterations int + useEC2Tag bool + expectedEntity expectedEntity + }{ + "IAMRole": { + agentConfigPath: filepath.Join("resources", "config_log.json"), + iterations: 1000, + expectedEntity: expectedEntity{ + entityType: "Service", + name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing + environment: "ec2:default", + platformType: "AWS::EC2", + instanceId: instanceId, + }, + }, + "EC2Tags": { + agentConfigPath: filepath.Join("resources", "config_log.json"), + iterations: 1000, + useEC2Tag: true, + expectedEntity: expectedEntity{ + entityType: "Service", + name: "service-test", //should match the value in tagsToCreate + environment: "ec2:default", + platformType: "AWS::EC2", + instanceId: instanceId, + }, + }, + "ServiceInConfig": { + agentConfigPath: filepath.Join("resources", "config_log_service_name.json"), + iterations: 1000, + expectedEntity: expectedEntity{ + entityType: "Service", + name: "service-in-config", //should match the service.name value in the config file + environment: "environment-in-config", //should match the deployment.environment value in the config file + platformType: "AWS::EC2", + instanceId: instanceId, + }, + }, + } + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Cleanup(func() { + // delete the log group/stream after each test case + awsservice.DeleteLogGroupAndStream(instanceId, instanceId) + + // delete EC2 tags added to the instance for the test + if testCase.useEC2Tag { + input := &ec2.DeleteTagsInput{ + Resources: []string{instanceId}, + Tags: tagsToCreate, + } + _, err := ec2Client.DeleteTags(context.TODO(), input) + assert.NoError(t, err) + } + }) + if testCase.useEC2Tag { + input := &ec2.CreateTagsInput{ + Resources: []string{instanceId}, + Tags: tagsToCreate, + } + _, err := ec2Client.CreateTags(context.TODO(), input) + assert.NoError(t, err) + } + id := uuid.New() + f, err := os.Create(logFilePath + "-" + id.String()) + if err != nil { + t.Fatalf("Error occurred creating log file for writing: %v", err) + } + common.DeleteFile(common.AgentLogFile) + common.TouchFile(common.AgentLogFile) + + common.CopyFile(testCase.agentConfigPath, configOutputPath) + + common.StartAgent(configOutputPath, true, false) + writeLogLines(t, f, testCase.iterations) + time.Sleep(sleepForFlush) + common.StopAgent() + end := time.Now() + + ValidateEntity(t, instanceId, instanceId, &end, testCase.expectedEntity) + + f.Close() + os.Remove(logFilePath + "-" + id.String()) + }) + } +} + // TestAutoRemovalStopAgent configures agent to monitor a file with auto removal on. // Then it restarts the agent. // Verify the file is NOT removed. @@ -332,3 +478,152 @@ func checkData(t *testing.T, start time.Time, lineCount int) { ) assert.NoError(t, err) } + +func ValidateEntity(t *testing.T, logGroup, logStream string, end *time.Time, expectedEntity expectedEntity) { + log.Printf("Validating entity for log group: %s, stream: %s", logGroup, logStream) + + logGroupInfo, err := getLogGroup() + for _, lg := range logGroupInfo { + if *lg.LogGroupName == logGroup { + log.Println("Log group " + *lg.LogGroupName + " exists") + break + } + } + assert.NoError(t, err) + begin := end.Add(-sleepForFlush * 4) + log.Printf("Query start time is " + begin.String() + " and end time is " + end.String()) + queryId, err := getLogQueryId(logGroup, &begin, end) + assert.NoError(t, err) + log.Printf("queryId is " + *queryId) + result, err := getQueryResult(queryId) + assert.NoError(t, err) + if !assert.NotZero(t, len(result)) { + return + } + requiredEntityFields := map[string]bool{ + entityType: false, + entityName: false, + entityEnvironment: false, + entityPlatform: false, + entityInstanceId: false, + } + for _, field := range result[0] { + switch aws.ToString(field.Field) { + case entityType: + requiredEntityFields[entityType] = true + assert.Equal(t, expectedEntity.entityType, aws.ToString(field.Value)) + case entityName: + requiredEntityFields[entityName] = true + assert.Equal(t, expectedEntity.name, aws.ToString(field.Value)) + case entityEnvironment: + requiredEntityFields[entityEnvironment] = true + assert.Equal(t, expectedEntity.environment, aws.ToString(field.Value)) + case entityPlatform: + requiredEntityFields[entityPlatform] = true + assert.Equal(t, expectedEntity.platformType, aws.ToString(field.Value)) + case entityInstanceId: + requiredEntityFields[entityInstanceId] = true + assert.Equal(t, expectedEntity.instanceId, aws.ToString(field.Value)) + } + fmt.Printf("%s: %s\n", aws.ToString(field.Field), aws.ToString(field.Value)) + } + allEntityFieldsFound := true + for field, value := range requiredEntityFields { + if !value { + log.Printf("Missing required entity field: %s", field) + allEntityFieldsFound = false + } + } + assert.True(t, allEntityFieldsFound) +} + +func getLogQueryId(logGroup string, since, until *time.Time) (*string, error) { + var queryId *string + params := &cloudwatchlogs.StartQueryInput{ + QueryString: aws.String("fields @message, @entity.KeyAttributes.Type, @entity.KeyAttributes.Name, @entity.KeyAttributes.Environment, @entity.Attributes.PlatformType, @entity.Attributes.EC2.InstanceId"), + LogGroupName: aws.String(logGroup), + } + if since != nil { + params.StartTime = aws.Int64(since.UnixMilli()) + } + if until != nil { + params.EndTime = aws.Int64(until.UnixMilli()) + } + attempts := 0 + + for { + output, err := cwlClient.StartQuery(context.Background(), params) + attempts += 1 + + if err != nil { + if errors.As(err, &rnf) && attempts <= awsservice.StandardRetries { + // The log group/stream hasn't been created yet, so wait and retry + time.Sleep(retryWaitTime) + continue + } + + // if the error is not a ResourceNotFoundException, we should fail here. + return queryId, err + } + queryId = output.QueryId + return queryId, err + } +} + +func getQueryResult(queryId *string) ([][]types.ResultField, error) { + attempts := 0 + var results [][]types.ResultField + params := &cloudwatchlogs.GetQueryResultsInput{ + QueryId: aws.String(*queryId), + } + for { + if attempts > awsservice.StandardRetries { + return results, errors.New("exceeded retry count") + } + result, err := cwlClient.GetQueryResults(context.Background(), params) + log.Printf("GetQueryResult status is: %v", result.Status) + attempts += 1 + if result.Status != types.QueryStatusComplete { + log.Printf("GetQueryResult: sleeping for 5 seconds until status is complete") + time.Sleep(5 * time.Second) + continue + } + log.Printf("GetQueryResult: result length is %d", len(result.Results)) + if err != nil { + if errors.As(err, &rnf) { + // The log group/stream hasn't been created yet, so wait and retry + time.Sleep(retryWaitTime) + continue + } + + // if the error is not a ResourceNotFoundException, we should fail here. + return results, err + } + results = result.Results + return results, err + } +} + +func getLogGroup() ([]types.LogGroup, error) { + attempts := 0 + var logGroups []types.LogGroup + params := &cloudwatchlogs.DescribeLogGroupsInput{} + for { + output, err := cwlClient.DescribeLogGroups(context.Background(), params) + + attempts += 1 + + if err != nil { + if errors.As(err, &rnf) && attempts <= awsservice.StandardRetries { + // The log group/stream hasn't been created yet, so wait and retry + time.Sleep(retryWaitTime) + continue + } + + // if the error is not a ResourceNotFoundException, we should fail here. + return logGroups, err + } + logGroups = output.LogGroups + return logGroups, err + } +} From 5ed1a937531e841c29793f3a6645572ca6657567 Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 00:28:00 +0000 Subject: [PATCH 03/25] Have the agent sleep after starting it --- test/cloudwatchlogs/publish_logs_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 2e27c2d87..f9a311b9e 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -259,6 +259,7 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { Tags: tagsToCreate, } _, err := ec2Client.CreateTags(context.TODO(), input) + //modify here assert.NoError(t, err) } id := uuid.New() @@ -272,8 +273,8 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { common.CopyFile(testCase.agentConfigPath, configOutputPath) common.StartAgent(configOutputPath, true, false) - writeLogLines(t, f, testCase.iterations) time.Sleep(sleepForFlush) + writeLogLines(t, f, testCase.iterations) common.StopAgent() end := time.Now() From 96522cd8a68fbe3cb980e04e32bf763a9dd764ec Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 00:43:36 +0000 Subject: [PATCH 04/25] update flush time to 90s --- test/cloudwatchlogs/publish_logs_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index f9a311b9e..d8a3de2ba 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -36,7 +36,7 @@ const ( logLineId1 = "foo" logLineId2 = "bar" logFilePath = "/tmp/cwagent_log_test.log" // TODO: not sure how well this will work on Windows - sleepForFlush = 120 * time.Second // default flush interval is 5 seconds + sleepForFlush = 90 * time.Second // default flush interval is 5 seconds retryWaitTime = 30 * time.Second configPathAutoRemoval = "resources/config_auto_removal.json" standardLogGroupClass = "STANDARD" @@ -275,6 +275,7 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { common.StartAgent(configOutputPath, true, false) time.Sleep(sleepForFlush) writeLogLines(t, f, testCase.iterations) + time.Sleep(sleepForFlush) common.StopAgent() end := time.Now() From b98c612119ddccc53e82a0ea8bcb36c52e309c04 Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 01:03:47 +0000 Subject: [PATCH 05/25] Update flush time to 120 seconds --- test/cloudwatchlogs/publish_logs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index d8a3de2ba..663638714 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -36,7 +36,7 @@ const ( logLineId1 = "foo" logLineId2 = "bar" logFilePath = "/tmp/cwagent_log_test.log" // TODO: not sure how well this will work on Windows - sleepForFlush = 90 * time.Second // default flush interval is 5 seconds + sleepForFlush = 120 * time.Second // default flush interval is 5 seconds retryWaitTime = 30 * time.Second configPathAutoRemoval = "resources/config_auto_removal.json" standardLogGroupClass = "STANDARD" From bdd40b938942ca530b9559c7fd86058cba849893 Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 01:39:41 +0000 Subject: [PATCH 06/25] Add delay to ensure tag deletion propagates --- test/cloudwatchlogs/publish_logs_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 663638714..c1b4672a5 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -36,7 +36,7 @@ const ( logLineId1 = "foo" logLineId2 = "bar" logFilePath = "/tmp/cwagent_log_test.log" // TODO: not sure how well this will work on Windows - sleepForFlush = 120 * time.Second // default flush interval is 5 seconds + sleepForFlush = 180 * time.Second // default flush interval is 5 seconds retryWaitTime = 30 * time.Second configPathAutoRemoval = "resources/config_auto_removal.json" standardLogGroupClass = "STANDARD" @@ -202,24 +202,24 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { useEC2Tag bool expectedEntity expectedEntity }{ - "IAMRole": { + "EC2Tags": { agentConfigPath: filepath.Join("resources", "config_log.json"), iterations: 1000, + useEC2Tag: true, expectedEntity: expectedEntity{ entityType: "Service", - name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing + name: "service-test", //should match the value in tagsToCreate environment: "ec2:default", platformType: "AWS::EC2", instanceId: instanceId, }, }, - "EC2Tags": { + "IAMRole": { agentConfigPath: filepath.Join("resources", "config_log.json"), iterations: 1000, - useEC2Tag: true, expectedEntity: expectedEntity{ entityType: "Service", - name: "service-test", //should match the value in tagsToCreate + name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing environment: "ec2:default", platformType: "AWS::EC2", instanceId: instanceId, @@ -251,6 +251,8 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { } _, err := ec2Client.DeleteTags(context.TODO(), input) assert.NoError(t, err) + // Add a short delay to ensure tag deletion propagates + time.Sleep(5 * time.Second) } }) if testCase.useEC2Tag { From 43d2ca63793e44b36cbfa4b9a93e59cc3912877f Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 02:42:52 +0000 Subject: [PATCH 07/25] Change order of tests --- test/cloudwatchlogs/publish_logs_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index c1b4672a5..f456a3811 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -214,24 +214,24 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { instanceId: instanceId, }, }, - "IAMRole": { - agentConfigPath: filepath.Join("resources", "config_log.json"), + "ServiceInConfig": { + agentConfigPath: filepath.Join("resources", "config_log_service_name.json"), iterations: 1000, expectedEntity: expectedEntity{ entityType: "Service", - name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing - environment: "ec2:default", + name: "service-in-config", //should match the service.name value in the config file + environment: "environment-in-config", //should match the deployment.environment value in the config file platformType: "AWS::EC2", instanceId: instanceId, }, }, - "ServiceInConfig": { - agentConfigPath: filepath.Join("resources", "config_log_service_name.json"), + "IAMRole": { + agentConfigPath: filepath.Join("resources", "config_log.json"), iterations: 1000, expectedEntity: expectedEntity{ entityType: "Service", - name: "service-in-config", //should match the service.name value in the config file - environment: "environment-in-config", //should match the deployment.environment value in the config file + name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing + environment: "ec2:default", platformType: "AWS::EC2", instanceId: instanceId, }, From 9f28f03331d1e7b6b28626ee9cc10448fa986265 Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 03:10:49 +0000 Subject: [PATCH 08/25] Change order of tests again --- test/cloudwatchlogs/publish_logs_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index f456a3811..0ff65869f 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -202,13 +202,12 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { useEC2Tag bool expectedEntity expectedEntity }{ - "EC2Tags": { + "IAMRole": { agentConfigPath: filepath.Join("resources", "config_log.json"), iterations: 1000, - useEC2Tag: true, expectedEntity: expectedEntity{ entityType: "Service", - name: "service-test", //should match the value in tagsToCreate + name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing environment: "ec2:default", platformType: "AWS::EC2", instanceId: instanceId, @@ -225,12 +224,13 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { instanceId: instanceId, }, }, - "IAMRole": { + "EC2Tags": { agentConfigPath: filepath.Join("resources", "config_log.json"), iterations: 1000, + useEC2Tag: true, expectedEntity: expectedEntity{ entityType: "Service", - name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing + name: "service-test", //should match the value in tagsToCreate environment: "ec2:default", platformType: "AWS::EC2", instanceId: instanceId, From e1bb9ce952c2d04f8d351e9f4361b15f44508550 Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 05:00:08 +0000 Subject: [PATCH 09/25] Enable instance metadata tags --- test/cloudwatchlogs/publish_logs_test.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 0ff65869f..3e3228ce9 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -256,13 +256,20 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { } }) if testCase.useEC2Tag { + // enable instance metadata tags + modifyInput := &ec2.ModifyInstanceMetadataOptionsInput{ + InstanceId: aws.String(instanceId), + InstanceMetadataTags: ec2Types.InstanceMetadataTagsStateEnabled, + } + _, modifyErr := ec2Client.ModifyInstanceMetadataOptions(context.TODO(), modifyInput) + assert.NoError(t, modifyErr) + input := &ec2.CreateTagsInput{ Resources: []string{instanceId}, Tags: tagsToCreate, } - _, err := ec2Client.CreateTags(context.TODO(), input) - //modify here - assert.NoError(t, err) + _, createErr := ec2Client.CreateTags(context.TODO(), input) + assert.NoError(t, createErr) } id := uuid.New() f, err := os.Create(logFilePath + "-" + id.String()) From fd597135a44747955bbeac1a7bb2c1a1c69d6de3 Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 14:12:53 +0000 Subject: [PATCH 10/25] Clean up unused code --- test/cloudwatchlogs/publish_logs_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 3e3228ce9..43356b05b 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -49,8 +49,6 @@ const ( entityEnvironment = "@entity.KeyAttributes.Environment" entityPlatform = "@entity.Attributes.PlatformType" entityInstanceId = "@entity.Attributes.EC2.InstanceId" - serviceName = "service.name" - deploymentName = "deployment.name" ) var ( From 6e007577b748dc330c72956a8f5af01a0213881f Mon Sep 17 00:00:00 2001 From: Varun C Date: Tue, 19 Nov 2024 18:59:21 +0000 Subject: [PATCH 11/25] Differentiate sleep times --- test/cloudwatchlogs/publish_logs_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 43356b05b..6bb5f2408 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -36,7 +36,8 @@ const ( logLineId1 = "foo" logLineId2 = "bar" logFilePath = "/tmp/cwagent_log_test.log" // TODO: not sure how well this will work on Windows - sleepForFlush = 180 * time.Second // default flush interval is 5 seconds + sleepForFlush = 20 * time.Second // default flush interval is 5 seconds + sleepForExtendedFlush = 180 * time.Second // increase flush time for the two main tests retryWaitTime = 30 * time.Second configPathAutoRemoval = "resources/config_auto_removal.json" standardLogGroupClass = "STANDARD" @@ -160,9 +161,9 @@ func TestWriteLogsToCloudWatch(t *testing.T) { // ensure that there is enough time from the "start" time and the first log line, // so we don't miss it in the GetLogEvents call - time.Sleep(sleepForFlush) + time.Sleep(sleepForExtendedFlush) writeLogLines(t, f, param.iterations) - time.Sleep(sleepForFlush) + time.Sleep(sleepForExtendedFlush) common.StopAgent() end := time.Now() @@ -280,9 +281,9 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { common.CopyFile(testCase.agentConfigPath, configOutputPath) common.StartAgent(configOutputPath, true, false) - time.Sleep(sleepForFlush) + time.Sleep(sleepForExtendedFlush) writeLogLines(t, f, testCase.iterations) - time.Sleep(sleepForFlush) + time.Sleep(sleepForExtendedFlush) common.StopAgent() end := time.Now() @@ -499,7 +500,7 @@ func ValidateEntity(t *testing.T, logGroup, logStream string, end *time.Time, ex } } assert.NoError(t, err) - begin := end.Add(-sleepForFlush * 4) + begin := end.Add(-sleepForExtendedFlush * 4) log.Printf("Query start time is " + begin.String() + " and end time is " + end.String()) queryId, err := getLogQueryId(logGroup, &begin, end) assert.NoError(t, err) From 84cc3d39def9c67c5a0460105bd990fe712f031e Mon Sep 17 00:00:00 2001 From: Varun C Date: Fri, 22 Nov 2024 16:28:40 +0000 Subject: [PATCH 12/25] Address nits and make minor changes --- test/cloudwatchlogs/publish_logs_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 6bb5f2408..1bc69df96 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -50,6 +50,7 @@ const ( entityEnvironment = "@entity.KeyAttributes.Environment" entityPlatform = "@entity.Attributes.PlatformType" entityInstanceId = "@entity.Attributes.EC2.InstanceId" + queryString = "fields @message, @entity.KeyAttributes.Type, @entity.KeyAttributes.Name, @entity.KeyAttributes.Environment, @entity.Attributes.PlatformType, @entity.Attributes.EC2.InstanceId" ) var ( @@ -88,9 +89,9 @@ var ( logGroupClass: types.LogGroupClassInfrequentAccess, }, } - rnf *types.ResourceNotFoundException - cwlClient *cloudwatchlogs.Client - ec2Client *ec2.Client + resourceNotFoundException *types.ResourceNotFoundException + cwlClient *cloudwatchlogs.Client + ec2Client *ec2.Client ) type writeToCloudWatchTestInput struct { @@ -122,8 +123,7 @@ func init() { config.WithRegion(pdxRegionalCode), ) if err != nil { - fmt.Println("There was an error trying to load default config: ", err) - return + log.Fatalf("Failed to load default config: %v", err) } cwlClient = cloudwatchlogs.NewFromConfig(awsCfg, func(o *cloudwatchlogs.Options) { @@ -550,7 +550,7 @@ func ValidateEntity(t *testing.T, logGroup, logStream string, end *time.Time, ex func getLogQueryId(logGroup string, since, until *time.Time) (*string, error) { var queryId *string params := &cloudwatchlogs.StartQueryInput{ - QueryString: aws.String("fields @message, @entity.KeyAttributes.Type, @entity.KeyAttributes.Name, @entity.KeyAttributes.Environment, @entity.Attributes.PlatformType, @entity.Attributes.EC2.InstanceId"), + QueryString: aws.String(queryString), LogGroupName: aws.String(logGroup), } if since != nil { @@ -566,7 +566,7 @@ func getLogQueryId(logGroup string, since, until *time.Time) (*string, error) { attempts += 1 if err != nil { - if errors.As(err, &rnf) && attempts <= awsservice.StandardRetries { + if errors.As(err, &resourceNotFoundException) && attempts <= awsservice.StandardRetries { // The log group/stream hasn't been created yet, so wait and retry time.Sleep(retryWaitTime) continue @@ -600,7 +600,7 @@ func getQueryResult(queryId *string) ([][]types.ResultField, error) { } log.Printf("GetQueryResult: result length is %d", len(result.Results)) if err != nil { - if errors.As(err, &rnf) { + if errors.As(err, &resourceNotFoundException) { // The log group/stream hasn't been created yet, so wait and retry time.Sleep(retryWaitTime) continue @@ -624,7 +624,7 @@ func getLogGroup() ([]types.LogGroup, error) { attempts += 1 if err != nil { - if errors.As(err, &rnf) && attempts <= awsservice.StandardRetries { + if errors.As(err, &resourceNotFoundException) && attempts <= awsservice.StandardRetries { // The log group/stream hasn't been created yet, so wait and retry time.Sleep(retryWaitTime) continue From 7a3b428bacde3adcb2ffc5eddaa148b4f1a3fd67 Mon Sep 17 00:00:00 2001 From: Varun C Date: Fri, 22 Nov 2024 16:50:12 +0000 Subject: [PATCH 13/25] UGet region from EC2 metadata instead of hardcoding --- test/cloudwatchlogs/publish_logs_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 1bc69df96..c7196a2f2 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -19,6 +19,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/aws/aws-sdk-go-v2/service/ec2" @@ -43,7 +44,6 @@ const ( standardLogGroupClass = "STANDARD" infrequentAccessLogGroupClass = "INFREQUENT_ACCESS" cwlPerfEndpoint = "https://logs.us-west-2.amazonaws.com" - pdxRegionalCode = "us-west-2" entityType = "@entity.KeyAttributes.Type" entityName = "@entity.KeyAttributes.Name" @@ -118,9 +118,19 @@ type expectedEntity struct { func init() { environment.RegisterEnvironmentMetaDataFlags() + + imdsClient := imds.New(imds.Options{}) // create IMDS client to get region from EC2 instance + + region, err := imdsClient.GetRegion(context.Background(), &imds.GetRegionInput{}) + if err != nil { + log.Printf("Failed to get region from EC2 metadata, falling back to default region: %v", err) + region = &imds.GetRegionOutput{ + Region: "us-west-2", + } + } awsCfg, err := config.LoadDefaultConfig( context.Background(), - config.WithRegion(pdxRegionalCode), + config.WithRegion(region.Region), ) if err != nil { log.Fatalf("Failed to load default config: %v", err) From e37d84a1a7e3c9ff7dc6e5ac0d63cd07ac25f8f5 Mon Sep 17 00:00:00 2001 From: Varun C Date: Fri, 22 Nov 2024 17:27:27 +0000 Subject: [PATCH 14/25] Revert EC2 metadata change --- test/cloudwatchlogs/publish_logs_test.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index c7196a2f2..f101e7962 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -118,19 +118,9 @@ type expectedEntity struct { func init() { environment.RegisterEnvironmentMetaDataFlags() - - imdsClient := imds.New(imds.Options{}) // create IMDS client to get region from EC2 instance - - region, err := imdsClient.GetRegion(context.Background(), &imds.GetRegionInput{}) - if err != nil { - log.Printf("Failed to get region from EC2 metadata, falling back to default region: %v", err) - region = &imds.GetRegionOutput{ - Region: "us-west-2", - } - } awsCfg, err := config.LoadDefaultConfig( context.Background(), - config.WithRegion(region.Region), + config.WithRegion(pdxRegionalCode), ) if err != nil { log.Fatalf("Failed to load default config: %v", err) From 79744f18298e8e3bcb333fcd00a0bc5a50844233 Mon Sep 17 00:00:00 2001 From: Varun C Date: Fri, 22 Nov 2024 17:29:09 +0000 Subject: [PATCH 15/25] Revert EC2 metadata change --- test/cloudwatchlogs/publish_logs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index f101e7962..e9cfc6002 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -19,7 +19,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/aws/aws-sdk-go-v2/service/ec2" @@ -44,6 +43,7 @@ const ( standardLogGroupClass = "STANDARD" infrequentAccessLogGroupClass = "INFREQUENT_ACCESS" cwlPerfEndpoint = "https://logs.us-west-2.amazonaws.com" + pdxRegionalCode = "us-west-2" entityType = "@entity.KeyAttributes.Type" entityName = "@entity.KeyAttributes.Name" From 795072522768ef075f3068f93a5c84642193acc3 Mon Sep 17 00:00:00 2001 From: Varun C Date: Fri, 22 Nov 2024 18:08:07 +0000 Subject: [PATCH 16/25] Address misc comments --- test/cloudwatchlogs/publish_logs_test.go | 33 ++++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index e9cfc6002..cdef7b7f9 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -43,7 +43,7 @@ const ( standardLogGroupClass = "STANDARD" infrequentAccessLogGroupClass = "INFREQUENT_ACCESS" cwlPerfEndpoint = "https://logs.us-west-2.amazonaws.com" - pdxRegionalCode = "us-west-2" + pdxRegionalCode = "us-west-2" entityType = "@entity.KeyAttributes.Type" entityName = "@entity.KeyAttributes.Name" @@ -275,6 +275,17 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { if err != nil { t.Fatalf("Error occurred creating log file for writing: %v", err) } + + // Defer file closing and removal with error handling + defer func() { + if err := f.Close(); err != nil { + t.Errorf("Error occurred closing log file: %v", err) + } + if err := os.Remove(logFilePath + "-" + id.String()); err != nil { + t.Errorf("Error occurred removing log file: %v", err) + } + }() + common.DeleteFile(common.AgentLogFile) common.TouchFile(common.AgentLogFile) @@ -286,11 +297,9 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { time.Sleep(sleepForExtendedFlush) common.StopAgent() end := time.Now() + begin := end.Add(-sleepForExtendedFlush * 4) - ValidateEntity(t, instanceId, instanceId, &end, testCase.expectedEntity) - - f.Close() - os.Remove(logFilePath + "-" + id.String()) + ValidateEntity(t, instanceId, instanceId, &begin, &end, testCase.expectedEntity) }) } } @@ -489,10 +498,10 @@ func checkData(t *testing.T, start time.Time, lineCount int) { assert.NoError(t, err) } -func ValidateEntity(t *testing.T, logGroup, logStream string, end *time.Time, expectedEntity expectedEntity) { +func ValidateEntity(t *testing.T, logGroup, logStream string, begin, end *time.Time, expectedEntity expectedEntity) { log.Printf("Validating entity for log group: %s, stream: %s", logGroup, logStream) - logGroupInfo, err := getLogGroup() + logGroupInfo, err := getLogGroup(logGroup) for _, lg := range logGroupInfo { if *lg.LogGroupName == logGroup { log.Println("Log group " + *lg.LogGroupName + " exists") @@ -500,9 +509,9 @@ func ValidateEntity(t *testing.T, logGroup, logStream string, end *time.Time, ex } } assert.NoError(t, err) - begin := end.Add(-sleepForExtendedFlush * 4) + log.Printf("Query start time is " + begin.String() + " and end time is " + end.String()) - queryId, err := getLogQueryId(logGroup, &begin, end) + queryId, err := getLogQueryId(logGroup, begin, end) assert.NoError(t, err) log.Printf("queryId is " + *queryId) result, err := getQueryResult(queryId) @@ -614,10 +623,12 @@ func getQueryResult(queryId *string) ([][]types.ResultField, error) { } } -func getLogGroup() ([]types.LogGroup, error) { +func getLogGroup(logGroupName string) ([]types.LogGroup, error) { attempts := 0 var logGroups []types.LogGroup - params := &cloudwatchlogs.DescribeLogGroupsInput{} + params := &cloudwatchlogs.DescribeLogGroupsInput{ + LogGroupNamePrefix: aws.String(logGroupName), + } for { output, err := cwlClient.DescribeLogGroups(context.Background(), params) From 37cb8a5fdd5f39e60140be8fa7c36803acd3a8c9 Mon Sep 17 00:00:00 2001 From: Varun C Date: Fri, 22 Nov 2024 18:23:41 +0000 Subject: [PATCH 17/25] update code to search for log group directly --- test/cloudwatchlogs/publish_logs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index cdef7b7f9..870c6b41a 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -627,7 +627,7 @@ func getLogGroup(logGroupName string) ([]types.LogGroup, error) { attempts := 0 var logGroups []types.LogGroup params := &cloudwatchlogs.DescribeLogGroupsInput{ - LogGroupNamePrefix: aws.String(logGroupName), + LogGroupName: aws.String(logGroupName), } for { output, err := cwlClient.DescribeLogGroups(context.Background(), params) From 006a057bf450c4f0f243278f50bde941a01dd2d9 Mon Sep 17 00:00:00 2001 From: Varun C Date: Fri, 22 Nov 2024 18:28:37 +0000 Subject: [PATCH 18/25] Revert to search by prefix --- test/cloudwatchlogs/publish_logs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 870c6b41a..cdef7b7f9 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -627,7 +627,7 @@ func getLogGroup(logGroupName string) ([]types.LogGroup, error) { attempts := 0 var logGroups []types.LogGroup params := &cloudwatchlogs.DescribeLogGroupsInput{ - LogGroupName: aws.String(logGroupName), + LogGroupNamePrefix: aws.String(logGroupName), } for { output, err := cwlClient.DescribeLogGroups(context.Background(), params) From eb95406ca153d93aac8ba2f64ee3a86b755bd6a9 Mon Sep 17 00:00:00 2001 From: Varun C Date: Wed, 4 Dec 2024 15:55:40 +0000 Subject: [PATCH 19/25] address comments --- test/cloudwatchlogs/publish_logs_test.go | 133 ++--------------------- 1 file changed, 9 insertions(+), 124 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index cdef7b7f9..fb72d3e37 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -18,7 +18,6 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/aws/aws-sdk-go-v2/service/ec2" @@ -42,8 +41,6 @@ const ( configPathAutoRemoval = "resources/config_auto_removal.json" standardLogGroupClass = "STANDARD" infrequentAccessLogGroupClass = "INFREQUENT_ACCESS" - cwlPerfEndpoint = "https://logs.us-west-2.amazonaws.com" - pdxRegionalCode = "us-west-2" entityType = "@entity.KeyAttributes.Type" entityName = "@entity.KeyAttributes.Name" @@ -118,18 +115,6 @@ type expectedEntity struct { func init() { environment.RegisterEnvironmentMetaDataFlags() - awsCfg, err := config.LoadDefaultConfig( - context.Background(), - config.WithRegion(pdxRegionalCode), - ) - if err != nil { - log.Fatalf("Failed to load default config: %v", err) - } - - cwlClient = cloudwatchlogs.NewFromConfig(awsCfg, func(o *cloudwatchlogs.Options) { - o.BaseEndpoint = aws.String(cwlPerfEndpoint) - }) - ec2Client = ec2.NewFromConfig(awsCfg) } // TestWriteLogsToCloudWatch writes N number of logs, and then validates that N logs @@ -501,22 +486,15 @@ func checkData(t *testing.T, start time.Time, lineCount int) { func ValidateEntity(t *testing.T, logGroup, logStream string, begin, end *time.Time, expectedEntity expectedEntity) { log.Printf("Validating entity for log group: %s, stream: %s", logGroup, logStream) - logGroupInfo, err := getLogGroup(logGroup) - for _, lg := range logGroupInfo { - if *lg.LogGroupName == logGroup { - log.Println("Log group " + *lg.LogGroupName + " exists") - break - } - } - assert.NoError(t, err) + logGroupExists := awsservice.IsLogGroupExists(logGroup) + assert.True(t, logGroupExists, "Log group %s does not exist", logGroup) log.Printf("Query start time is " + begin.String() + " and end time is " + end.String()) - queryId, err := getLogQueryId(logGroup, begin, end) - assert.NoError(t, err) - log.Printf("queryId is " + *queryId) - result, err := getQueryResult(queryId) - assert.NoError(t, err) - if !assert.NotZero(t, len(result)) { + + results, err := awsservice.GetLogQueryResults(logGroup, begin.Unix(), end.Unix(), queryString) + assert.NoError(t, err, "Failed to get query results") + + if !assert.NotZero(t, len(results)) { return } requiredEntityFields := map[string]bool{ @@ -526,7 +504,7 @@ func ValidateEntity(t *testing.T, logGroup, logStream string, begin, end *time.T entityPlatform: false, entityInstanceId: false, } - for _, field := range result[0] { + for _, field := range results[0] { switch aws.ToString(field.Field) { case entityType: requiredEntityFields[entityType] = true @@ -554,97 +532,4 @@ func ValidateEntity(t *testing.T, logGroup, logStream string, begin, end *time.T } } assert.True(t, allEntityFieldsFound) -} - -func getLogQueryId(logGroup string, since, until *time.Time) (*string, error) { - var queryId *string - params := &cloudwatchlogs.StartQueryInput{ - QueryString: aws.String(queryString), - LogGroupName: aws.String(logGroup), - } - if since != nil { - params.StartTime = aws.Int64(since.UnixMilli()) - } - if until != nil { - params.EndTime = aws.Int64(until.UnixMilli()) - } - attempts := 0 - - for { - output, err := cwlClient.StartQuery(context.Background(), params) - attempts += 1 - - if err != nil { - if errors.As(err, &resourceNotFoundException) && attempts <= awsservice.StandardRetries { - // The log group/stream hasn't been created yet, so wait and retry - time.Sleep(retryWaitTime) - continue - } - - // if the error is not a ResourceNotFoundException, we should fail here. - return queryId, err - } - queryId = output.QueryId - return queryId, err - } -} - -func getQueryResult(queryId *string) ([][]types.ResultField, error) { - attempts := 0 - var results [][]types.ResultField - params := &cloudwatchlogs.GetQueryResultsInput{ - QueryId: aws.String(*queryId), - } - for { - if attempts > awsservice.StandardRetries { - return results, errors.New("exceeded retry count") - } - result, err := cwlClient.GetQueryResults(context.Background(), params) - log.Printf("GetQueryResult status is: %v", result.Status) - attempts += 1 - if result.Status != types.QueryStatusComplete { - log.Printf("GetQueryResult: sleeping for 5 seconds until status is complete") - time.Sleep(5 * time.Second) - continue - } - log.Printf("GetQueryResult: result length is %d", len(result.Results)) - if err != nil { - if errors.As(err, &resourceNotFoundException) { - // The log group/stream hasn't been created yet, so wait and retry - time.Sleep(retryWaitTime) - continue - } - - // if the error is not a ResourceNotFoundException, we should fail here. - return results, err - } - results = result.Results - return results, err - } -} - -func getLogGroup(logGroupName string) ([]types.LogGroup, error) { - attempts := 0 - var logGroups []types.LogGroup - params := &cloudwatchlogs.DescribeLogGroupsInput{ - LogGroupNamePrefix: aws.String(logGroupName), - } - for { - output, err := cwlClient.DescribeLogGroups(context.Background(), params) - - attempts += 1 - - if err != nil { - if errors.As(err, &resourceNotFoundException) && attempts <= awsservice.StandardRetries { - // The log group/stream hasn't been created yet, so wait and retry - time.Sleep(retryWaitTime) - continue - } - - // if the error is not a ResourceNotFoundException, we should fail here. - return logGroups, err - } - logGroups = output.LogGroups - return logGroups, err - } -} +} \ No newline at end of file From df7abce54d89a0868388d05c7e17920f3ce7ad33 Mon Sep 17 00:00:00 2001 From: Varun C Date: Wed, 4 Dec 2024 15:57:29 +0000 Subject: [PATCH 20/25] remove unused packageS --- test/cloudwatchlogs/publish_logs_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index fb72d3e37..925f791db 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -7,7 +7,6 @@ package cloudwatchlogs import ( "context" - "errors" "fmt" "log" "os" From 8dcc2d4198d3815601947b03ec26447a429b65d4 Mon Sep 17 00:00:00 2001 From: Varun C Date: Wed, 4 Dec 2024 16:39:24 +0000 Subject: [PATCH 21/25] use clients from the awsservice package --- test/cloudwatchlogs/publish_logs_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 925f791db..9b4b9678a 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -17,7 +17,6 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/aws/aws-sdk-go-v2/service/ec2" ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types" @@ -86,8 +85,6 @@ var ( }, } resourceNotFoundException *types.ResourceNotFoundException - cwlClient *cloudwatchlogs.Client - ec2Client *ec2.Client ) type writeToCloudWatchTestInput struct { @@ -232,7 +229,7 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { Resources: []string{instanceId}, Tags: tagsToCreate, } - _, err := ec2Client.DeleteTags(context.TODO(), input) + _, err := awsservice.Ec2Client.DeleteTags(context.TODO(), input) assert.NoError(t, err) // Add a short delay to ensure tag deletion propagates time.Sleep(5 * time.Second) @@ -244,14 +241,14 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { InstanceId: aws.String(instanceId), InstanceMetadataTags: ec2Types.InstanceMetadataTagsStateEnabled, } - _, modifyErr := ec2Client.ModifyInstanceMetadataOptions(context.TODO(), modifyInput) + _, modifyErr := awsservice.Ec2Client.ModifyInstanceMetadataOptions(context.TODO(), modifyInput) assert.NoError(t, modifyErr) input := &ec2.CreateTagsInput{ Resources: []string{instanceId}, Tags: tagsToCreate, } - _, createErr := ec2Client.CreateTags(context.TODO(), input) + _, createErr := awsservice.Ec2Client.CreateTags(context.TODO(), input) assert.NoError(t, createErr) } id := uuid.New() From 6f1e4cfcb43e97e8f161c43e15780bf78555b102 Mon Sep 17 00:00:00 2001 From: Varun C Date: Wed, 4 Dec 2024 20:09:12 +0000 Subject: [PATCH 22/25] Make ValidateEntity a common package --- test/cloudwatchlogs/publish_logs_test.go | 100 +++++------------------ 1 file changed, 20 insertions(+), 80 deletions(-) diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index 9b4b9678a..383acfd7e 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -101,14 +101,6 @@ type cloudWatchLogGroupClassTestInput struct { logGroupClass types.LogGroupClass } -type expectedEntity struct { - entityType string - name string - environment string - platformType string - instanceId string -} - func init() { environment.RegisterEnvironmentMetaDataFlags() } @@ -180,40 +172,40 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { agentConfigPath string iterations int useEC2Tag bool - expectedEntity expectedEntity + expectedEntity common.ExpectedEntity }{ "IAMRole": { agentConfigPath: filepath.Join("resources", "config_log.json"), iterations: 1000, - expectedEntity: expectedEntity{ - entityType: "Service", - name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing - environment: "ec2:default", - platformType: "AWS::EC2", - instanceId: instanceId, + expectedEntity: common.ExpectedEntity{ + EntityType: "Service", + Name: "cwa-e2e-iam-role", //should match the name of the IAM role used in our testing + Environment: "ec2:default", + PlatformType: "AWS::EC2", + InstanceId: instanceId, }, }, "ServiceInConfig": { agentConfigPath: filepath.Join("resources", "config_log_service_name.json"), iterations: 1000, - expectedEntity: expectedEntity{ - entityType: "Service", - name: "service-in-config", //should match the service.name value in the config file - environment: "environment-in-config", //should match the deployment.environment value in the config file - platformType: "AWS::EC2", - instanceId: instanceId, + expectedEntity: common.ExpectedEntity{ + EntityType: "Service", + Name: "service-in-config", //should match the service.name value in the config file + Environment: "environment-in-config", //should match the deployment.environment value in the config file + PlatformType: "AWS::EC2", + InstanceId: instanceId, }, }, "EC2Tags": { agentConfigPath: filepath.Join("resources", "config_log.json"), iterations: 1000, useEC2Tag: true, - expectedEntity: expectedEntity{ - entityType: "Service", - name: "service-test", //should match the value in tagsToCreate - environment: "ec2:default", - platformType: "AWS::EC2", - instanceId: instanceId, + expectedEntity: common.ExpectedEntity{ + EntityType: "Service", + Name: "service-test", //should match the value in tagsToCreate + Environment: "ec2:default", + PlatformType: "AWS::EC2", + InstanceId: instanceId, }, }, } @@ -278,9 +270,8 @@ func TestWriteLogsWithEntityInfo(t *testing.T) { time.Sleep(sleepForExtendedFlush) common.StopAgent() end := time.Now() - begin := end.Add(-sleepForExtendedFlush * 4) - ValidateEntity(t, instanceId, instanceId, &begin, &end, testCase.expectedEntity) + common.ValidateLogEntity(t, instanceId, instanceId, &end, queryString, testCase.expectedEntity, "EC2") }) } } @@ -478,54 +469,3 @@ func checkData(t *testing.T, start time.Time, lineCount int) { ) assert.NoError(t, err) } - -func ValidateEntity(t *testing.T, logGroup, logStream string, begin, end *time.Time, expectedEntity expectedEntity) { - log.Printf("Validating entity for log group: %s, stream: %s", logGroup, logStream) - - logGroupExists := awsservice.IsLogGroupExists(logGroup) - assert.True(t, logGroupExists, "Log group %s does not exist", logGroup) - - log.Printf("Query start time is " + begin.String() + " and end time is " + end.String()) - - results, err := awsservice.GetLogQueryResults(logGroup, begin.Unix(), end.Unix(), queryString) - assert.NoError(t, err, "Failed to get query results") - - if !assert.NotZero(t, len(results)) { - return - } - requiredEntityFields := map[string]bool{ - entityType: false, - entityName: false, - entityEnvironment: false, - entityPlatform: false, - entityInstanceId: false, - } - for _, field := range results[0] { - switch aws.ToString(field.Field) { - case entityType: - requiredEntityFields[entityType] = true - assert.Equal(t, expectedEntity.entityType, aws.ToString(field.Value)) - case entityName: - requiredEntityFields[entityName] = true - assert.Equal(t, expectedEntity.name, aws.ToString(field.Value)) - case entityEnvironment: - requiredEntityFields[entityEnvironment] = true - assert.Equal(t, expectedEntity.environment, aws.ToString(field.Value)) - case entityPlatform: - requiredEntityFields[entityPlatform] = true - assert.Equal(t, expectedEntity.platformType, aws.ToString(field.Value)) - case entityInstanceId: - requiredEntityFields[entityInstanceId] = true - assert.Equal(t, expectedEntity.instanceId, aws.ToString(field.Value)) - } - fmt.Printf("%s: %s\n", aws.ToString(field.Field), aws.ToString(field.Value)) - } - allEntityFieldsFound := true - for field, value := range requiredEntityFields { - if !value { - log.Printf("Missing required entity field: %s", field) - allEntityFieldsFound = false - } - } - assert.True(t, allEntityFieldsFound) -} \ No newline at end of file From 5bd4a65cd0e2100171b99c5c4760d8e856c71992 Mon Sep 17 00:00:00 2001 From: Varun C Date: Wed, 4 Dec 2024 20:23:55 +0000 Subject: [PATCH 23/25] modify ValidateLogs to be a function in a common package --- test/entity/entity_test.go | 205 +++++++------------------------------ util/common/entity.go | 147 ++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 167 deletions(-) create mode 100644 util/common/entity.go diff --git a/test/entity/entity_test.go b/test/entity/entity_test.go index a45b7464b..cdc7292f9 100644 --- a/test/entity/entity_test.go +++ b/test/entity/entity_test.go @@ -11,29 +11,17 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/stretchr/testify/assert" "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/util/common" "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" ) const ( sleepForFlush = 240 * time.Second - entityType = "@entity.KeyAttributes.Type" - entityName = "@entity.KeyAttributes.Name" - entityEnvironment = "@entity.KeyAttributes.Environment" - - entityPlatform = "@entity.Attributes.PlatformType" - entityInstanceId = "@entity.Attributes.EC2.InstanceId" - entityEKSCluster = "@entity.Attributes.EKS.Cluster" - entityK8sNode = "@entity.Attributes.K8s.Node" - entityK8sNamespace = "@entity.Attributes.K8s.Namespace" - entityK8sWorkload = "@entity.Attributes.K8s.Workload" - entityServiceNameSource = "@entity.Attributes.AWS.ServiceNameSource" - - // Constants for possible vaues for entity attributes + // Constants for possible values for entity attributes eksServiceEntityType = "Service" entityEKSPlatform = "AWS::EKS" k8sDefaultNamespace = "default" @@ -41,96 +29,6 @@ const ( entityServiceNameSourceK8sWorkload = "K8sWorkload" ) -type EntityValidator struct { - requiredFields map[string]bool - expectedEntity expectedEntity - platformType string - fieldValidators map[string]func(fieldValue string) bool -} - -// NewEntityValidator initializes the validator based on the platform type. -func NewEntityValidator(platformType string, expected expectedEntity) *EntityValidator { - ev := &EntityValidator{ - expectedEntity: expected, - platformType: platformType, - requiredFields: make(map[string]bool), - fieldValidators: make(map[string]func(fieldValue string) bool), - } - - // Define platform-specific required fields and validators - if platformType == "EC2" { - ev.requiredFields = map[string]bool{ - entityType: false, - entityName: false, - entityEnvironment: false, - entityPlatform: false, - entityInstanceId: false, - } - ev.fieldValidators = map[string]func(fieldValue string) bool{ - entityType: func(v string) bool { return v == ev.expectedEntity.entityType }, - entityName: func(v string) bool { return v == ev.expectedEntity.name }, - entityEnvironment: func(v string) bool { return v == ev.expectedEntity.environment }, - entityPlatform: func(v string) bool { return v == ev.expectedEntity.platformType }, - entityInstanceId: func(v string) bool { return v == ev.expectedEntity.instanceId }, - } - } else if platformType == "EKS" { - ev.requiredFields = map[string]bool{ - entityType: false, - entityName: false, - entityEnvironment: false, - entityPlatform: false, - entityEKSCluster: false, - entityK8sNode: false, - entityK8sNamespace: false, - entityK8sWorkload: false, - entityServiceNameSource: false, - } - ev.fieldValidators = map[string]func(fieldValue string) bool{ - entityType: func(v string) bool { return v == ev.expectedEntity.entityType }, - entityName: func(v string) bool { return v == ev.expectedEntity.name }, - entityEnvironment: func(v string) bool { return v == ev.expectedEntity.environment }, - entityPlatform: func(v string) bool { return v == ev.expectedEntity.platformType }, - entityEKSCluster: func(v string) bool { return v == ev.expectedEntity.eksCluster }, - entityK8sNode: func(v string) bool { return v == ev.expectedEntity.k8sNode }, - entityK8sNamespace: func(v string) bool { return v == ev.expectedEntity.k8sNamespace }, - entityK8sWorkload: func(v string) bool { return v == ev.expectedEntity.k8sWorkload }, - entityServiceNameSource: func(v string) bool { return v == ev.expectedEntity.serviceNameSource }, - } - } - return ev -} - -// ValidateField checks if a field is expected and matches the expected value. -func (ev *EntityValidator) ValidateField(field, value string, t *testing.T) { - if validator, ok := ev.fieldValidators[field]; ok { - ev.requiredFields[field] = true - assert.True(t, validator(value), "Validation failed for field %s", field) - } -} - -// AllFieldsPresent ensures all required fields are found. -func (ev *EntityValidator) AllFieldsPresent() bool { - for _, present := range ev.requiredFields { - if !present { - return false - } - } - return true -} - -type expectedEntity struct { - entityType string - name string - environment string - platformType string - k8sWorkload string - k8sNode string - k8sNamespace string - eksCluster string - serviceNameSource string - instanceId string -} - func init() { environment.RegisterEnvironmentMetaDataFlags() } @@ -161,58 +59,58 @@ func TestPutLogEventEntityEKS(t *testing.T) { agentConfigPath string podName string useEC2Tag bool - expectedEntity expectedEntity + expectedEntity common.ExpectedEntity }{ "Entity/K8sWorkloadServiceNameSource": { agentConfigPath: filepath.Join("resources", "compass_default_log.json"), podName: "log-generator", - expectedEntity: expectedEntity{ - entityType: eksServiceEntityType, - name: "log-generator", - environment: "eks:" + env.EKSClusterName + "/" + k8sDefaultNamespace, - platformType: entityEKSPlatform, - k8sWorkload: "log-generator", - k8sNode: *instancePrivateDNS, - k8sNamespace: k8sDefaultNamespace, - eksCluster: env.EKSClusterName, - instanceId: env.InstanceId, - serviceNameSource: entityServiceNameSourceK8sWorkload, + expectedEntity: common.ExpectedEntity{ + EntityType: eksServiceEntityType, + Name: "log-generator", + Environment: "eks:" + env.EKSClusterName + "/" + k8sDefaultNamespace, + PlatformType: entityEKSPlatform, + K8sWorkload: "log-generator", + K8sNode: *instancePrivateDNS, + K8sNamespace: k8sDefaultNamespace, + EksCluster: env.EKSClusterName, + InstanceId: env.InstanceId, + ServiceNameSource: entityServiceNameSourceK8sWorkload, }, }, "Entity/InstrumentationServiceNameSource": { agentConfigPath: filepath.Join("resources", "compass_default_log.json"), podName: "petclinic-instrumentation-default-env", - expectedEntity: expectedEntity{ - entityType: eksServiceEntityType, + expectedEntity: common.ExpectedEntity{ + EntityType: eksServiceEntityType, // This service name comes from OTEL_SERVICE_NAME which is // customized in the terraform code when creating the pod - name: "petclinic-custom-service-name", - environment: "eks:" + env.EKSClusterName + "/" + k8sDefaultNamespace, - platformType: entityEKSPlatform, - k8sWorkload: "petclinic-instrumentation-default-env", - k8sNode: *instancePrivateDNS, - k8sNamespace: k8sDefaultNamespace, - eksCluster: env.EKSClusterName, - instanceId: env.InstanceId, - serviceNameSource: entityServiceNameSourceInstrumentation, + Name: "petclinic-custom-service-name", + Environment: "eks:" + env.EKSClusterName + "/" + k8sDefaultNamespace, + PlatformType: entityEKSPlatform, + K8sWorkload: "petclinic-instrumentation-default-env", + K8sNode: *instancePrivateDNS, + K8sNamespace: k8sDefaultNamespace, + EksCluster: env.EKSClusterName, + InstanceId: env.InstanceId, + ServiceNameSource: entityServiceNameSourceInstrumentation, }, }, "Entity/InstrumentationServiceNameSourceCustomEnvironment": { agentConfigPath: filepath.Join("resources", "compass_default_log.json"), podName: "petclinic-instrumentation-custom-env", - expectedEntity: expectedEntity{ - entityType: eksServiceEntityType, + expectedEntity: common.ExpectedEntity{ + EntityType: eksServiceEntityType, // This service name comes from OTEL_SERVICE_NAME which is // customized in the terraform code when creating the pod - name: "petclinic-custom-service-name", - environment: "petclinic-custom-environment", - platformType: entityEKSPlatform, - k8sWorkload: "petclinic-instrumentation-custom-env", - k8sNode: *instancePrivateDNS, - k8sNamespace: k8sDefaultNamespace, - eksCluster: env.EKSClusterName, - instanceId: env.InstanceId, - serviceNameSource: entityServiceNameSourceInstrumentation, + Name: "petclinic-custom-service-name", + Environment: "petclinic-custom-environment", + PlatformType: entityEKSPlatform, + K8sWorkload: "petclinic-instrumentation-custom-env", + K8sNode: *instancePrivateDNS, + K8sNamespace: k8sDefaultNamespace, + EksCluster: env.EKSClusterName, + InstanceId: env.InstanceId, + ServiceNameSource: entityServiceNameSourceInstrumentation, }, }, } @@ -232,34 +130,7 @@ func TestPutLogEventEntityEKS(t *testing.T) { assert.NotEmpty(t, podApplicationLogStream) // check CWL to ensure we got the expected entities in the log group queryString := fmt.Sprintf("fields @message, @entity.KeyAttributes.Type, @entity.KeyAttributes.Name, @entity.KeyAttributes.Environment, @entity.Attributes.PlatformType, @entity.Attributes.EKS.Cluster, @entity.Attributes.K8s.Node, @entity.Attributes.K8s.Namespace, @entity.Attributes.K8s.Workload, @entity.Attributes.AWS.ServiceNameSource, @entity.Attributes.EC2.InstanceId | filter @logStream == \"%s\"", podApplicationLogStream) - ValidateLogEntity(t, appLogGroup, podApplicationLogStream, &end, queryString, testCase.expectedEntity, string(env.ComputeType)) + common.ValidateLogEntity(t, appLogGroup, podApplicationLogStream, &end, queryString, testCase.expectedEntity, string(env.ComputeType)) }) } -} - -// ValidateLogEntity performs the entity validation for PutLogEvents. -func ValidateLogEntity(t *testing.T, logGroup, logStream string, end *time.Time, queryString string, expectedEntity expectedEntity, entityPlatformType string) { - log.Printf("Checking log group/stream: %s/%s", logGroup, logStream) - if !awsservice.IsLogGroupExists(logGroup) { - t.Fatalf("application log group used for entity validation doesn't exist: %s", logGroup) - } - - begin := end.Add(-2 * time.Minute) - log.Printf("Start time is %s and end time is %s", begin.String(), end.String()) - - result, err := awsservice.GetLogQueryResults(logGroup, begin.Unix(), end.Unix(), queryString) - assert.NoError(t, err) - if !assert.NotZero(t, len(result)) { - return - } - - validator := NewEntityValidator(entityPlatformType, expectedEntity) - for _, field := range result[0] { - fieldName := aws.ToString(field.Field) - fieldValue := aws.ToString(field.Value) - validator.ValidateField(fieldName, fieldValue, t) - fmt.Printf("%s: %s\n", fieldName, fieldValue) - } - - assert.True(t, validator.AllFieldsPresent(), "Not all required fields were found") -} +} \ No newline at end of file diff --git a/util/common/entity.go b/util/common/entity.go new file mode 100644 index 000000000..5623723bd --- /dev/null +++ b/util/common/entity.go @@ -0,0 +1,147 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "fmt" + "log" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/stretchr/testify/assert" + + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" +) + +const ( + entityType = "@entity.KeyAttributes.Type" + entityName = "@entity.KeyAttributes.Name" + entityEnvironment = "@entity.KeyAttributes.Environment" + + entityPlatform = "@entity.Attributes.PlatformType" + entityInstanceId = "@entity.Attributes.EC2.InstanceId" + entityEKSCluster = "@entity.Attributes.EKS.Cluster" + entityK8sNode = "@entity.Attributes.K8s.Node" + entityK8sNamespace = "@entity.Attributes.K8s.Namespace" + entityK8sWorkload = "@entity.Attributes.K8s.Workload" + entityServiceNameSource = "@entity.Attributes.AWS.ServiceNameSource" +) + +type ExpectedEntity struct { + EntityType string + Name string + Environment string + PlatformType string + K8sWorkload string + K8sNode string + K8sNamespace string + EksCluster string + ServiceNameSource string + InstanceId string +} + +type EntityValidator struct { + requiredFields map[string]bool + expectedEntity ExpectedEntity + platformType string + fieldValidators map[string]func(fieldValue string) bool +} + +// NewEntityValidator initializes the validator based on the platform type. +func NewEntityValidator(platformType string, expected ExpectedEntity) *EntityValidator { + ev := &EntityValidator{ + expectedEntity: expected, + platformType: platformType, + requiredFields: make(map[string]bool), + fieldValidators: make(map[string]func(fieldValue string) bool), + } + + // Define platform-specific required fields and validators + if platformType == "EC2" { + ev.requiredFields = map[string]bool{ + entityType: false, + entityName: false, + entityEnvironment: false, + entityPlatform: false, + entityInstanceId: false, + } + ev.fieldValidators = map[string]func(fieldValue string) bool{ + entityType: func(v string) bool { return v == ev.expectedEntity.EntityType }, + entityName: func(v string) bool { return v == ev.expectedEntity.Name }, + entityEnvironment: func(v string) bool { return v == ev.expectedEntity.Environment }, + entityPlatform: func(v string) bool { return v == ev.expectedEntity.PlatformType }, + entityInstanceId: func(v string) bool { return v == ev.expectedEntity.InstanceId }, + } + } else if platformType == "EKS" { + ev.requiredFields = map[string]bool{ + entityType: false, + entityName: false, + entityEnvironment: false, + entityPlatform: false, + entityEKSCluster: false, + entityK8sNode: false, + entityK8sNamespace: false, + entityK8sWorkload: false, + entityServiceNameSource: false, + } + ev.fieldValidators = map[string]func(fieldValue string) bool{ + entityType: func(v string) bool { return v == ev.expectedEntity.EntityType }, + entityName: func(v string) bool { return v == ev.expectedEntity.Name }, + entityEnvironment: func(v string) bool { return v == ev.expectedEntity.Environment }, + entityPlatform: func(v string) bool { return v == ev.expectedEntity.PlatformType }, + entityEKSCluster: func(v string) bool { return v == ev.expectedEntity.EksCluster }, + entityK8sNode: func(v string) bool { return v == ev.expectedEntity.K8sNode }, + entityK8sNamespace: func(v string) bool { return v == ev.expectedEntity.K8sNamespace }, + entityK8sWorkload: func(v string) bool { return v == ev.expectedEntity.K8sWorkload }, + entityServiceNameSource: func(v string) bool { return v == ev.expectedEntity.ServiceNameSource }, + } + } + return ev +} + +// ValidateField checks if a field is expected and matches the expected value. +func (ev *EntityValidator) ValidateField(field, value string, t *testing.T) { + if validator, ok := ev.fieldValidators[field]; ok { + ev.requiredFields[field] = true + assert.True(t, validator(value), "Validation failed for field %s", field) + } +} + +// AllFieldsPresent ensures all required fields are found. +func (ev *EntityValidator) AllFieldsPresent() bool { + for _, present := range ev.requiredFields { + if !present { + return false + } + } + return true +} + +// ValidateLogEntity validates the entity data for both EC2 and EKS +func ValidateLogEntity(t *testing.T, logGroup, logStream string, end *time.Time, queryString string, expectedEntity ExpectedEntity, entityPlatformType string) { + log.Printf("Checking log group/stream: %s/%s", logGroup, logStream) + if !awsservice.IsLogGroupExists(logGroup) { + t.Fatalf("application log group used for entity validation doesn't exist: %s", logGroup) + } + + begin := end.Add(-2 * time.Minute) + log.Printf("Start time is %s and end time is %s", begin.String(), end.String()) + + result, err := awsservice.GetLogQueryResults(logGroup, begin.Unix(), end.Unix(), queryString) + assert.NoError(t, err) + if !assert.NotZero(t, len(result)) { + return + } + + validator := NewEntityValidator(entityPlatformType, expectedEntity) + for _, field := range result[0] { + fieldName := aws.ToString(field.Field) + fieldValue := aws.ToString(field.Value) + validator.ValidateField(fieldName, fieldValue, t) + fmt.Printf("%s: %s\n", fieldName, fieldValue) + } + + assert.True(t, validator.AllFieldsPresent(), "Not all required fields were found") +} \ No newline at end of file From 6c272ee69cd65fe2afa79dafeb62a04aaef37606 Mon Sep 17 00:00:00 2001 From: Varun C Date: Thu, 5 Dec 2024 02:46:50 +0000 Subject: [PATCH 24/25] Increase flush time --- util/common/entity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/common/entity.go b/util/common/entity.go index 5623723bd..bd33484a0 100644 --- a/util/common/entity.go +++ b/util/common/entity.go @@ -126,7 +126,7 @@ func ValidateLogEntity(t *testing.T, logGroup, logStream string, end *time.Time, t.Fatalf("application log group used for entity validation doesn't exist: %s", logGroup) } - begin := end.Add(-2 * time.Minute) + begin := end.Add(-12 * time.Minute) log.Printf("Start time is %s and end time is %s", begin.String(), end.String()) result, err := awsservice.GetLogQueryResults(logGroup, begin.Unix(), end.Unix(), queryString) From 58f0a88378429cf941d9bf64995b25f3f908ac16 Mon Sep 17 00:00:00 2001 From: Varun C Date: Thu, 5 Dec 2024 03:10:33 +0000 Subject: [PATCH 25/25] run linter --- test/entity/entity_test.go | 4 ++-- util/common/entity.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/entity/entity_test.go b/test/entity/entity_test.go index cdc7292f9..04aa39694 100644 --- a/test/entity/entity_test.go +++ b/test/entity/entity_test.go @@ -14,8 +14,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/aws/amazon-cloudwatch-agent-test/environment" - "github.com/aws/amazon-cloudwatch-agent-test/util/common" "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" + "github.com/aws/amazon-cloudwatch-agent-test/util/common" ) const ( @@ -133,4 +133,4 @@ func TestPutLogEventEntityEKS(t *testing.T) { common.ValidateLogEntity(t, appLogGroup, podApplicationLogStream, &end, queryString, testCase.expectedEntity, string(env.ComputeType)) }) } -} \ No newline at end of file +} diff --git a/util/common/entity.go b/util/common/entity.go index bd33484a0..70d34c8cd 100644 --- a/util/common/entity.go +++ b/util/common/entity.go @@ -144,4 +144,4 @@ func ValidateLogEntity(t *testing.T, logGroup, logStream string, end *time.Time, } assert.True(t, validator.AllFieldsPresent(), "Not all required fields were found") -} \ No newline at end of file +}