diff --git a/.github/workflows/integrationTest.yml b/.github/workflows/integrationTest.yml index dc3474af22..49e23f262f 100644 --- a/.github/workflows/integrationTest.yml +++ b/.github/workflows/integrationTest.yml @@ -361,8 +361,8 @@ jobs: path: go.mod key: ec2-linux-integration-test-${{ github.sha }}-${{ matrix.arrays.os }} - - name: Echo OS - run: echo run on ec2 instance os ${{ matrix.arrays.os }} + - name: Echo Test Info + run: echo run on ec2 instance os ${{ matrix.arrays.os }} test dir ${{ matrix.arrays.test_dir }} - name: Verify Terraform version run: terraform --version @@ -392,7 +392,7 @@ jobs: -var="key_name=${KEY_NAME}" \ -var="test_name=cw-integ-test-${{ matrix.arrays.os }}" \ -var="iam_instance_profile=${IAM_ROLE}" \ - -var="tag=${{ matrix.arrays.tag }}" ; then terraform destroy -auto-approve + -var="test_dir=${{ matrix.arrays.test_dir }}" ; then terraform destroy -auto-approve else terraform destroy -auto-approve && exit 1 fi diff --git a/go.mod b/go.mod index 250f62ac44..86382b2f22 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.16.2 github.com/aws/aws-sdk-go-v2/config v1.15.3 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.18.1 github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.4 github.com/aws/aws-sdk-go-v2/service/ec2 v1.29.0 github.com/aws/smithy-go v1.11.2 diff --git a/go.sum b/go.sum index 4da0653ec8..34f7ed09ac 100644 --- a/go.sum +++ b/go.sum @@ -174,6 +174,8 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 h1:9stUQR/u2KXU6HkFJYl github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.18.1 h1:8PHGmLw1QbTdXfgEpXclOk3kob72vkc/cEoyBxkmR0M= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.18.1/go.mod h1:Z+8JhhltQDM1vIHvEtQLr1wVVAqQVLpvCDMVqYBrwr8= github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.4 h1:mBqjBKtZzvAc9j7gU+FEHbhTKSr02iqMOdQIL/7GZ78= github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.4/go.mod h1:R49Py2lGoKH7bCpwhjN9l7MfR/PU6zHXn1tCRR8cwOs= github.com/aws/aws-sdk-go-v2/service/ec2 v1.29.0 h1:7jk4NfzDnnSbaR9E4mOBWRZXQThq5rsqjlDC+uu9dsI= diff --git a/integration/generator/test_case_generator.go b/integration/generator/test_case_generator.go index a9719869e5..3abccd67ae 100644 --- a/integration/generator/test_case_generator.go +++ b/integration/generator/test_case_generator.go @@ -15,24 +15,27 @@ const ( linux = "linux" windows = "windows" mac = "mac" - tag = "tag" + testDir = "test_dir" ) //you can't have a const map in golang -var osToTagMap = map[string][]string{ - linux: {"integration"}, - windows: {}, +var osToTestDirMap = map[string][]string{ + linux: {"./integration/test/ca_bundle", + "./integration/test/cloudwatchlogs", + "./integration/test/metrics_number_dimension"}, + // @TODO add real tests + windows: {""}, mac: {}, } func main() { - for osType, tags := range osToTagMap { - testMatrix := genMatrix(osType, tags) + for osType, testDir := range osToTestDirMap { + testMatrix := genMatrix(osType, testDir) writeTestMatrixFile(osType, testMatrix) } } -func genMatrix(targetOS string, tags []string) []map[string]string { +func genMatrix(targetOS string, testDirList []string) []map[string]string { openTestMatrix, err := os.Open(fmt.Sprintf("integration/generator/resources/%v_test_matrix.json", targetOS)) if err != nil { log.Panicf("can't read file %v_test_matrix.json err %v", targetOS, err) @@ -47,11 +50,11 @@ func genMatrix(targetOS string, tags []string) []map[string]string { var testMatrixComplete []map[string]string for _, test := range testMatrix { - testLine := copyMap(test) - for _, testTag := range tags { - testLine[tag] = testTag + for _, testDirectory := range testDirList { + testLine := copyMap(test) + testLine[testDir] = testDirectory + testMatrixComplete = append(testMatrixComplete, testLine) } - testMatrixComplete = append(testMatrixComplete, testLine) } return testMatrixComplete } diff --git a/integration/terraform/ec2/linux/README.md b/integration/terraform/ec2/linux/README.md index 269aeda580..1ddea73f07 100644 --- a/integration/terraform/ec2/linux/README.md +++ b/integration/terraform/ec2/linux/README.md @@ -73,6 +73,7 @@ for how to easily generate a new policy. "Action": [ "cloudwatch:GetMetricData", "cloudwatch:PutMetricData", + "cloudwatch:ListMetrics" "ec2:DescribeVolumes", "ec2:DescribeTags", "logs:PutLogEvents", diff --git a/integration/terraform/ec2/linux/main.tf b/integration/terraform/ec2/linux/main.tf index fccd24985f..6229c1a9da 100644 --- a/integration/terraform/ec2/linux/main.tf +++ b/integration/terraform/ec2/linux/main.tf @@ -24,7 +24,8 @@ resource "aws_instance" "integration-test" { "export AWS_REGION=${var.region}", "echo run tests with the tag integration, one at a time, and verbose", "cd ~/amazon-cloudwatch-agent", - "go test ./integration/test/... -p 1 -v --tags=${var.tag}" + "echo run sanity test && go test ./integration/test/sanity -p 1 -v --tags=integration", + "go test ${var.test_dir} -p 1 -v --tags=integration" ] connection { type = "ssh" diff --git a/integration/terraform/ec2/linux/variables.tf b/integration/terraform/ec2/linux/variables.tf index aab8b2d424..fdbf017df9 100644 --- a/integration/terraform/ec2/linux/variables.tf +++ b/integration/terraform/ec2/linux/variables.tf @@ -84,7 +84,7 @@ variable "test_name" { default = "" } -variable "tag" { +variable "test_dir" { type = string default = "" } \ No newline at end of file diff --git a/integration/test/agent_util.go b/integration/test/agent_util.go index 03313974b4..84ffe7b814 100644 --- a/integration/test/agent_util.go +++ b/integration/test/agent_util.go @@ -7,7 +7,11 @@ package test import ( + "context" "fmt" + "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/cloudwatch" "log" "os/exec" "path/filepath" @@ -93,3 +97,28 @@ func ReplaceLocalStackHostName(pathIn string) { log.Fatal(fmt.Sprint(err) + string(out)) } } + +func GetInstanceId() string { + ctx := context.Background() + c, err := config.LoadDefaultConfig(ctx) + if err != nil { + // fail fast so we don't continue the test + log.Fatalf("Error occurred while creating SDK config: %v", err) + } + + // TODO: this only works for EC2 based testing + client := imds.NewFromConfig(c) + metadata, err := client.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{}) + if err != nil { + log.Fatalf("Error occurred while retrieving EC2 instance ID: %v", err) + } + return metadata.InstanceID +} + +func GetCWClient(cxt context.Context) *cloudwatch.Client { + defaultConfig, err := config.LoadDefaultConfig(cxt) + if err != nil { + log.Fatalf("err occurred while creating config %v", err) + } + return cloudwatch.NewFromConfig(defaultConfig) +} diff --git a/integration/test/cloudwatchlogs/publish_logs_test.go b/integration/test/cloudwatchlogs/publish_logs_test.go index 82682f415f..db777162db 100644 --- a/integration/test/cloudwatchlogs/publish_logs_test.go +++ b/integration/test/cloudwatchlogs/publish_logs_test.go @@ -7,11 +7,8 @@ package cloudwatchlogs import ( - "context" "fmt" "github.com/aws/amazon-cloudwatch-agent/integration/test" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "log" "os" @@ -54,20 +51,7 @@ var testParameters = []input{ func TestWriteLogsToCloudWatch(t *testing.T) { // this uses the {instance_id} placeholder in the agent configuration, // so we need to determine the host's instance ID for validation - ctx := context.Background() - c, err := config.LoadDefaultConfig(ctx) - if err != nil { - // fail fast so we don't continue the test - t.Fatalf("Error occurred while creating SDK config: %v", err) - } - - // TODO: this only works for EC2 based testing - client := imds.NewFromConfig(c) - metadata, err := client.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{}) - if err != nil { - t.Fatalf("Error occurred while retrieving EC2 instance ID: %v", err) - } - instanceId := metadata.InstanceID + instanceId := test.GetInstanceId() log.Printf("Found instance id %s", instanceId) defer cleanUp(instanceId) diff --git a/integration/test/empty/empty_test.go b/integration/test/empty/empty_test.go deleted file mode 100644 index 978b492206..0000000000 --- a/integration/test/empty/empty_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT - -//go:build integration -// +build integration - -package empty - -import "testing" - -//sanity check test context works -func TestEmpty(t *testing.T) { - -} diff --git a/integration/test/metrics_number_dimension/metrics_number_dimension_test.go b/integration/test/metrics_number_dimension/metrics_number_dimension_test.go new file mode 100644 index 0000000000..162851cbbf --- /dev/null +++ b/integration/test/metrics_number_dimension/metrics_number_dimension_test.go @@ -0,0 +1,136 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build linux && integration +// +build linux,integration + +package metrics_number_dimension + +import ( + "context" + "fmt" + "github.com/aws/amazon-cloudwatch-agent/integration/test" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "log" + "strings" + "testing" + "time" +) + +const configOutputPath = "/opt/aws/amazon-cloudwatch-agent/bin/config.json" +const configJSON = "/config.json" +const namespace = "MetricNumberDimensionTest" +const instanceId = "InstanceId" +const appendMetric = "append" + +// @TODO use the value from plugins/outputs/cloudwatch/cloudwatch.go when https://github.com/aws/amazon-cloudwatch-agent/pull/361 is merged +const maxDimension = 30 + +//Let the agent run for 2 minutes. This will give agent enough time to call server +const agentRuntime = 2 * time.Minute + +const targetString = "max MaxDimensions %v is less than than number of dimensions %v thus only taking the max number" + +type input struct { + resourcePath string + findTarget bool + numberDimensionsInCW int + metricName string +} + +type metric struct { + name string + value string +} + +//Must run this test with parallel 1 since this will fail if more than one test is running at the same time +func TestNumberMetricDimension(t *testing.T) { + + parameters := []input{ + { + resourcePath: "resources/10_dimension", + findTarget: false, + numberDimensionsInCW: 10, + metricName: "mem_used_percent", + }, + // @TODO add when https://github.com/aws/amazon-cloudwatch-agent/pull/361 is merged + // {resourcePath: "resources/30_dimension", findTarget: false, numberDimensionsInCW: 30, metricName: "mem_used_percent",}, + // {resourcePath: "resources/35_dimension", findTarget: true, numberDimensionsInCW: 30, metricName: "mem_used_percent",}, + } + + for _, parameter := range parameters { + //before test run + log.Printf("resource file location %s find target %t input number dimension %d metric name %s", + parameter.resourcePath, parameter.findTarget, parameter.numberDimensionsInCW, parameter.metricName) + + target := fmt.Sprintf(targetString, maxDimension, parameter.numberDimensionsInCW) + + t.Run(fmt.Sprintf("resource file location %s find target %t", parameter.resourcePath, parameter.findTarget), func(t *testing.T) { + test.CopyFile(parameter.resourcePath+configJSON, configOutputPath) + test.StartAgent(configOutputPath) + time.Sleep(agentRuntime) + log.Printf("Agent has been running for : %s", agentRuntime.String()) + test.StopAgent() + + // test for target string + output := test.ReadAgentOutput(agentRuntime) + containsTarget := outputLogContainsTarget(output, target) + if (parameter.findTarget && !containsTarget) || (!parameter.findTarget && containsTarget) { + t.Errorf("Find target is %t contains target is %t", parameter.findTarget, containsTarget) + } + + // test for cloud watch metrics + cxt := context.Background() + dimensionFilter := buildDimensionFilterList(parameter.numberDimensionsInCW) + client := test.GetCWClient(cxt) + listMetricsInput := cloudwatch.ListMetricsInput{ + MetricName: aws.String(parameter.metricName), + Namespace: aws.String(namespace), + Dimensions: dimensionFilter, + } + data, err := client.ListMetrics(cxt, &listMetricsInput) + if err != nil { + t.Errorf("Error getting metric data %v", err) + } + if len(data.Metrics) == 0 { + metrics := make([]metric, len(dimensionFilter)) + for i, filter := range dimensionFilter { + metrics[i] = metric{ + name: *filter.Name, + value: *filter.Value, + } + } + t.Errorf("No metrics found for dimension %v metric name %v namespace %v", + metrics, parameter.metricName, namespace) + } + }) + } +} + +func buildDimensionFilterList(appendDimension int) []types.DimensionFilter { + // we append dimension from 0 to max number - 2 + // then we add dimension instance id + // thus for max dimension 10, 0 to 8 + instance id = 10 dimension + ec2InstanceId := test.GetInstanceId() + dimensionFilter := make([]types.DimensionFilter, appendDimension) + for i := 0; i < appendDimension-1; i++ { + dimensionFilter[i] = types.DimensionFilter{ + Name: aws.String(fmt.Sprintf("%s%d", appendMetric, i)), + Value: aws.String(fmt.Sprintf("%s%d", appendMetric, i)), + } + } + dimensionFilter[appendDimension-1] = types.DimensionFilter{ + Name: aws.String(instanceId), + Value: aws.String(ec2InstanceId), + } + return dimensionFilter +} + +func outputLogContainsTarget(output string, targetString string) bool { + log.Printf("Log file %s", output) + contains := strings.Contains(output, targetString) + log.Printf("Log file contains target string %t", contains) + return contains +} diff --git a/integration/test/metrics_number_dimension/resources/10_dimension/config.json b/integration/test/metrics_number_dimension/resources/10_dimension/config.json new file mode 100644 index 0000000000..04dff7afe1 --- /dev/null +++ b/integration/test/metrics_number_dimension/resources/10_dimension/config.json @@ -0,0 +1,33 @@ +{ + "agent": { + "metrics_collection_interval": 60, + "run_as_user": "root", + "debug": true, + "logfile": "" + }, + "metrics": { + "namespace": "MetricNumberDimensionTest", + "append_dimensions": { + "InstanceId": "${aws:InstanceId}" + }, + "metrics_collected": { + "mem": { + "measurement": [ + "mem_used_percent" + ], + "metrics_collection_interval": 60, + "append_dimensions": { + "append0": "append0", + "append1": "append1", + "append2": "append2", + "append3": "append3", + "append4": "append4", + "append5": "append5", + "append6": "append6", + "append7": "append7", + "append8": "append8" + } + } + } + } +} \ No newline at end of file diff --git a/integration/test/metrics_number_dimension/resources/30_dimension/config.json b/integration/test/metrics_number_dimension/resources/30_dimension/config.json new file mode 100644 index 0000000000..164c13dc61 --- /dev/null +++ b/integration/test/metrics_number_dimension/resources/30_dimension/config.json @@ -0,0 +1,53 @@ +{ + "agent": { + "metrics_collection_interval": 60, + "run_as_user": "root", + "debug": true, + "logfile": "" + }, + "metrics": { + "namespace": "MetricNumberDimensionTest", + "append_dimensions": { + "InstanceId": "${aws:InstanceId}" + }, + "metrics_collected": { + "mem": { + "measurement": [ + "mem_used_percent" + ], + "metrics_collection_interval": 60, + "append_dimensions": { + "append0": "append0", + "append1": "append1", + "append2": "append2", + "append3": "append3", + "append4": "append4", + "append5": "append5", + "append6": "append6", + "append7": "append7", + "append8": "append8", + "append9": "append9", + "append10": "append10", + "append11": "append11", + "append12": "append12", + "append13": "append13", + "append14": "append14", + "append15": "append15", + "append16": "append16", + "append17": "append17", + "append18": "append18", + "append19": "append19", + "append20": "append20", + "append21": "append21", + "append22": "append22", + "append23": "append23", + "append24": "append24", + "append25": "append25", + "append26": "append26", + "append27": "append27", + "append28": "append28" + } + } + } + } +} \ No newline at end of file diff --git a/integration/test/metrics_number_dimension/resources/35_dimension/config.json b/integration/test/metrics_number_dimension/resources/35_dimension/config.json new file mode 100644 index 0000000000..0706a3b30c --- /dev/null +++ b/integration/test/metrics_number_dimension/resources/35_dimension/config.json @@ -0,0 +1,58 @@ +{ + "agent": { + "metrics_collection_interval": 60, + "run_as_user": "root", + "debug": true, + "logfile": "" + }, + "metrics": { + "namespace": "MetricNumberDimensionTest", + "append_dimensions": { + "InstanceId": "${aws:InstanceId}" + }, + "metrics_collected": { + "mem": { + "measurement": [ + "mem_used_percent" + ], + "metrics_collection_interval": 60, + "append_dimensions": { + "append0": "append0", + "append1": "append1", + "append2": "append2", + "append3": "append3", + "append4": "append4", + "append5": "append5", + "append6": "append6", + "append7": "append7", + "append8": "append8", + "append9": "append9", + "append10": "append10", + "append11": "append11", + "append12": "append12", + "append13": "append13", + "append14": "append14", + "append15": "append15", + "append16": "append16", + "append17": "append17", + "append18": "append18", + "append19": "append19", + "append20": "append20", + "append21": "append21", + "append22": "append22", + "append23": "append23", + "append24": "append24", + "append25": "append25", + "append26": "append26", + "append27": "append27", + "append28": "append28", + "append29": "append29", + "append30": "append30", + "append31": "append31", + "append32": "append32", + "append33": "append33" + } + } + } + } +} \ No newline at end of file