diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml index ace35f56f..57c6d54ec 100644 --- a/.github/workflows/build-check.yml +++ b/.github/workflows/build-check.yml @@ -25,6 +25,9 @@ jobs: - name: Lint Terraform if: runner.os == 'Linux' run: cd terraform && make check-fmt + - name: Lint Go + if: runner.os == 'Linux' + run: make simple-lint - name: Compile tests run: | echo "Compile tests" diff --git a/Makefile b/Makefile index 625211eb2..59ccb8485 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,16 @@ BASE_SPACE=$(shell pwd) BUILD_SPACE=$(BASE_SPACE)/build +IMPORT_PATH=github.com/aws/amazon-cloudwatch-agent-test +ALL_SRC := $(shell find . -name '*.go' -type f | sort) TOOLS_BIN_DIR := $(abspath ./build/tools) + +GOIMPORTS = $(TOOLS_BIN_DIR)/goimports LINTER = $(TOOLS_BIN_DIR)/golangci-lint +IMPI = $(TOOLS_BIN_DIR)/impi +ADDLICENSE = $(TOOLS_BIN_DIR)/addlicense + +GOIMPORTS_OPT?= -w -local $(IMPORT_PATH) WIN_BUILD = GOOS=windows GOARCH=amd64 go build -trimpath -o $(BUILD_SPACE) LINUX_AMD64_BUILD = CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -o $(BUILD_SPACE) @@ -22,14 +30,56 @@ LOADGEN_LINUX_ARM64_BUILD = $(LINUX_ARM64_BUILD)/linux/arm64 LOADGEN_DARWIN_AMD64_BUILD = $(DARWIN_AMD64_BUILD)/darwin/amd64 LOADGEN_DARWIN_ARM64_BUILD = $(DARWIN_ARM64_BUILD)/darwin/arm64 -install-tools: +install-goimports: + GOBIN=$(TOOLS_BIN_DIR) go install golang.org/x/tools/cmd/goimports + +install-impi: + GOBIN=$(TOOLS_BIN_DIR) go install github.com/pavius/impi/cmd/impi@v0.0.3 + +install-addlicense: + # Using 04bfe4e to get SPDX template changes that are not present in the most recent tag v1.0.0 + # This is required to be able to easily omit the year in our license header. + GOBIN=$(TOOLS_BIN_DIR) go install github.com/google/addlicense@04bfe4e + +install-golang-lint: #Install from source for golangci-lint is not recommended based on https://golangci-lint.run/usage/install/#install-from-source so using binary #installation curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TOOLS_BIN_DIR) v1.50.1 -lint: install-tools +fmt: install-goimports addlicense + go fmt ./... + @echo $(ALL_SRC) | xargs -n 10 $(GOIMPORTS) $(GOIMPORTS_OPT) + +impi: install-impi + @echo $(ALL_SRC) | xargs -n 10 $(IMPI) --local $(IMPORT_PATH) --scheme stdThirdPartyLocal + @echo "Check import order/grouping finished" + +simple-lint: checklicense impi + +lint: install-golang-lint simple-lint ${LINTER} run ./... +addlicense: install-addlicense + @ADDLICENSEOUT=`$(ADDLICENSE) -y="" -s=only -l="mit" -c="Amazon.com, Inc. or its affiliates. All Rights Reserved." $(ALL_SRC) 2>&1`; \ + if [ "$$ADDLICENSEOUT" ]; then \ + echo "$(ADDLICENSE) FAILED => add License errors:\n"; \ + echo "$$ADDLICENSEOUT\n"; \ + exit 1; \ + else \ + echo "Add License finished successfully"; \ + fi + +checklicense: install-addlicense + @ADDLICENSEOUT=`$(ADDLICENSE) -check $(ALL_SRC) 2>&1`; \ + if [ "$$ADDLICENSEOUT" ]; then \ + echo "$(ADDLICENSE) FAILED => add License errors:\n"; \ + echo "$$ADDLICENSEOUT\n"; \ + echo "Use 'make addlicense' to fix this."; \ + exit 1; \ + else \ + echo "Check License finished successfully"; \ + fi + compile: # this is a workaround to compile and cache all of the tests without actually running any of them go test -run=NO_MATCH ./... diff --git a/cmd/emf-generator/emf-generator.go b/cmd/emf-generator/emf-generator.go index cf0d0604c..175a0b4ae 100644 --- a/cmd/emf-generator/emf-generator.go +++ b/cmd/emf-generator/emf-generator.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package main import ( diff --git a/cmd/log-generator/log-generator.go b/cmd/log-generator/log-generator.go index 007db4b49..041849a46 100644 --- a/cmd/log-generator/log-generator.go +++ b/cmd/log-generator/log-generator.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package main import ( diff --git a/cmd/statsd-generator/statsd-generator.go b/cmd/statsd-generator/statsd-generator.go index 37ab94c98..6b0458499 100644 --- a/cmd/statsd-generator/statsd-generator.go +++ b/cmd/statsd-generator/statsd-generator.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package main import ( diff --git a/filesystem/unix_permission.go b/filesystem/unix_permission.go index ad029c024..67e382808 100644 --- a/filesystem/unix_permission.go +++ b/filesystem/unix_permission.go @@ -7,8 +7,9 @@ package filesystem import ( "fmt" - "golang.org/x/sys/unix" "os/user" + + "golang.org/x/sys/unix" ) type FilePermission string diff --git a/filesystem/windows_permission.go b/filesystem/windows_permission.go index 65820157a..23f680de5 100644 --- a/filesystem/windows_permission.go +++ b/filesystem/windows_permission.go @@ -7,9 +7,10 @@ package filesystem import ( "fmt" - "golang.org/x/sys/windows" "os" "unsafe" + + "golang.org/x/sys/windows" ) // CheckFileRights check that the given filename has access controls and system permission for Administrator, Local System diff --git a/generator/resources/ecs_ec2_daemon_test_matrix.json b/generator/resources/ecs_ec2_daemon_test_matrix.json index 71a236920..82602d987 100644 --- a/generator/resources/ecs_ec2_daemon_test_matrix.json +++ b/generator/resources/ecs_ec2_daemon_test_matrix.json @@ -2,6 +2,13 @@ { "instanceType":"t3a.xlarge", "ami": "cloudwatch-agent-integration-test-al2*", - "family": "linux" + "family": "linux", + "metadataEnabled": "enabled" + }, + { + "instanceType":"t3a.xlarge", + "ami": "cloudwatch-agent-integration-test-al2*", + "family": "linux", + "metadataEnabled": "disabled" } ] \ No newline at end of file diff --git a/generator/test_case_generator.go b/generator/test_case_generator.go index 0656de5f3..84699f108 100644 --- a/generator/test_case_generator.go +++ b/generator/test_case_generator.go @@ -31,16 +31,21 @@ type matrixRow struct { TerraformDir string `json:"terraform_dir"` UseSSM bool `json:"useSSM"` ExcludedTests string `json:"excludedTests"` + MetadataEnabled string `json:"metadataEnabled"` + MaxAttempts int `json:"max_attempts"` } type testConfig struct { // this gives more flexibility to define terraform dir when there should be a different set of terraform files // e.g. statsd can have a multiple terraform module sets for difference test scenarios (ecs, eks or ec2) - testDir string - terraformDir string + testDir string + terraformDir string + runMockServer bool // define target matrix field as set(s) // empty map means a testConfig will be created with a test entry for each entry from *_test_matrix.json targets map[string]map[string]struct{} + // maxAttempts limits the number of times a test will be run. + maxAttempts int } const ( @@ -59,6 +64,11 @@ var testTypeToTestConfig = map[string][]testConfig{ testDir: "./test/metrics_number_dimension", targets: map[string]map[string]struct{}{"os": {"al2": {}}}, }, + { + testDir: "./test/emf_concurrent", + targets: map[string]map[string]struct{}{"os": {"al2": {}}}, + maxAttempts: 1, + }, {testDir: "./test/metric_value_benchmark"}, {testDir: "./test/run_as_user"}, {testDir: "./test/collection_interval"}, @@ -111,6 +121,7 @@ var testTypeToTestConfig = map[string][]testConfig{ {testDir: "../../../test/feature/windows"}, {testDir: "../../../test/restart"}, {testDir: "../../../test/acceptance"}, + {testDir: "../../../test/feature/windows/event_logs"}, // assume role test doesn't add much value, and it already being tested with linux //{testDir: "../../../test/assume_role"}, }, @@ -120,6 +131,7 @@ var testTypeToTestConfig = map[string][]testConfig{ {testDir: "../../test/performance/system"}, {testDir: "../../test/performance/statsd"}, {testDir: "../../test/performance/collectd"}, + {testDir: "../../test/performance/trace/xray", runMockServer: true}, }, "ec2_windows_performance": { {testDir: "../../test/performance/windows/logs"}, @@ -140,9 +152,22 @@ var testTypeToTestConfig = map[string][]testConfig{ {testDir: "./test/ecs/ecs_metadata"}, }, "ecs_ec2_daemon": { - {testDir: "./test/metric_value_benchmark"}, - {testDir: "./test/statsd"}, - {testDir: "./test/emf"}, + { + testDir: "./test/metric_value_benchmark", + targets: map[string]map[string]struct{}{"metadataEnabled": {"enabled": {}}}, + }, + { + testDir: "./test/statsd", + targets: map[string]map[string]struct{}{"metadataEnabled": {"enabled": {}}}, + }, + { + testDir: "./test/emf", + targets: map[string]map[string]struct{}{"metadataEnabled": {"disabled": {}}}, + }, + { + testDir: "./test/emf", + targets: map[string]map[string]struct{}{"metadataEnabled": {"enabled": {}}}, + }, }, "eks_daemon": { { @@ -162,6 +187,24 @@ var testTypeToTestConfig = map[string][]testConfig{ targets: map[string]map[string]struct{}{"arc": {"amd64": {}}}, }, {testDir: "./test/fluent", terraformDir: "terraform/eks/daemon/fluent/bit"}, + {testDir: "./test/app_signals", terraformDir: "terraform/eks/daemon/app_signals", + targets: map[string]map[string]struct{}{"arc": {"amd64": {}}}, + }, + { + testDir: "./test/app_signals/high_cardinality_drop", + terraformDir: "terraform/eks/daemon/app_signals", + targets: map[string]map[string]struct{}{"arc": {"amd64": {}}}, + }, + { + testDir: "./test/app_signals/high_cardinality_keep", + terraformDir: "terraform/eks/daemon/app_signals", + targets: map[string]map[string]struct{}{"arc": {"amd64": {}}}, + }, + { + testDir: "./test/app_signals/high_cardinality_replace", + terraformDir: "terraform/eks/daemon/app_signals", + targets: map[string]map[string]struct{}{"arc": {"amd64": {}}}, + }, }, "eks_deployment": { {testDir: "./test/metric_value_benchmark"}, @@ -170,7 +213,7 @@ var testTypeToTestConfig = map[string][]testConfig{ func copyAllEC2LinuxTestForOnpremTesting() { /* Some tests need to be fixed in order to run in both environment, so for now for PoC, run one that works. - testTypeToTestConfig["ec2_linux_onprem"] = testTypeToTestConfig[testTypeKeyEc2Linux] + testTypeToTestConfig["ec2_linux_onprem"] = testTypeToTestConfig[testTypeKeyEc2Linux] */ testTypeToTestConfig["ec2_linux_onprem"] = []testConfig{ { @@ -209,7 +252,12 @@ func genMatrix(testType string, testConfigs []testConfig) []matrixRow { testMatrixComplete := make([]matrixRow, 0, len(testMatrix)) for _, test := range testMatrix { for _, testConfig := range testConfigs { - row := matrixRow{TestDir: testConfig.testDir, TestType: testType, TerraformDir: testConfig.terraformDir} + row := matrixRow{ + TestDir: testConfig.testDir, + TestType: testType, + TerraformDir: testConfig.terraformDir, + MaxAttempts: testConfig.maxAttempts, + } err = mapstructure.Decode(test, &row) if err != nil { log.Panicf("can't decode map test %v to metric line struct with error %v", testConfig, err) @@ -232,6 +280,8 @@ func shouldAddTest(row *matrixRow, targets map[string]map[string]struct{}) bool rowVal = row.Arc } else if key == "os" { rowVal = row.Os + } else if key == "metadataEnabled" { + rowVal = row.MetadataEnabled } if rowVal == "" { diff --git a/install/install_agent.go b/install/install_agent.go index f8c52af01..afced242b 100644 --- a/install/install_agent.go +++ b/install/install_agent.go @@ -11,16 +11,16 @@ import ( ) const ( - retryNumber = 10 - retryTime = 30 * time.Second - debInstall = "deb" - rpmInstall = "rpm" + retryNumber = 10 + retryTime = 30 * time.Second + debInstall = "deb" + rpmInstall = "rpm" ) func main() { installType := os.Args[1] installCommand := "" - + debInstallCommand := "sudo dpkg -i -E ./amazon-cloudwatch-agent.deb" rpmInstallCommand := "sudo rpm -U ./amazon-cloudwatch-agent.rpm" if os.Geteuid() == 0 { diff --git a/mockserver/Dockerfile b/mockserver/Dockerfile new file mode 100644 index 000000000..a26bc7a38 --- /dev/null +++ b/mockserver/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.19 AS build +WORKDIR $GOPATH/main +COPY . . +RUN go env -w GOPROXY=direct +RUN GO111MODULE=on go mod download +RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -o=/bin/main . +EXPOSE 80 +EXPOSE 443 +FROM scratch +ENV AWS_REGION="us-west-2" +COPY --from=build /bin/main /bin/main +COPY certificates certificates +ENTRYPOINT ["/bin/main"] \ No newline at end of file diff --git a/mockserver/Makefile b/mockserver/Makefile new file mode 100644 index 000000000..d54dbbd3a --- /dev/null +++ b/mockserver/Makefile @@ -0,0 +1,26 @@ +# Header above mocked_servers cert in bundle +CERT_HEADER=mockserver + +CONFIG_PATH=openssl.conf +CERT_PATH=certificates/ssl/certificate.crt +KEY_PATH=certificates/private.key +BUNDLE_PATH=certificates/ssl/ca-bundle.crt +.PHONY: clean +clean: + rm -rf $(CERT_PATH) $(BUNDLE_PATH) $(KEY_PATH) + touch $(CERT_PATH) $(BUNDLE_PATH) $(KEY_PATH) +.PHONY: update-certs +update-certs: clean gen-cert update-bundle + +# Expects mocked_servers cert to be the last cert in the bundle +# Cuts until the first instance of "mocked_servers" in the bundle +# and concatenates it with the current cert +.PHONY: update-bundle +update-bundle: + sed /$(CERT_HEADER)/q $(BUNDLE_PATH) | cat - $(CERT_PATH) > $(BUNDLE_PATH).tmp && \ + mv $(BUNDLE_PATH).tmp $(BUNDLE_PATH) + +# Generates the annual cert and private key using the config +.PHONY: gen-cert +gen-cert: + openssl req -config $(CONFIG_PATH) -new -x509 -nodes -days 365 -out $(CERT_PATH) -keyout $(KEY_PATH) \ No newline at end of file diff --git a/mockserver/README.md b/mockserver/README.md new file mode 100644 index 000000000..39d5b6c0c --- /dev/null +++ b/mockserver/README.md @@ -0,0 +1,41 @@ +# The Mock Server + +## Overview + +The Mock Server is a simple server designed for receiving metric and trace data, providing a simulated endpoint for testing purposes. It listens on two separate ports: 8080 and 443. +## Running the server +This server is runs as a docker container to run this server: +1. First build the docker container with +```sh +sudo docker build -t mockserver . +``` +2. Run the container by mapping the ports you would like to use, for example: +```sh +sudo docker run --name mockserver -d -p 8080:8080 -p 443:443 mockserver +``` + +## How it Works +### The Receiver + +The receiver component of the Mock Server operates on port 443. It is responsible for receiving messages and incrementing the transaction count. To simulate real-world conditions, there is a built-in 15ms latency between each received message. The data received can be sent to three possible routes: + +- **Check Receiver Status:** You can check if the receiver is alive by making a request to `/ping`. + +- **Send Data:** Use the `/put-data` route to send data. This route supports two sub-routes: + - `/put-data/trace/v1`: Use this sub-route for sending trace data. + - `/put-data/metrics`: Use this sub-route for sending metrics data. + +> [!Important] +> Currently, both traces and metrics are handled in the same way. + +### The Verifier + +The verifier component can be accessed via a listener on port 8080. It provides information about the transactions, including: + +- **Transactions per Minute:** You can obtain the transactions per minute by making a request to `/tpm`. + +- **Transaction Count:** To check the total transaction count, use the `/check-data` route. + +- **Verifier Status:** Determine if the verification server is alive by sending a request to `/ping`. + + diff --git a/mockserver/certificates/private.key b/mockserver/certificates/private.key new file mode 100644 index 000000000..ffd7280da --- /dev/null +++ b/mockserver/certificates/private.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCs0ou1PZSRfHmL +UmkWI3upcnUSWjQcoUXNSeSxPn5sQtEeObd91D8xBVAOFTdTFgKv5rKT7NcDpflW +//b/ovOqui1uxKDAim1LHs4auhMvRXhi2iLFOCrlAFuDXOFeNe2xvd/zDJr4pQNY +jieLbSGhXQKhNP6E4iguCuSZLhV8aqj56lk5fi9OiiAIz2JqLpv5F3H1YGFde5/l +v5wyuFvozYTehIF8KDpcuvl3NWb376QOghV9Plmb4QoLYN24TvnlOFrgprJy2zRK +n1bZ1jOrIsiB700wHHcFWlkyb0k+o1BoDWFaN1lBDn171oSp5FNXB/86wsJKWX/b +i5v9HHzT12KMpPs3HFvdbPSdmUiRhVwX9XNpbv0sm6Jo3ZX2FrtWj7pWPA97VHrs +nCtErtdb3qnAKFyHGte4gCBDC6RzY/Sx51++LE+nyIukawY3YIyXn8n+e5AKUM6w +7K9sTxLj27kkBw71R1DKwHoEhoFOyE2KbQi6T/2YxOQclvfC9qC8M1sQMw4OdXak +JNn6Elju2PMjYj8fLPOwphNn+7z6p8fjWKvvrGkfgIfEpSK8Y7Em/MtqT+RH17kT +8AZrJnmlLRgyEQIaUZ6PEbnakJZS1zGPBP7unzsY6BZ9jzUXdMgtEEJSgm+c3vPh +CUt/70VW5pBZO++PzSBQkkaTlCZY5wIDAQABAoICAAXItE50ZGQQ5B1pbcAsZHUh +qEwr3epQnUz6+nBIewEpLmWZpiR9VY94IyBh14yioaQVdtHOIM1TOc0/wku7qrsg +MsuGt6AhbjTU9MuyWfYFXQo/dEIlzqEYDJWQxDmM9qhZS0AZeyH9sV1IStaFwPd2 +9tLMOspQDOr54NC7TzAqVzoMLfusTZ7c/c+hXYc6YkUOO1vYsmSYj2CoUTHR/IUt +V/+u0cS//5yovVIyqae3QQsjQstRgwt60gU9UIvmcCUatx/qSAW6NaAggHY+jwRw +pM9wxEH1CBTh74WuSkUcGTozg4RI/GYLABuEdpvhAmS7b6Gnq/Hp/62gmNchfHyV +quJfQuTUMd84AAroN48oaQyqYjCHeoHLxCMV6irrMHWUigP4IJBNr1uxCXNqyJVZ +NN06MU8SLtTscxl+rmsKzVWkoe4VONoXmFoZJUtwJ53xg+RlCWUOl0isI2JMyyaT +TDRoQn0Ta4SMTAuF57RjuTbPcQ57SwgVPV9QXzp1K9ylZoisR90BNyt9V76lMzDK +gjpiTz7yE7jF5MMrpOVPiBHRUkobNBavzlpPvt80E3KsxDYt6+XudanShBUn7oAc +hg27DKu0xcGw9pw5gQcHekDmHaveu0H4QSsLzP/uMah4JzbZATI0BYoj6At+tatF +Mu4hGFqesbStPIyPkmuhAoIBAQDhlaxgtHds2P2IIwuoFRAFVhGzcZdvNdInkR+x +48BsUCTKnr3S+svhjBp6naGtTyINP1KF4wQuMVzhSc6z7JqY5DodvKduzW49q5jm +05JoHGxog3s8Pb4xOZxJQCoHnEQL9gG+hJmQsRCiqun0Lvx0I8+ST4SSQ0oz71zq +fMg3icDCBb9erqyxTfsaAbZMBC8ZcSQNmelE50Ml4KBqcJM0yQqPjHpf1xSqiHlP +ojfpFtsf25q5//T4RufNBu42KApSdv34dGo5GZgoaZD90sFiSkuJAs+yzI1GRZRy +xLIjWS5DX48SL72mPtLpl5uKLyKAZ5War9EVJ1nF8sb2I4s5AoIBAQDEH7iH5RfQ +Dj8TdwmNpu3Cy8Zme957P+MxbB21/ceCIEN727O9rnud2f4hmXh+mKPowHWJpo82 +Oi/bBhk0hDOUmG7waw3cdq/tvlLFXAlx97CfjDX2shmh6/diMYvqEOejMab16OKF +OrEbLAwtNg9rVjLeQibsJa9xj4Bj8kE2n0Eb0HfOlP4OTA8rr6GqJCSWs0pXoiMh +rnjXDWV/cT8NT4cVrNblUXL5wI9XFnKlR7fqbRHmWBX6Vm7pleQBSp1ti43/vsNy +E0QqC8o3IaruZ/uZT/o1z/uuYNIM1HFMlF/SOELiGp2hIkyRZaM3Sd3gBZOqjDej +aSMqWJTQQ2UfAoIBAQDKsBYbKeuoNGvQ11RQ6OPlN4leBzE+rkguXvnwdyfc6kG0 +gN8kY02vUZg1Fc9ADjsVVhEK1YhbDOVcU1nTVkMuHtqM/4YdS53C8ZzHwc/plahe +W8zturhaOF5RfsKE5gZKDPdSPIhSdpXw4sqlKVaR799AogwG80kH0wlUc1vecvps +GofxRddK1DtLCcDHGndLT9pKEkGDNJuju+nG2XGa2wyPIHSQCou6EjeVsrazy2KF +hGbIus9cCTGbiZ+dr2pe4CWgCNGsjm+l1/x749L4QrMN3yXJjHtfaYNRf6RjmGy2 +AnVlrNmlNwuA8UTC60j/wJKuU0z0yc+iyVm1rQgZAoIBAQC47PwPTymz6Sr6jJx6 +b3Ly4Tey/ItchXIQ8NPW/XL4NLnM+O0zJmmy/pCMV+xw2jZ7SbXKVD3nMNhc2hQ8 +G4eTTmQU16ybO2JJdtMO+uiBCL8GwatEcMyQjDGX6gX2b3gqva+jYbLtUtkaON9G +ZhoF6KJQRenzctlJ57h1BUEOYv1+X4QISx5+lqMbWyDBkBDb9DReCyi7IosYo64X +i82bHGjQPEfotHMIIdRGlokFZWl6Ztug6V/Xy1YLdGUn/pYQa605/0LEtnvodXN3 +poxI/c0T04Cm6vRyiSKmLE6kmab8TkZqchQ9klzGICLVCBZonHmPL2Vq9MDOtfWj +plibAoIBAQCAJ1uRE5czjuh9KJqKr1GjQsDBOvFp/WcvqMPw/feictDfUONQF6H5 +jwaCPNtVI3wQEX+qlFk1pZUeclRAWJc5TjCx8NxQPBGyyd9GzW9wqEl7iqCQ9ccN +V3FHAjdCjXVbkKeO1qW6u6BnZbAJYeCmEXciy4oV2Vuhog4UCfLmIWIvZZknqPpZ +88/dRSWkoh7r9VUM539msvQaoVZoQ8YKrAwnr/GPc2+lwKvX2nYlLFBgNY+yHoJz +liaBRkmTNbr9D7l2dbBc+HBX+WAG7wDEbcHT/25VTDe9qVwaOJYaA/8YwHlKkgk6 +MS4NuDAuF8b/rhwtUJW4UY19lq3fstdV +-----END PRIVATE KEY----- diff --git a/mockserver/certificates/ssl/ca-bundle.crt b/mockserver/certificates/ssl/ca-bundle.crt new file mode 100644 index 000000000..f476b3333 --- /dev/null +++ b/mockserver/certificates/ssl/ca-bundle.crt @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGOTCCBCGgAwIBAgIUTbGrEtezpQ4lTSJq4Vq4/Wf7wokwDQYJKoZIhvcNAQEL +BQAwgaAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH +DAdTZWF0dGxlMRgwFgYDVQQKDA9FeGFtcGxlIENvbXBhbnkxFjAUBgNVBAsMDUlU +IERlcGFydG1lbnQxIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20x +FDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIzMDgxNjIzMDY0M1oXDTI0MDgxNTIz +MDY0M1owgaAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYD +VQQHDAdTZWF0dGxlMRgwFgYDVQQKDA9FeGFtcGxlIENvbXBhbnkxFjAUBgNVBAsM +DUlUIERlcGFydG1lbnQxIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5j +b20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEArNKLtT2UkXx5i1JpFiN7qXJ1Elo0HKFFzUnksT5+bELRHjm3fdQ/ +MQVQDhU3UxYCr+ayk+zXA6X5Vv/2/6LzqrotbsSgwIptSx7OGroTL0V4YtoixTgq +5QBbg1zhXjXtsb3f8wya+KUDWI4ni20hoV0CoTT+hOIoLgrkmS4VfGqo+epZOX4v +ToogCM9iai6b+Rdx9WBhXXuf5b+cMrhb6M2E3oSBfCg6XLr5dzVm9++kDoIVfT5Z +m+EKC2DduE755Tha4Kaycts0Sp9W2dYzqyLIge9NMBx3BVpZMm9JPqNQaA1hWjdZ +QQ59e9aEqeRTVwf/OsLCSll/24ub/Rx809dijKT7Nxxb3Wz0nZlIkYVcF/VzaW79 +LJuiaN2V9ha7Vo+6VjwPe1R67JwrRK7XW96pwChchxrXuIAgQwukc2P0sedfvixP +p8iLpGsGN2CMl5/J/nuQClDOsOyvbE8S49u5JAcO9UdQysB6BIaBTshNim0Iuk/9 +mMTkHJb3wvagvDNbEDMODnV2pCTZ+hJY7tjzI2I/HyzzsKYTZ/u8+qfH41ir76xp +H4CHxKUivGOxJvzLak/kR9e5E/AGayZ5pS0YMhECGlGejxG52pCWUtcxjwT+7p87 +GOgWfY81F3TILRBCUoJvnN7z4QlLf+9FVuaQWTvvj80gUJJGk5QmWOcCAwEAAaNp +MGcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwLgYDVR0RBCcwJYIJbG9jYWxob3N0 +ggkxMjcuMC4wLjGCDW1vY2tlZC1zZXJ2ZXIwHQYDVR0OBBYEFK0KPMa2jkCkjtQI +35yprUPDT+XcMA0GCSqGSIb3DQEBCwUAA4ICAQBCqvGkeBmklslKPUQtfLD6h2uJ +7l/CsVn9Vw8c6SMIwL83IjUHyFxSmQauplYOKH6wN5++EUYazPYXfQRrbNXa9dMw +U8W7B8neo2/EVF3GC+9lGEhhiGgBIizwID9CG1k6vl40TPmD7o4qNP8QjQeAQbDo +bRta2oCb+40IOVHJQrr3H3KzP+6sRd4H3WbyWAf/Wtb76GSC4M52xmcL99SsaK0u +q1jErAGekfc6l37KHUypimaA065KgnJz7Crg1t6JUNHjpROXjdkrf1+TZZ/z6C+p +KPm2zvITUvr05dLlS05J2ghOd3B/+DDg0b+GgOuqq3bxcTgNwVPogpKO/mJGIxY/ +HDPkLcPulnaHm+NB/IIK2owfnXRC8Qujq2bthyHdP4cGGn4Q82cA8FRO04L3oGrC +6BcroCZqWkllaVNJtoFig0UqXQv6aNqT0kgO3B12Km7wd50ye2+awyVP62J0Bi9W +qBNMWT5LVAgt6sY4xBwxjKd3fizoQEcxRrD2Hk+mw4DCU/6ni5PSf8l0x31Mk5rk +UnuHFMnxTBBfUonIhRKg59b5qVdgs6Il8N+aRV2ZP3WUoOEZCFiNzqbJrcjMMlur +8Zj/fYYyywA7Ck4Srv8NPVskc4wic1aXBlDvbz4UjC+T1ElIfgM36HfV+d/weMs/ +rKvn+B6q4wmPGcC4Rw== +-----END CERTIFICATE----- diff --git a/mockserver/certificates/ssl/certificate.crt b/mockserver/certificates/ssl/certificate.crt new file mode 100644 index 000000000..f476b3333 --- /dev/null +++ b/mockserver/certificates/ssl/certificate.crt @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGOTCCBCGgAwIBAgIUTbGrEtezpQ4lTSJq4Vq4/Wf7wokwDQYJKoZIhvcNAQEL +BQAwgaAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH +DAdTZWF0dGxlMRgwFgYDVQQKDA9FeGFtcGxlIENvbXBhbnkxFjAUBgNVBAsMDUlU +IERlcGFydG1lbnQxIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20x +FDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIzMDgxNjIzMDY0M1oXDTI0MDgxNTIz +MDY0M1owgaAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYD +VQQHDAdTZWF0dGxlMRgwFgYDVQQKDA9FeGFtcGxlIENvbXBhbnkxFjAUBgNVBAsM +DUlUIERlcGFydG1lbnQxIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5j +b20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEArNKLtT2UkXx5i1JpFiN7qXJ1Elo0HKFFzUnksT5+bELRHjm3fdQ/ +MQVQDhU3UxYCr+ayk+zXA6X5Vv/2/6LzqrotbsSgwIptSx7OGroTL0V4YtoixTgq +5QBbg1zhXjXtsb3f8wya+KUDWI4ni20hoV0CoTT+hOIoLgrkmS4VfGqo+epZOX4v +ToogCM9iai6b+Rdx9WBhXXuf5b+cMrhb6M2E3oSBfCg6XLr5dzVm9++kDoIVfT5Z +m+EKC2DduE755Tha4Kaycts0Sp9W2dYzqyLIge9NMBx3BVpZMm9JPqNQaA1hWjdZ +QQ59e9aEqeRTVwf/OsLCSll/24ub/Rx809dijKT7Nxxb3Wz0nZlIkYVcF/VzaW79 +LJuiaN2V9ha7Vo+6VjwPe1R67JwrRK7XW96pwChchxrXuIAgQwukc2P0sedfvixP +p8iLpGsGN2CMl5/J/nuQClDOsOyvbE8S49u5JAcO9UdQysB6BIaBTshNim0Iuk/9 +mMTkHJb3wvagvDNbEDMODnV2pCTZ+hJY7tjzI2I/HyzzsKYTZ/u8+qfH41ir76xp +H4CHxKUivGOxJvzLak/kR9e5E/AGayZ5pS0YMhECGlGejxG52pCWUtcxjwT+7p87 +GOgWfY81F3TILRBCUoJvnN7z4QlLf+9FVuaQWTvvj80gUJJGk5QmWOcCAwEAAaNp +MGcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwLgYDVR0RBCcwJYIJbG9jYWxob3N0 +ggkxMjcuMC4wLjGCDW1vY2tlZC1zZXJ2ZXIwHQYDVR0OBBYEFK0KPMa2jkCkjtQI +35yprUPDT+XcMA0GCSqGSIb3DQEBCwUAA4ICAQBCqvGkeBmklslKPUQtfLD6h2uJ +7l/CsVn9Vw8c6SMIwL83IjUHyFxSmQauplYOKH6wN5++EUYazPYXfQRrbNXa9dMw +U8W7B8neo2/EVF3GC+9lGEhhiGgBIizwID9CG1k6vl40TPmD7o4qNP8QjQeAQbDo +bRta2oCb+40IOVHJQrr3H3KzP+6sRd4H3WbyWAf/Wtb76GSC4M52xmcL99SsaK0u +q1jErAGekfc6l37KHUypimaA065KgnJz7Crg1t6JUNHjpROXjdkrf1+TZZ/z6C+p +KPm2zvITUvr05dLlS05J2ghOd3B/+DDg0b+GgOuqq3bxcTgNwVPogpKO/mJGIxY/ +HDPkLcPulnaHm+NB/IIK2owfnXRC8Qujq2bthyHdP4cGGn4Q82cA8FRO04L3oGrC +6BcroCZqWkllaVNJtoFig0UqXQv6aNqT0kgO3B12Km7wd50ye2+awyVP62J0Bi9W +qBNMWT5LVAgt6sY4xBwxjKd3fizoQEcxRrD2Hk+mw4DCU/6ni5PSf8l0x31Mk5rk +UnuHFMnxTBBfUonIhRKg59b5qVdgs6Il8N+aRV2ZP3WUoOEZCFiNzqbJrcjMMlur +8Zj/fYYyywA7Ck4Srv8NPVskc4wic1aXBlDvbz4UjC+T1ElIfgM36HfV+d/weMs/ +rKvn+B6q4wmPGcC4Rw== +-----END CERTIFICATE----- diff --git a/mockserver/go.mod b/mockserver/go.mod new file mode 100644 index 000000000..9cd6b1a7a --- /dev/null +++ b/mockserver/go.mod @@ -0,0 +1,5 @@ +module mockserver + +go 1.20 + +require github.com/gorilla/mux v1.8.0 \ No newline at end of file diff --git a/mockserver/go.sum b/mockserver/go.sum new file mode 100644 index 000000000..2a61a10c4 --- /dev/null +++ b/mockserver/go.sum @@ -0,0 +1 @@ +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/mockserver/main.go b/mockserver/main.go new file mode 100644 index 000000000..72dcfe0a1 --- /dev/null +++ b/mockserver/main.go @@ -0,0 +1,140 @@ +// Copyright 2023 Amazon.com, Inc. or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "encoding/json" + "io" + "log" + "net/http" + "path" + "sync" + "sync/atomic" + "time" + + "github.com/gorilla/mux" +) + +const ( + HealthCheckMessage = "healthcheck" + SuccessMessage = "success" +) + +var ( + CertFilePath = path.Join("certificates", "ssl", "certificate.crt") + KeyFilePath = path.Join("certificates", "private.key") +) + +type transactionHttpServer struct { + transactions uint32 + startTime time.Time +} + +type TransactionPayload struct { + TransactionsPerMinute float64 `json:"GetNumberOfTransactionsPerMinute"` +} + +func healthCheck(w http.ResponseWriter, _ *http.Request) { + if _, err := io.WriteString(w, HealthCheckMessage); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Printf("Unable to write response: %v", err) + return + } + w.WriteHeader(http.StatusOK) +} + +func (ts *transactionHttpServer) checkTransactionCount(w http.ResponseWriter, _ *http.Request) { + var message string + var t = atomic.LoadUint32(&ts.transactions) + if t > 0 { + message = SuccessMessage + } + log.Printf("\033[31m Time: %d | checkTransactionCount msg: %s | %d\033[0m \n", time.Now().Unix(), message, t) + if _, err := io.WriteString(w, message); err != nil { + w.WriteHeader(http.StatusInternalServerError) + io.WriteString(w, err.Error()) + log.Printf("Unable to write response: %v", err) + return + } + w.WriteHeader(http.StatusOK) +} + +func (ts *transactionHttpServer) recordTransaction(w http.ResponseWriter, _ *http.Request) { + atomic.AddUint32(&ts.transactions, 1) + + // Built-in latency + log.Printf("\033[31m Time: %s | transaction received \033[0m \n", time.Now().String()) + time.Sleep(15 * time.Millisecond) + w.WriteHeader(http.StatusOK) +} + +// Retrieve number of transactions per minute +func (ts *transactionHttpServer) GetNumberOfTransactionsPerMinute(w http.ResponseWriter, _ *http.Request) { + // Calculate duration in minutes + duration := time.Now().Sub(ts.startTime) + transactions := float64(atomic.LoadUint32(&ts.transactions)) + tpm := transactions / duration.Minutes() + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(TransactionPayload{tpm}); err != nil { + w.WriteHeader(http.StatusInternalServerError) + io.WriteString(w, err.Error()) + log.Printf("Unable to write response: %v", err) + } +} + +// Starts an HTTP server that receives request from validator only to verify the data ingestion +func StartHttpServer() { + var wg sync.WaitGroup + log.Println("\033[31m Starting Server \033[0m") + store := transactionHttpServer{startTime: time.Now()} + //2 servers one for receiving the data , one for verify data + dataApp := mux.NewRouter() + dataReceiverServer := &http.Server{Addr: ":443", Handler: dataApp} + verificationRequestServer := http.NewServeMux() + appServer := &http.Server{Addr: ":8080", Handler: verificationRequestServer} + wg.Add(2) + go func(ts *transactionHttpServer) { + defer wg.Done() + dataApp.HandleFunc("/ping", healthCheck) + dataApp.PathPrefix("/put-data").HandlerFunc(ts.recordTransaction) + dataApp.HandleFunc("/trace/v1", ts.recordTransaction) + dataApp.HandleFunc("/metric/v1", ts.recordTransaction) + if err := dataReceiverServer.ListenAndServeTLS(CertFilePath, KeyFilePath); err != nil { + log.Printf("HTTPS server error: %v", err) + err = dataReceiverServer.Shutdown(context.TODO()) + log.Fatalf("Shutdown server error: %v", err) + } + }(&store) + + go func(ts *transactionHttpServer) { + defer wg.Done() + verificationRequestServer.HandleFunc("/ping", healthCheck) + verificationRequestServer.HandleFunc("/check-data", ts.checkTransactionCount) + verificationRequestServer.HandleFunc("/tpm", ts.GetNumberOfTransactionsPerMinute) + if err := appServer.ListenAndServe(); err != nil { + log.Printf("Verification server error: %v", err) + err := appServer.Shutdown(context.TODO()) + log.Fatalf("Shutdown server error: %v", err) + } + }(&store) + wg.Wait() + log.Println("\033[32m Stopping Server \033[0m") +} + +func main() { + StartHttpServer() +} diff --git a/mockserver/openssl.conf b/mockserver/openssl.conf new file mode 100644 index 000000000..b764c53fc --- /dev/null +++ b/mockserver/openssl.conf @@ -0,0 +1,29 @@ +[CA_default] +copy_extensions = copy + +[req] +default_bits = 4096 +prompt = no +default_md = sha256 +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +[req_distinguished_name] +C = US +ST = Washington +L = Seattle +O = Example Company +OU = IT Department +emailAddress = example@example.com +CN = example.com + +[v3_ca] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alternate_names + +[alternate_names] +DNS.1 = localhost +DNS.2 = 127.0.0.1 +DNS.3 = mockserver +IP.1 = 127.0.0.1 \ No newline at end of file diff --git a/terraform/ec2/win/main.tf b/terraform/ec2/win/main.tf index d7a6f2bda..3de3828c7 100644 --- a/terraform/ec2/win/main.tf +++ b/terraform/ec2/win/main.tf @@ -220,4 +220,4 @@ data "aws_ami" "latest" { name = "name" values = [var.ami] } -} \ No newline at end of file +} diff --git a/terraform/ecs_ec2/daemon/main.tf b/terraform/ecs_ec2/daemon/main.tf index ed78f5da0..11a888834 100644 --- a/terraform/ecs_ec2/daemon/main.tf +++ b/terraform/ecs_ec2/daemon/main.tf @@ -186,11 +186,12 @@ resource "aws_ecs_task_definition" "cwagent_task_definition" { } resource "aws_ecs_service" "cwagent_service" { - name = "cwagent-service-${module.common.testing_id}" - cluster = aws_ecs_cluster.cluster.id - task_definition = aws_ecs_task_definition.cwagent_task_definition.arn - launch_type = "EC2" - scheduling_strategy = "DAEMON" + name = "cwagent-service-${module.common.testing_id}" + cluster = aws_ecs_cluster.cluster.id + task_definition = aws_ecs_task_definition.cwagent_task_definition.arn + launch_type = "EC2" + scheduling_strategy = "DAEMON" + enable_execute_command = true depends_on = [aws_ecs_task_definition.cwagent_task_definition] } @@ -220,11 +221,12 @@ resource "aws_ecs_task_definition" "extra_apps_task_definition" { } resource "aws_ecs_service" "extra_apps_service" { - name = "extra-apps-service-${module.common.testing_id}" - cluster = aws_ecs_cluster.cluster.id - task_definition = aws_ecs_task_definition.extra_apps_task_definition.arn - desired_count = 1 - launch_type = "FARGATE" + name = "extra-apps-service-${module.common.testing_id}" + cluster = aws_ecs_cluster.cluster.id + task_definition = aws_ecs_task_definition.extra_apps_task_definition.arn + desired_count = 1 + launch_type = "FARGATE" + enable_execute_command = true network_configuration { security_groups = [module.basic_components.security_group] @@ -243,5 +245,20 @@ resource "null_resource" "validator" { go test ${var.test_dir} -timeout 0 -computeType=ECS -ecsLaunchType=EC2 -ecsDeploymentStrategy=DAEMON -cwagentConfigSsmParamName=${local.cwagent_config_ssm_param_name} -clusterArn=${aws_ecs_cluster.cluster.arn} -cwagentECSServiceName=${aws_ecs_service.cwagent_service.name} -v EOT } - depends_on = [aws_ecs_service.cwagent_service, aws_ecs_service.extra_apps_service] + depends_on = [aws_ecs_service.cwagent_service, aws_ecs_service.extra_apps_service, null_resource.disable_metadata] } + +resource "null_resource" "disable_metadata" { + + provisioner "local-exec" { + command = <<-EOT + echo Sleep for 30 seconds to allow instance to be attached to the cluster + sleep 30 + echo Setting metadata option for ECS EC2 instance to ${var.metadataEnabled} + INSTANCEID=`aws ec2 describe-instances --filters Name=tag:ClusterName,Values=${aws_ecs_cluster.cluster.name} --query "Reservations[*].Instances[*].InstanceId" --output text` + echo Instance ID for ECS instance is $INSTANCEID + aws ec2 modify-instance-metadata-options --instance-id $INSTANCEID --http-endpoint ${var.metadataEnabled} + EOT + } + depends_on = [aws_ecs_service.cwagent_service, aws_ecs_service.extra_apps_service, aws_autoscaling_group.cluster] +} \ No newline at end of file diff --git a/terraform/ecs_ec2/daemon/variables.tf b/terraform/ecs_ec2/daemon/variables.tf index dcfc3c82e..1ce33ad61 100644 --- a/terraform/ecs_ec2/daemon/variables.tf +++ b/terraform/ecs_ec2/daemon/variables.tf @@ -26,4 +26,9 @@ variable "cwagent_image_repo" { variable "cwagent_image_tag" { type = string default = "latest" +} + +variable "metadataEnabled" { + type = string + default = "enabled" } \ No newline at end of file diff --git a/terraform/ecs_fargate/linux/main.tf b/terraform/ecs_fargate/linux/main.tf index 442706d9f..dc830bcfb 100644 --- a/terraform/ecs_fargate/linux/main.tf +++ b/terraform/ecs_fargate/linux/main.tf @@ -85,11 +85,12 @@ resource "aws_ecs_task_definition" "cwagent_task_definition" { } resource "aws_ecs_service" "cwagent_service" { - name = "cwagent-service-${module.common.testing_id}" - cluster = aws_ecs_cluster.cluster.id - task_definition = aws_ecs_task_definition.cwagent_task_definition.arn - desired_count = 1 - launch_type = "FARGATE" + name = "cwagent-service-${module.common.testing_id}" + cluster = aws_ecs_cluster.cluster.id + task_definition = aws_ecs_task_definition.cwagent_task_definition.arn + desired_count = 1 + launch_type = "FARGATE" + enable_execute_command = true network_configuration { security_groups = [module.basic_components.security_group] @@ -125,11 +126,12 @@ resource "aws_ecs_task_definition" "extra_apps_task_definition" { } resource "aws_ecs_service" "extra_apps_service" { - name = "extra-apps-service-${module.common.testing_id}" - cluster = aws_ecs_cluster.cluster.id - task_definition = aws_ecs_task_definition.extra_apps_task_definition.arn - desired_count = 1 - launch_type = "FARGATE" + name = "extra-apps-service-${module.common.testing_id}" + cluster = aws_ecs_cluster.cluster.id + task_definition = aws_ecs_task_definition.extra_apps_task_definition.arn + desired_count = 1 + launch_type = "FARGATE" + enable_execute_command = true network_configuration { security_groups = [module.basic_components.security_group] diff --git a/terraform/eks/daemon/app_signals/main.tf b/terraform/eks/daemon/app_signals/main.tf new file mode 100644 index 000000000..56aba8a37 --- /dev/null +++ b/terraform/eks/daemon/app_signals/main.tf @@ -0,0 +1,514 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +module "common" { + source = "../../../common" + cwagent_image_repo = var.cwagent_image_repo + cwagent_image_tag = var.cwagent_image_tag +} + +module "basic_components" { + source = "../../../basic_components" + + region = var.region +} + +data "aws_eks_cluster_auth" "this" { + name = aws_eks_cluster.this.name +} + +resource "aws_eks_cluster" "this" { + name = "cwagent-eks-integ-${module.common.testing_id}" + role_arn = module.basic_components.role_arn + version = var.k8s_version + enabled_cluster_log_types = [ + "api", + "audit", + "authenticator", + "controllerManager", + "scheduler" + ] + vpc_config { + subnet_ids = module.basic_components.public_subnet_ids + security_group_ids = [module.basic_components.security_group] + } +} + +# EKS Node Groups +resource "aws_eks_node_group" "this" { + cluster_name = aws_eks_cluster.this.name + node_group_name = "cwagent-eks-integ-node" + node_role_arn = aws_iam_role.node_role.arn + subnet_ids = module.basic_components.public_subnet_ids + + scaling_config { + desired_size = 1 + max_size = 1 + min_size = 1 + } + + ami_type = "AL2_x86_64" + capacity_type = "ON_DEMAND" + disk_size = 20 + instance_types = ["t3.medium"] + + depends_on = [ + aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly, + aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy, + aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy, + aws_iam_role_policy_attachment.node_CloudWatchAgentServerPolicy, + aws_iam_role_policy_attachment.node_AWSXRayDaemonWriteAccess + ] +} + +# EKS Node IAM Role +resource "aws_iam_role" "node_role" { + name = "cwagent-eks-Worker-Role-${module.common.testing_id}" + + assume_role_policy = < traceid_generator.go && chmod +x traceid_generator.go; export START_TIME=$(date +%s%N); export TRACE_ID=$(go run ./traceid_generator.go); do echo '${data.template_file.server_consumer.rendered}' | sed -e \"s/START_TIME/$START_TIME/\" > server_consumer.json; curl -H 'Content-Type: application/json' -d @server_consumer.json -i http://127.0.0.1:4316/v1/metrics --verbose; echo '${data.template_file.client_producer.rendered}' | sed -e \"s/START_TIME/$START_TIME/\" > client_producer.json; curl -H 'Content-Type: application/json' -d @client_producer.json -i http://127.0.0.1:4316/v1/metrics --verbose; echo '${data.template_file.traces.rendered}' | sed -e \"s/START_TIME/$START_TIME/\" | sed -e \"s/TRACE_ID/$TRACE_ID/\" > traces.json; curl -H 'Content-Type: application/json' -d @traces.json -i http://127.0.0.1:4316/v1/traces --verbose; sleep 1; done" + ] + env { + name = "HOST_IP" + value_from { + field_ref { + field_path = "status.hostIP" + } + } + } + env { + name = "HOST_NAME" + value_from { + field_ref { + field_path = "spec.nodeName" + } + } + } + env { + name = "K8S_NAMESPACE" + value_from { + field_ref { + field_path = "metadata.namespace" + } + } + } + volume_mount { + mount_path = "/etc/cwagentconfig" + name = "cwagentconfig" + } + } + service_account_name = "cloudwatch-agent" + termination_grace_period_seconds = 60 + } + } + } +} + +########################################## +# Template Files +########################################## +locals { + cwagent_config = "../../../../${var.test_dir}/resources/config.json" + server_consumer = "../../../../${var.test_dir}/resources/metrics/server_consumer.json" + client_producer = "../../../../${var.test_dir}/resources/metrics/client_producer.json" + traces = "../../../../${var.test_dir}/resources/traces/traces.json" + traceid_generator = "../../../../${var.test_dir}/resources/traceid_generator.go" +} + +data "template_file" "cwagent_config" { + template = file(local.cwagent_config) + vars = { + } +} + +resource "kubernetes_config_map" "cwagentconfig" { + depends_on = [ + kubernetes_namespace.namespace, + kubernetes_service_account.cwagentservice + ] + metadata { + name = "cwagentconfig" + namespace = "amazon-cloudwatch" + } + data = { + "cwagentconfig.json" : data.template_file.cwagent_config.rendered + } +} + +data "template_file" "server_consumer" { + template = file(local.server_consumer) + vars = { + } +} + +data "template_file" "client_producer" { + template = file(local.client_producer) + vars = { + } +} + +data "template_file" "traces" { + template = file(local.traces) + vars = { + } +} + +data "template_file" "traceid_generator" { + template = file(local.traceid_generator) + vars = { + } +} + +resource "kubernetes_service_account" "cwagentservice" { + depends_on = [kubernetes_namespace.namespace] + metadata { + name = "cloudwatch-agent" + namespace = "amazon-cloudwatch" + } +} + +resource "kubernetes_cluster_role" "clusterrole" { + depends_on = [kubernetes_namespace.namespace] + metadata { + name = "cloudwatch-agent-role" + } + rule { + verbs = ["list", "watch"] + resources = ["pods", "nodes", "endpoints", "services"] + api_groups = [""] + } + rule { + verbs = ["list", "watch"] + resources = ["replicasets"] + api_groups = ["apps"] + } + rule { + verbs = ["list", "watch"] + resources = ["jobs"] + api_groups = ["batch"] + } + rule { + verbs = ["get"] + resources = ["nodes/proxy", "configmaps"] + api_groups = [""] + } + rule { + verbs = ["create"] + resources = ["nodes/stats", "configmaps", "events"] + api_groups = [""] + } + rule { + verbs = ["get", "update"] + resource_names = ["cwagent-clusterleader"] + resources = ["configmaps"] + api_groups = [""] + } +} + +resource "kubernetes_cluster_role_binding" "rolebinding" { + depends_on = [kubernetes_namespace.namespace] + metadata { + name = "cloudwatch-agent-role-binding" + } + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "ClusterRole" + name = "cloudwatch-agent-role" + } + subject { + kind = "ServiceAccount" + name = "cloudwatch-agent" + namespace = "amazon-cloudwatch" + } +} + +resource "null_resource" "validator" { + depends_on = [ + aws_eks_node_group.this, + kubernetes_daemonset.service, + kubernetes_cluster_role_binding.rolebinding, + kubernetes_service_account.cwagentservice, + ] + provisioner "local-exec" { + command = <<-EOT + echo "Validating EKS metrics/traces for AppSignals" + cd ../../../.. + go test ${var.test_dir} -timeout 1h -eksClusterName=${aws_eks_cluster.this.name} -computeType=EKS -v -eksDeploymentStrategy=DAEMON + EOT + } +} diff --git a/terraform/eks/daemon/app_signals/providers.tf b/terraform/eks/daemon/app_signals/providers.tf new file mode 100644 index 000000000..9bd2885f5 --- /dev/null +++ b/terraform/eks/daemon/app_signals/providers.tf @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +provider "aws" { + region = var.region +} + +provider "kubernetes" { + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + args = ["eks", "get-token", "--cluster-name", aws_eks_cluster.this.name] + } + host = aws_eks_cluster.this.endpoint + cluster_ca_certificate = base64decode(aws_eks_cluster.this.certificate_authority.0.data) + token = data.aws_eks_cluster_auth.this.token +} \ No newline at end of file diff --git a/terraform/eks/daemon/app_signals/variables.tf b/terraform/eks/daemon/app_signals/variables.tf new file mode 100644 index 000000000..a0434630e --- /dev/null +++ b/terraform/eks/daemon/app_signals/variables.tf @@ -0,0 +1,37 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +variable "region" { + type = string + default = "us-west-2" +} + +variable "test_dir" { + type = string + default = "./test/app_signals" +} + +variable "cwagent_image_repo" { + type = string + default = "public.ecr.aws/cloudwatch-agent/cloudwatch-agent" +} + +variable "cwagent_image_tag" { + type = string + default = "latest" +} + +variable "k8s_version" { + type = string + default = "1.24" +} + +variable "ami_type" { + type = string + default = "AL2_x86_64" +} + +variable "instance_type" { + type = string + default = "t3a.medium" +} \ No newline at end of file diff --git a/terraform/eks/daemon/default_resources/default_amazon_cloudwatch_agent.json b/terraform/eks/daemon/default_resources/default_amazon_cloudwatch_agent.json index 01d141110..2c6ee84b2 100644 --- a/terraform/eks/daemon/default_resources/default_amazon_cloudwatch_agent.json +++ b/terraform/eks/daemon/default_resources/default_amazon_cloudwatch_agent.json @@ -2,7 +2,8 @@ "logs": { "metrics_collected": { "kubernetes": { - "metrics_collection_interval": 30 + "metrics_collection_interval": 30, + "enhanced_container_insights": true } }, "force_flush_interval": 5 diff --git a/terraform/eks/daemon/emf/main.tf b/terraform/eks/daemon/emf/main.tf index 28b8247de..bf51a46db 100644 --- a/terraform/eks/daemon/emf/main.tf +++ b/terraform/eks/daemon/emf/main.tf @@ -65,19 +65,19 @@ resource "aws_iam_role" "node_role" { name = "cwagent-eks-Worker-Role-${module.common.testing_id}" assume_role_policy = <>>> Starting AppSignalsTestSuite") +} + +func (suite *AppSignalsTestSuite) TearDownSuite() { + suite.Result.Print() + fmt.Println(">>>> Finished AppSignalsTestSuite") +} + +func init() { + environment.RegisterEnvironmentMetaDataFlags() +} + +var ( + eksTestRunners []*test_runner.EKSTestRunner +) + +func getEksTestRunners(env *environment.MetaData) []*test_runner.EKSTestRunner { + if eksTestRunners == nil { + factory := dimension.GetDimensionFactory(*env) + + eksTestRunners = []*test_runner.EKSTestRunner{ + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsServerConsumerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsClientProducerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsTracesRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsTracesTestName, env.EKSClusterName}, + Env: *env, + }, + } + } + return eksTestRunners +} + +func (suite *AppSignalsTestSuite) TestAllInSuite() { + env := environment.GetEnvironmentMetaData() + switch env.ComputeType { + case computetype.EKS: + log.Println("Environment compute type is EKS") + for _, testRunner := range getEksTestRunners(env) { + testRunner.Run(suite, env) + } + default: + return + } + + suite.Assert().Equal(status.SUCCESSFUL, suite.Result.GetStatus(), "AppSignals Test Suite Failed") +} + +func (suite *AppSignalsTestSuite) AddToSuiteResult(r status.TestGroupResult) { + suite.Result.TestGroupResults = append(suite.Result.TestGroupResults, r) +} + +func TestAppSignalsSuite(t *testing.T) { + suite.Run(t, new(AppSignalsTestSuite)) +} diff --git a/test/app_signals/high_cardinality_drop/app_signals_test.go b/test/app_signals/high_cardinality_drop/app_signals_test.go new file mode 100644 index 000000000..01e9976b8 --- /dev/null +++ b/test/app_signals/high_cardinality_drop/app_signals_test.go @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "fmt" + "log" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" +) + +const ( + AppSignalsServerConsumerTestName = "AppSignals-Server-Consumer" + AppSignalsClientProducerTestName = "AppSignals-Client-Producer" + AppSignalsTracesTestName = "AppSignals-Traces" +) + +type AppSignalsTestSuite struct { + suite.Suite + test_runner.TestSuite +} + +func (suite *AppSignalsTestSuite) SetupSuite() { + fmt.Println(">>>> Starting AppSignalsTestSuite") +} + +func (suite *AppSignalsTestSuite) TearDownSuite() { + suite.Result.Print() + fmt.Println(">>>> Finished AppSignalsTestSuite") +} + +func init() { + environment.RegisterEnvironmentMetaDataFlags() +} + +var ( + eksTestRunners []*test_runner.EKSTestRunner +) + +func getEksTestRunners(env *environment.MetaData) []*test_runner.EKSTestRunner { + if eksTestRunners == nil { + factory := dimension.GetDimensionFactory(*env) + + eksTestRunners = []*test_runner.EKSTestRunner{ + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsServerConsumerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsClientProducerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsTracesRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsTracesTestName, env.EKSClusterName}, + Env: *env, + }, + } + } + return eksTestRunners +} + +func (suite *AppSignalsTestSuite) TestAllInSuite() { + env := environment.GetEnvironmentMetaData() + switch env.ComputeType { + case computetype.EKS: + log.Println("Environment compute type is EKS") + for _, testRunner := range getEksTestRunners(env) { + testRunner.Run(suite, env) + } + default: + return + } + + suite.Assert().Equal(status.SUCCESSFUL, suite.Result.GetStatus(), "AppSignals Test Suite Failed") +} + +func (suite *AppSignalsTestSuite) AddToSuiteResult(r status.TestGroupResult) { + suite.Result.TestGroupResults = append(suite.Result.TestGroupResults, r) +} + +func TestAppSignalsSuite(t *testing.T) { + suite.Run(t, new(AppSignalsTestSuite)) +} diff --git a/test/app_signals/high_cardinality_drop/high_cardinality_drop_metrics_test.go b/test/app_signals/high_cardinality_drop/high_cardinality_drop_metrics_test.go new file mode 100644 index 000000000..d2f43d415 --- /dev/null +++ b/test/app_signals/high_cardinality_drop/high_cardinality_drop_metrics_test.go @@ -0,0 +1,69 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "time" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" +) + +const testRetryCount = 6 +const namespace = "AppSignals" + +type AppSignalsMetricsRunner struct { + test_runner.BaseTestRunner + testName string + dimensionKey string +} + +func (t *AppSignalsMetricsRunner) Validate() status.TestGroupResult { + metricsToFetch := t.GetMeasuredMetrics() + testResults := make([]status.TestResult, len(metricsToFetch)) + instructions := GetInstructionsFromTestName(t.testName) + + for i, metricName := range metricsToFetch { + var testResult status.TestResult + for j := 0; j < testRetryCount; j++ { + testResult = metric.ValidateAppSignalsMetric(t.DimensionFactory, namespace, metricName, instructions) + if testResult.Status == status.SUCCESSFUL { + break + } + time.Sleep(30 * time.Second) + } + testResults[i] = testResult + } + + return status.TestGroupResult{ + Name: t.GetTestName(), + TestResults: testResults, + } +} + +func (t *AppSignalsMetricsRunner) GetTestName() string { + return t.testName +} + +func (t *AppSignalsMetricsRunner) GetAgentRunDuration() time.Duration { + return 3 * time.Minute +} + +func (t *AppSignalsMetricsRunner) GetMeasuredMetrics() []string { + return metric.AppSignalsMetricNames +} + +func (e *AppSignalsMetricsRunner) GetAgentConfigFileName() string { + return "" +} + +func GetInstructionsFromTestName(testName string) []dimension.Instruction { + return metric.ServerConsumerInstructions +} + +var _ test_runner.ITestRunner = (*AppSignalsMetricsRunner)(nil) diff --git a/test/app_signals/high_cardinality_drop/resources/config.json b/test/app_signals/high_cardinality_drop/resources/config.json new file mode 100644 index 000000000..c05d464ec --- /dev/null +++ b/test/app_signals/high_cardinality_drop/resources/config.json @@ -0,0 +1,31 @@ +{ + "agent": { + "debug": true + }, + "logs": { + "metrics_collected": { + "app_signals": { + "enabled": true, + "rules": [ + { + "selectors": [ + { + "dimensions": "RemoteTarget", + "match": "remote-target" + } + ], + "action": "drop", + "rule_name": "drop" + } + ] + } + } + }, + "traces": { + "traces_collected": { + "app_signals": { + "enabled": true + } + } + } + } \ No newline at end of file diff --git a/test/app_signals/high_cardinality_drop/resources/metrics/client_producer.json b/test/app_signals/high_cardinality_drop/resources/metrics/client_producer.json new file mode 100644 index 000000000..8a39c60d6 --- /dev/null +++ b/test/app_signals/high_cardinality_drop/resources/metrics/client_producer.json @@ -0,0 +1,199 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/high_cardinality_drop/resources/metrics/server_consumer.json b/test/app_signals/high_cardinality_drop/resources/metrics/server_consumer.json new file mode 100644 index 000000000..6748e6fd0 --- /dev/null +++ b/test/app_signals/high_cardinality_drop/resources/metrics/server_consumer.json @@ -0,0 +1,219 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/high_cardinality_keep/app_signals_test.go b/test/app_signals/high_cardinality_keep/app_signals_test.go new file mode 100644 index 000000000..01e9976b8 --- /dev/null +++ b/test/app_signals/high_cardinality_keep/app_signals_test.go @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "fmt" + "log" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" +) + +const ( + AppSignalsServerConsumerTestName = "AppSignals-Server-Consumer" + AppSignalsClientProducerTestName = "AppSignals-Client-Producer" + AppSignalsTracesTestName = "AppSignals-Traces" +) + +type AppSignalsTestSuite struct { + suite.Suite + test_runner.TestSuite +} + +func (suite *AppSignalsTestSuite) SetupSuite() { + fmt.Println(">>>> Starting AppSignalsTestSuite") +} + +func (suite *AppSignalsTestSuite) TearDownSuite() { + suite.Result.Print() + fmt.Println(">>>> Finished AppSignalsTestSuite") +} + +func init() { + environment.RegisterEnvironmentMetaDataFlags() +} + +var ( + eksTestRunners []*test_runner.EKSTestRunner +) + +func getEksTestRunners(env *environment.MetaData) []*test_runner.EKSTestRunner { + if eksTestRunners == nil { + factory := dimension.GetDimensionFactory(*env) + + eksTestRunners = []*test_runner.EKSTestRunner{ + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsServerConsumerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsClientProducerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsTracesRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsTracesTestName, env.EKSClusterName}, + Env: *env, + }, + } + } + return eksTestRunners +} + +func (suite *AppSignalsTestSuite) TestAllInSuite() { + env := environment.GetEnvironmentMetaData() + switch env.ComputeType { + case computetype.EKS: + log.Println("Environment compute type is EKS") + for _, testRunner := range getEksTestRunners(env) { + testRunner.Run(suite, env) + } + default: + return + } + + suite.Assert().Equal(status.SUCCESSFUL, suite.Result.GetStatus(), "AppSignals Test Suite Failed") +} + +func (suite *AppSignalsTestSuite) AddToSuiteResult(r status.TestGroupResult) { + suite.Result.TestGroupResults = append(suite.Result.TestGroupResults, r) +} + +func TestAppSignalsSuite(t *testing.T) { + suite.Run(t, new(AppSignalsTestSuite)) +} diff --git a/test/app_signals/high_cardinality_keep/high_cardinality_keep_metrics_test.go b/test/app_signals/high_cardinality_keep/high_cardinality_keep_metrics_test.go new file mode 100644 index 000000000..85a7d8f31 --- /dev/null +++ b/test/app_signals/high_cardinality_keep/high_cardinality_keep_metrics_test.go @@ -0,0 +1,69 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "time" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" +) + +const testRetryCount = 6 +const namespace = "AppSignals" + +type AppSignalsMetricsRunner struct { + test_runner.BaseTestRunner + testName string + dimensionKey string +} + +func (t *AppSignalsMetricsRunner) Validate() status.TestGroupResult { + metricsToFetch := t.GetMeasuredMetrics() + testResults := make([]status.TestResult, len(metricsToFetch)) + instructions := GetInstructionsFromTestName(t.testName) + + for i, metricName := range metricsToFetch { + var testResult status.TestResult + for j := 0; j < testRetryCount; j++ { + testResult = metric.ValidateAppSignalsMetric(t.DimensionFactory, namespace, metricName, instructions) + if testResult.Status == status.SUCCESSFUL { + break + } + time.Sleep(30 * time.Second) + } + testResults[i] = testResult + } + + return status.TestGroupResult{ + Name: t.GetTestName(), + TestResults: testResults, + } +} + +func (t *AppSignalsMetricsRunner) GetTestName() string { + return t.testName +} + +func (t *AppSignalsMetricsRunner) GetAgentRunDuration() time.Duration { + return 3 * time.Minute +} + +func (t *AppSignalsMetricsRunner) GetMeasuredMetrics() []string { + return metric.AppSignalsMetricNames +} + +func (e *AppSignalsMetricsRunner) GetAgentConfigFileName() string { + return "" +} + +func GetInstructionsFromTestName(testName string) []dimension.Instruction { + return metric.ClientProducerInstructions +} + +var _ test_runner.ITestRunner = (*AppSignalsMetricsRunner)(nil) diff --git a/test/app_signals/high_cardinality_keep/resources/config.json b/test/app_signals/high_cardinality_keep/resources/config.json new file mode 100644 index 000000000..729bc9191 --- /dev/null +++ b/test/app_signals/high_cardinality_keep/resources/config.json @@ -0,0 +1,31 @@ +{ + "agent": { + "debug": true + }, + "logs": { + "metrics_collected": { + "app_signals": { + "enabled": true, + "rules": [ + { + "selectors": [ + { + "dimensions": "RemoteTarget", + "match": "remote-target" + } + ], + "action": "keep", + "rule_name": "keep" + } + ] + } + } + }, + "traces": { + "traces_collected": { + "app_signals": { + "enabled": true + } + } + } + } \ No newline at end of file diff --git a/test/app_signals/high_cardinality_keep/resources/metrics/client_producer.json b/test/app_signals/high_cardinality_keep/resources/metrics/client_producer.json new file mode 100644 index 000000000..8a39c60d6 --- /dev/null +++ b/test/app_signals/high_cardinality_keep/resources/metrics/client_producer.json @@ -0,0 +1,199 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/high_cardinality_keep/resources/metrics/server_consumer.json b/test/app_signals/high_cardinality_keep/resources/metrics/server_consumer.json new file mode 100644 index 000000000..6748e6fd0 --- /dev/null +++ b/test/app_signals/high_cardinality_keep/resources/metrics/server_consumer.json @@ -0,0 +1,219 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/high_cardinality_replace/app_signals_test.go b/test/app_signals/high_cardinality_replace/app_signals_test.go new file mode 100644 index 000000000..01e9976b8 --- /dev/null +++ b/test/app_signals/high_cardinality_replace/app_signals_test.go @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "fmt" + "log" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" +) + +const ( + AppSignalsServerConsumerTestName = "AppSignals-Server-Consumer" + AppSignalsClientProducerTestName = "AppSignals-Client-Producer" + AppSignalsTracesTestName = "AppSignals-Traces" +) + +type AppSignalsTestSuite struct { + suite.Suite + test_runner.TestSuite +} + +func (suite *AppSignalsTestSuite) SetupSuite() { + fmt.Println(">>>> Starting AppSignalsTestSuite") +} + +func (suite *AppSignalsTestSuite) TearDownSuite() { + suite.Result.Print() + fmt.Println(">>>> Finished AppSignalsTestSuite") +} + +func init() { + environment.RegisterEnvironmentMetaDataFlags() +} + +var ( + eksTestRunners []*test_runner.EKSTestRunner +) + +func getEksTestRunners(env *environment.MetaData) []*test_runner.EKSTestRunner { + if eksTestRunners == nil { + factory := dimension.GetDimensionFactory(*env) + + eksTestRunners = []*test_runner.EKSTestRunner{ + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsServerConsumerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsMetricsRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsClientProducerTestName, "HostedIn.EKS.Cluster"}, + Env: *env, + }, + { + Runner: &AppSignalsTracesRunner{test_runner.BaseTestRunner{DimensionFactory: factory}, AppSignalsTracesTestName, env.EKSClusterName}, + Env: *env, + }, + } + } + return eksTestRunners +} + +func (suite *AppSignalsTestSuite) TestAllInSuite() { + env := environment.GetEnvironmentMetaData() + switch env.ComputeType { + case computetype.EKS: + log.Println("Environment compute type is EKS") + for _, testRunner := range getEksTestRunners(env) { + testRunner.Run(suite, env) + } + default: + return + } + + suite.Assert().Equal(status.SUCCESSFUL, suite.Result.GetStatus(), "AppSignals Test Suite Failed") +} + +func (suite *AppSignalsTestSuite) AddToSuiteResult(r status.TestGroupResult) { + suite.Result.TestGroupResults = append(suite.Result.TestGroupResults, r) +} + +func TestAppSignalsSuite(t *testing.T) { + suite.Run(t, new(AppSignalsTestSuite)) +} diff --git a/test/app_signals/high_cardinality_replace/high_cardinality_replace_metrics_test.go b/test/app_signals/high_cardinality_replace/high_cardinality_replace_metrics_test.go new file mode 100644 index 000000000..366a79e70 --- /dev/null +++ b/test/app_signals/high_cardinality_replace/high_cardinality_replace_metrics_test.go @@ -0,0 +1,69 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "time" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" +) + +const testRetryCount = 6 +const namespace = "AppSignals" + +type AppSignalsMetricsRunner struct { + test_runner.BaseTestRunner + testName string + dimensionKey string +} + +func (t *AppSignalsMetricsRunner) Validate() status.TestGroupResult { + metricsToFetch := t.GetMeasuredMetrics() + testResults := make([]status.TestResult, len(metricsToFetch)) + instructions := GetInstructionsFromTestName(t.testName) + + for i, metricName := range metricsToFetch { + var testResult status.TestResult + for j := 0; j < testRetryCount; j++ { + testResult = metric.ValidateAppSignalsMetric(t.DimensionFactory, namespace, metricName, instructions) + if testResult.Status == status.SUCCESSFUL { + break + } + time.Sleep(30 * time.Second) + } + testResults[i] = testResult + } + + return status.TestGroupResult{ + Name: t.GetTestName(), + TestResults: testResults, + } +} + +func (t *AppSignalsMetricsRunner) GetTestName() string { + return t.testName +} + +func (t *AppSignalsMetricsRunner) GetAgentRunDuration() time.Duration { + return 3 * time.Minute +} + +func (t *AppSignalsMetricsRunner) GetMeasuredMetrics() []string { + return metric.AppSignalsMetricNames +} + +func (e *AppSignalsMetricsRunner) GetAgentConfigFileName() string { + return "" +} + +func GetInstructionsFromTestName(testName string) []dimension.Instruction { + return metric.ClientProducerReplacedInstructions +} + +var _ test_runner.ITestRunner = (*AppSignalsMetricsRunner)(nil) diff --git a/test/app_signals/high_cardinality_replace/resources/config.json b/test/app_signals/high_cardinality_replace/resources/config.json new file mode 100644 index 000000000..1e2f396c2 --- /dev/null +++ b/test/app_signals/high_cardinality_replace/resources/config.json @@ -0,0 +1,37 @@ +{ + "agent": { + "debug": true + }, + "logs": { + "metrics_collected": { + "app_signals": { + "enabled": true, + "rules": [ + { + "selectors": [ + { + "dimensions": "RemoteTarget", + "match": "remote-target" + } + ], + "replacements":[ + { + "target_dimension":"RemoteTarget", + "value":"replaced" + } + ], + "action": "replace", + "rule_name": "replace1" + } + ] + } + } + }, + "traces": { + "traces_collected": { + "app_signals": { + "enabled": true + } + } + } + } \ No newline at end of file diff --git a/test/app_signals/high_cardinality_replace/resources/metrics/client_producer.json b/test/app_signals/high_cardinality_replace/resources/metrics/client_producer.json new file mode 100644 index 000000000..8a39c60d6 --- /dev/null +++ b/test/app_signals/high_cardinality_replace/resources/metrics/client_producer.json @@ -0,0 +1,199 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/high_cardinality_replace/resources/metrics/server_consumer.json b/test/app_signals/high_cardinality_replace/resources/metrics/server_consumer.json new file mode 100644 index 000000000..6748e6fd0 --- /dev/null +++ b/test/app_signals/high_cardinality_replace/resources/metrics/server_consumer.json @@ -0,0 +1,219 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/metrics_test.go b/test/app_signals/metrics_test.go new file mode 100644 index 000000000..4771cba10 --- /dev/null +++ b/test/app_signals/metrics_test.go @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "time" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" +) + +const testRetryCount = 6 +const namespace = "AppSignals" + +type AppSignalsMetricsRunner struct { + test_runner.BaseTestRunner + testName string + dimensionKey string +} + +func (t *AppSignalsMetricsRunner) Validate() status.TestGroupResult { + metricsToFetch := t.GetMeasuredMetrics() + testResults := make([]status.TestResult, len(metricsToFetch)) + instructions := GetInstructionsFromTestName(t.testName) + + for i, metricName := range metricsToFetch { + var testResult status.TestResult + for j := 0; j < testRetryCount; j++ { + testResult = metric.ValidateAppSignalsMetric(t.DimensionFactory, namespace, metricName, instructions) + if testResult.Status == status.SUCCESSFUL { + break + } + time.Sleep(30 * time.Second) + } + testResults[i] = testResult + } + + return status.TestGroupResult{ + Name: t.GetTestName(), + TestResults: testResults, + } +} + +func (t *AppSignalsMetricsRunner) GetTestName() string { + return t.testName +} + +func (t *AppSignalsMetricsRunner) GetAgentRunDuration() time.Duration { + return 3 * time.Minute +} + +func (t *AppSignalsMetricsRunner) GetMeasuredMetrics() []string { + return metric.AppSignalsMetricNames +} + +func (e *AppSignalsMetricsRunner) GetAgentConfigFileName() string { + return "" +} + +func GetInstructionsFromTestName(testName string) []dimension.Instruction { + switch testName { + case AppSignalsClientProducerTestName: + return metric.ClientProducerInstructions + case AppSignalsServerConsumerTestName: + return metric.ServerConsumerInstructions + default: + return nil + } +} + +var _ test_runner.ITestRunner = (*AppSignalsMetricsRunner)(nil) diff --git a/test/app_signals/resources/config.json b/test/app_signals/resources/config.json new file mode 100644 index 000000000..9c03d5915 --- /dev/null +++ b/test/app_signals/resources/config.json @@ -0,0 +1,15 @@ +{ + "agent": { + "debug": true + }, + "logs": { + "metrics_collected": { + "app_signals": {} + } + }, + "traces": { + "traces_collected": { + "app_signals": {} + } + } +} \ No newline at end of file diff --git a/test/app_signals/resources/metrics/client_producer.json b/test/app_signals/resources/metrics/client_producer.json new file mode 100644 index 000000000..8a39c60d6 --- /dev/null +++ b/test/app_signals/resources/metrics/client_producer.json @@ -0,0 +1,199 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/resources/metrics/server_consumer.json b/test/app_signals/resources/metrics/server_consumer.json new file mode 100644 index 000000000..6748e6fd0 --- /dev/null +++ b/test/app_signals/resources/metrics/server_consumer.json @@ -0,0 +1,219 @@ +{ + "resourceMetrics": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeMetrics": [ + { + "metrics": [ + { + "name": "Error", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Fault", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + } + , + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + }, + { + "name": "Latency", + "unit": "Milliseconds", + "sum": { + "dataPoints": [ + { + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "SERVER" + } + }, + { + "key": "Operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "Service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "K8s.Namespace", + "value": { + "stringValue": "default" + } + }, + { + "key": "K8s.Pod", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "K8s.Node", + "value": { + "stringValue": "i-00000000000000000" + } + }, + { + "key": "K8s.Workload", + "value": { + "stringValue": "sample-app" + } + } + ], + "startTimeUnixNano": START_TIME, + "timeUnixNano": START_TIME, + "sum": 0, + "min": 0, + "max": 0 + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/resources/traceid_generator.go b/test/app_signals/resources/traceid_generator.go new file mode 100644 index 000000000..a56fde0f8 --- /dev/null +++ b/test/app_signals/resources/traceid_generator.go @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package main + +import ( + "crypto/rand" + "encoding/binary" + "encoding/hex" + "fmt" + "time" +) + +func main() { + var r [16]byte + epochNow := time.Now().Unix() + binary.BigEndian.PutUint32(r[0:4], uint32(epochNow)) + rand.Read(r[4:]) + fmt.Printf("%s", hex.EncodeToString(r[:])) +} \ No newline at end of file diff --git a/test/app_signals/resources/traces/traces.json b/test/app_signals/resources/traces/traces.json new file mode 100644 index 000000000..b809f1dd7 --- /dev/null +++ b/test/app_signals/resources/traces/traces.json @@ -0,0 +1,90 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [ + { + "key": "k8s.namespace.name", + "value": { + "stringValue": "default" + } + }, + { + "key": "k8s.pod.name", + "value": { + "stringValue": "pod-name" + } + }, + { + "key": "aws.deployment.name", + "value": { + "stringValue": "deployment-name" + } + }, + { + "key": "host.id", + "value": { + "stringValue": "i-00000000000000000" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "app-signals-integration-test" + }, + "spans": [ + { + "traceId": "TRACE_ID", + "spanId": "EEE19B7EC3C1B174", + "parentSpanId": "EEE19B7EC3C1B173", + "name": "app-signals-integration-test-traces", + "startTimeUnixNano": START_TIME, + "endTimeUnixNano": START_TIME, + "kind": 2, + "attributes": [ + { + "key": "aws.span.kind", + "value": { + "stringValue": "CLIENT" + } + }, + { + "key": "aws.local.operation", + "value": { + "stringValue": "operation" + } + }, + { + "key": "aws.local.service", + "value": { + "stringValue": "service-name" + } + }, + { + "key": "aws.remote.operation", + "value": { + "stringValue": "remote-operation" + } + }, + { + "key": "aws.remote.service", + "value": { + "stringValue": "service-name-remote" + } + }, + { + "key": "aws.remote.target", + "value": { + "stringValue": "remote-target" + } + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/app_signals/traces_test.go b/test/app_signals/traces_test.go new file mode 100644 index 000000000..fe84a2b0f --- /dev/null +++ b/test/app_signals/traces_test.go @@ -0,0 +1,77 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package app_signals + +import ( + "fmt" + "time" + + "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" +) + +const ( + lookbackDuration = time.Duration(-5) * time.Minute + EKSClusterAnnotation = "HostedIn_EKS_Cluster" +) + +var annotations = map[string]interface{}{ + "aws_remote_target": "remote-target", + "aws_remote_operation": "remote-operation", + "aws_local_service": "service-name", + "aws_remote_service": "service-name-remote", + "HostedIn_K8s_Namespace": "default", + "aws_local_operation": "operation", +} + +type AppSignalsTracesRunner struct { + test_runner.BaseTestRunner + testName string + clusterName string +} + +func (t *AppSignalsTracesRunner) Validate() status.TestGroupResult { + testResults := status.TestResult{ + Name: t.testName, + Status: status.FAILED, + } + timeNow := time.Now() + annotations[EKSClusterAnnotation] = t.clusterName + xrayFilter := awsservice.FilterExpression(annotations) + traceIds, err := awsservice.GetTraceIDs(timeNow.Add(lookbackDuration), timeNow, xrayFilter) + if err != nil { + fmt.Printf("error getting trace ids: %v", err) + } else { + fmt.Printf("Trace IDs: %v\n", traceIds) + if len(traceIds) > 0 { + testResults.Status = status.SUCCESSFUL + } + } + + return status.TestGroupResult{ + Name: t.GetTestName(), + TestResults: []status.TestResult{testResults}, + } +} + +func (t *AppSignalsTracesRunner) GetTestName() string { + return t.testName +} + +func (t *AppSignalsTracesRunner) GetAgentRunDuration() time.Duration { + return 3 * time.Minute +} + +func (t *AppSignalsTracesRunner) GetMeasuredMetrics() []string { + return nil +} + +func (e *AppSignalsTracesRunner) GetAgentConfigFileName() string { + return "" +} + +var _ test_runner.ITestRunner = (*AppSignalsTracesRunner)(nil) diff --git a/test/assume_role/assume_role_unix.go b/test/assume_role/assume_role_unix.go index 787d61cda..d8ec171ef 100644 --- a/test/assume_role/assume_role_unix.go +++ b/test/assume_role/assume_role_unix.go @@ -8,14 +8,15 @@ package assume_role import ( "log" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" "github.com/aws/amazon-cloudwatch-agent-test/util/common" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" ) const ( diff --git a/test/assume_role/assume_role_windows.go b/test/assume_role/assume_role_windows.go index 8626283fa..76a8409a8 100644 --- a/test/assume_role/assume_role_windows.go +++ b/test/assume_role/assume_role_windows.go @@ -10,12 +10,13 @@ import ( "log" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/filesystem" "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "github.com/aws/amazon-cloudwatch-agent-test/util/common" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" ) const ( diff --git a/test/ca_bundle/ca_bundle_test.go b/test/ca_bundle/ca_bundle_test.go index 2a041d932..a0de9481f 100644 --- a/test/ca_bundle/ca_bundle_test.go +++ b/test/ca_bundle/ca_bundle_test.go @@ -8,16 +8,17 @@ package ca_bundle import ( "context" "fmt" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/s3" "log" "os" "strings" "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/feature/s3/manager" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/util/common" ) @@ -29,8 +30,6 @@ const ( commonConfigTOML = "/common-config.toml" targetString = "x509: certificate signed by unknown authority" - // Let the agent run for 30 seconds. This will give agent enough time to call server - agentRuntime = 30 * time.Second localstackS3Key = "integration-test/ls_tmp/%s" keyDelimiter = "/" localstackConfigPath = "../../localstack/ls_tmp/" @@ -38,11 +37,15 @@ const ( combinePem = "combine.pem" snakeOilPem = "snakeoil.pem" tmpDirectory = "/tmp/" + runEMF = "sudo bash resources/emf.sh" + logfile = "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log" ) type input struct { - findTarget bool - dataInput string + findTarget bool + commonConfigInput string + agentConfigInput string + testType string } func init() { @@ -58,28 +61,36 @@ func TestBundle(t *testing.T) { parameters := []input{ //Use the system pem ca bundle + local stack pem file ssl should connect thus target string not found - {dataInput: "resources/integration/ssl/with/combine/bundle", findTarget: false}, + {commonConfigInput: "resources/with/combine/", agentConfigInput: "resources/https/", findTarget: false, testType: "metric"}, + {commonConfigInput: "resources/with/combine/", agentConfigInput: "resources/https/", findTarget: false, testType: "emf"}, //Do not look for ca bundle with http connection should connect thus target string not found - {dataInput: "resources/integration/ssl/without/bundle/http", findTarget: false}, + {commonConfigInput: "resources/without/", agentConfigInput: "resources/http/", findTarget: false, testType: "metric"}, + {commonConfigInput: "resources/without/", agentConfigInput: "resources/http/", findTarget: false, testType: "emf"}, //Use the system pem ca bundle ssl should not connect thus target string found - {dataInput: "resources/integration/ssl/with/original/bundle", findTarget: true}, + {commonConfigInput: "resources/with/original/", agentConfigInput: "resources/https/", findTarget: true, testType: "metric"}, + {commonConfigInput: "resources/with/original/", agentConfigInput: "resources/https/", findTarget: true, testType: "emf"}, //Do not look for ca bundle should not connect thus target string found - {dataInput: "resources/integration/ssl/without/bundle", findTarget: true}, + {commonConfigInput: "resources/without/", agentConfigInput: "resources/https/", findTarget: true, testType: "metric"}, + {commonConfigInput: "resources/without/", agentConfigInput: "resources/https/", findTarget: true, testType: "emf"}, } for _, parameter := range parameters { //before test run - log.Printf("resource file location %s find target %t", parameter.dataInput, parameter.findTarget) - t.Run(fmt.Sprintf("resource file location %s find target %t", parameter.dataInput, parameter.findTarget), func(t *testing.T) { - common.ReplaceLocalStackHostName(parameter.dataInput + configJSON) - t.Logf("config file after localstack host replace %s", string(readFile(parameter.dataInput+configJSON))) - common.CopyFile(parameter.dataInput+configJSON, configOutputPath) - common.CopyFile(parameter.dataInput+commonConfigTOML, commonConfigOutputPath) + configFile := parameter.agentConfigInput + parameter.testType + configJSON + commonConfigFile := parameter.commonConfigInput + commonConfigTOML + log.Printf("common config file location %s agent config file %s find target %t", commonConfigFile, configFile, parameter.findTarget) + t.Run(fmt.Sprintf("common config file location %s agent config file %s find target %t", commonConfigFile, configFile, parameter.findTarget), func(t *testing.T) { + common.RecreateAgentLogfile(logfile) + common.ReplaceLocalStackHostName(configFile) + t.Logf("config file after localstack host replace %s", string(readFile(configFile))) + common.CopyFile(configFile, configOutputPath) + common.CopyFile(commonConfigFile, commonConfigOutputPath) common.StartAgent(configOutputPath, true, false) - time.Sleep(agentRuntime) - log.Printf("Agent has been running for : %s", agentRuntime.String()) + // this command will take 5 seconds time 12 = 1 minute + common.RunCommand(runEMF) + log.Printf("Agent has been running for : %s", time.Minute) common.StopAgent() - output := common.ReadAgentOutput(agentRuntime) + output := common.ReadAgentLogfile(logfile) containsTarget := outputLogContainsTarget(output) if (parameter.findTarget && !containsTarget) || (!parameter.findTarget && containsTarget) { t.Errorf("Find target is %t contains target is %t", parameter.findTarget, containsTarget) diff --git a/test/ca_bundle/resources/emf.sh b/test/ca_bundle/resources/emf.sh new file mode 100755 index 000000000..aff268b06 --- /dev/null +++ b/test/ca_bundle/resources/emf.sh @@ -0,0 +1,6 @@ +for times in {1..6} + do + sleep 5 + CURRENT_TIME=$(date +%s%N | cut -b1-13) + echo '{"_aws":{"Timestamp":'"${CURRENT_TIME}"',"LogGroupName":"MetricValueBenchmarkTest","CloudWatchMetrics":[{"Namespace":"MetricValueBenchmarkTest","Dimensions":[["Type","InstanceId"]],"Metrics":[{"Name":"EMFCounter","Unit":"Count"}]}]},"Type":"Counter","EMFCounter":5}' \ > /dev/udp/0.0.0.0/25888 + done \ No newline at end of file diff --git a/test/ca_bundle/resources/http/emf/config.json b/test/ca_bundle/resources/http/emf/config.json new file mode 100644 index 000000000..1c39a0eca --- /dev/null +++ b/test/ca_bundle/resources/http/emf/config.json @@ -0,0 +1,13 @@ +{ + "agent": { + "run_as_user": "root", + "debug": true + }, + "logs": { + "endpoint_override": "http://localhost.localstack.cloud:4566", + "metrics_collected": { + "emf": { } + }, + "force_flush_interval": 5 + } +} \ No newline at end of file diff --git a/test/ca_bundle/resources/integration/ssl/without/bundle/http/config.json b/test/ca_bundle/resources/http/metric/config.json similarity index 92% rename from test/ca_bundle/resources/integration/ssl/without/bundle/http/config.json rename to test/ca_bundle/resources/http/metric/config.json index a86156cd7..606a9a7f7 100644 --- a/test/ca_bundle/resources/integration/ssl/without/bundle/http/config.json +++ b/test/ca_bundle/resources/http/metric/config.json @@ -2,8 +2,7 @@ "agent": { "metrics_collection_interval": 10, "run_as_user": "root", - "debug": true, - "logfile": "" + "debug": true }, "metrics": { "endpoint_override": "http://localhost.localstack.cloud:4566", diff --git a/test/ca_bundle/resources/https/emf/config.json b/test/ca_bundle/resources/https/emf/config.json new file mode 100644 index 000000000..d49a63403 --- /dev/null +++ b/test/ca_bundle/resources/https/emf/config.json @@ -0,0 +1,13 @@ +{ + "agent": { + "run_as_user": "root", + "debug": true + }, + "logs": { + "endpoint_override": "https://localhost.localstack.cloud:4566", + "metrics_collected": { + "emf": { } + }, + "force_flush_interval": 5 + } +} \ No newline at end of file diff --git a/test/ca_bundle/resources/integration/ssl/with/combine/bundle/config.json b/test/ca_bundle/resources/https/metric/config.json similarity index 92% rename from test/ca_bundle/resources/integration/ssl/with/combine/bundle/config.json rename to test/ca_bundle/resources/https/metric/config.json index e40748824..8b16014fc 100644 --- a/test/ca_bundle/resources/integration/ssl/with/combine/bundle/config.json +++ b/test/ca_bundle/resources/https/metric/config.json @@ -2,8 +2,7 @@ "agent": { "metrics_collection_interval": 10, "run_as_user": "root", - "debug": true, - "logfile": "" + "debug": true }, "metrics": { "endpoint_override": "https://localhost.localstack.cloud:4566", diff --git a/test/ca_bundle/resources/integration/ssl/with/original/bundle/config.json b/test/ca_bundle/resources/integration/ssl/with/original/bundle/config.json deleted file mode 100644 index e40748824..000000000 --- a/test/ca_bundle/resources/integration/ssl/with/original/bundle/config.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "agent": { - "metrics_collection_interval": 10, - "run_as_user": "root", - "debug": true, - "logfile": "" - }, - "metrics": { - "endpoint_override": "https://localhost.localstack.cloud:4566", - "metrics_collected": { - "disk": { - "measurement": [ - "used_percent" - ], - "resources": [ - "*" - ] - }, - "mem": { - "measurement": [ - "mem_used_percent" - ] - } - }, - "force_flush_interval": 10 - } -} \ No newline at end of file diff --git a/test/ca_bundle/resources/integration/ssl/without/bundle/config.json b/test/ca_bundle/resources/integration/ssl/without/bundle/config.json deleted file mode 100644 index e40748824..000000000 --- a/test/ca_bundle/resources/integration/ssl/without/bundle/config.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "agent": { - "metrics_collection_interval": 10, - "run_as_user": "root", - "debug": true, - "logfile": "" - }, - "metrics": { - "endpoint_override": "https://localhost.localstack.cloud:4566", - "metrics_collected": { - "disk": { - "measurement": [ - "used_percent" - ], - "resources": [ - "*" - ] - }, - "mem": { - "measurement": [ - "mem_used_percent" - ] - } - }, - "force_flush_interval": 10 - } -} \ No newline at end of file diff --git a/test/ca_bundle/resources/integration/ssl/without/bundle/http/common-config.toml b/test/ca_bundle/resources/integration/ssl/without/bundle/http/common-config.toml deleted file mode 100644 index 2c2bf52d5..000000000 --- a/test/ca_bundle/resources/integration/ssl/without/bundle/http/common-config.toml +++ /dev/null @@ -1,3 +0,0 @@ - -# [ssl] -# ca_bundle_path = "/tmp/combine.pem" \ No newline at end of file diff --git a/test/ca_bundle/resources/integration/ssl/with/combine/bundle/common-config.toml b/test/ca_bundle/resources/with/combine/common-config.toml similarity index 100% rename from test/ca_bundle/resources/integration/ssl/with/combine/bundle/common-config.toml rename to test/ca_bundle/resources/with/combine/common-config.toml diff --git a/test/ca_bundle/resources/integration/ssl/with/original/bundle/common-config.toml b/test/ca_bundle/resources/with/original/common-config.toml similarity index 100% rename from test/ca_bundle/resources/integration/ssl/with/original/bundle/common-config.toml rename to test/ca_bundle/resources/with/original/common-config.toml diff --git a/test/ca_bundle/resources/integration/ssl/without/bundle/common-config.toml b/test/ca_bundle/resources/without/common-config.toml similarity index 100% rename from test/ca_bundle/resources/integration/ssl/without/bundle/common-config.toml rename to test/ca_bundle/resources/without/common-config.toml diff --git a/test/canary/canary_test.go b/test/canary/canary_test.go index 9adeb2a5b..d0d92ab04 100644 --- a/test/canary/canary_test.go +++ b/test/canary/canary_test.go @@ -12,11 +12,12 @@ import ( "strings" "testing" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/stretchr/testify/require" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "github.com/aws/amazon-cloudwatch-agent-test/util/common" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" - "github.com/stretchr/testify/require" ) const ( diff --git a/test/cloudformation/cloudformation_test.go b/test/cloudformation/cloudformation_test.go index 183d1bfe0..da5a63d79 100644 --- a/test/cloudformation/cloudformation_test.go +++ b/test/cloudformation/cloudformation_test.go @@ -8,15 +8,17 @@ package cloudformation import ( "context" "flag" - "github.com/aws/amazon-cloudwatch-agent-test/test/metric" - "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" - "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" "log" "os" "testing" "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" ) const ( diff --git a/test/cloudwatchlogs/publish_logs_test.go b/test/cloudwatchlogs/publish_logs_test.go index c931380da..9b2bbe6c9 100644 --- a/test/cloudwatchlogs/publish_logs_test.go +++ b/test/cloudwatchlogs/publish_logs_test.go @@ -9,6 +9,8 @@ import ( "fmt" "log" "os" + "path/filepath" + "strconv" "strings" "testing" "time" @@ -22,11 +24,12 @@ import ( ) const ( - configOutputPath = "/opt/aws/amazon-cloudwatch-agent/bin/config.json" - logLineId1 = "foo" - logLineId2 = "bar" - logFilePath = "/tmp/test.log" // TODO: not sure how well this will work on Windows - agentRuntime = 20 * time.Second // default flush interval is 5 seconds + configOutputPath = "/opt/aws/amazon-cloudwatch-agent/bin/config.json" + 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 + configPathAutoRemoval = "resources/config_auto_removal.json" ) var logLineIds = []string{logLineId1, logLineId2} @@ -86,17 +89,10 @@ 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(agentRuntime) - writeLogs(t, f, param.iterations) - time.Sleep(agentRuntime) + time.Sleep(sleepForFlush) + writeLogLines(t, f, param.iterations) + time.Sleep(sleepForFlush) common.StopAgent() - - agentLog, err := common.RunCommand(common.CatCommand + common.AgentLogFile) - if err != nil { - return - } - t.Logf("Agent logs %s", agentLog) - end := time.Now() // check CWL to ensure we got the expected number of logs in the log stream @@ -113,6 +109,84 @@ func TestWriteLogsToCloudWatch(t *testing.T) { } } +func autoRemovalTestCleanup() { + instanceId := awsservice.GetInstanceId() + awsservice.DeleteLogGroupAndStream(instanceId, instanceId) + paths, _ := filepath.Glob(logFilePath + "*") + for _, p := range paths { + _ = os.Remove(p) + } +} + +// checkData queries CWL and verifies the number of log lines. +func checkData(t *testing.T, start time.Time, lineCount int) { + end := time.Now() + // Sleep to ensure backend stores logs. + time.Sleep(time.Second * 60) + instanceId := awsservice.GetInstanceId() + err := awsservice.ValidateLogs( + instanceId, + instanceId, + &start, + &end, + // *2 because 2 lines per loop + awsservice.AssertLogsCount(lineCount), + awsservice.AssertNoDuplicateLogs(), + ) + assert.NoError(t, err) + +} + +func writeSleepRestart(t *testing.T, f *os.File, configPath string, linesPerLoop int, doRestart bool) { + if doRestart { + common.StartAgent(configPath, true, false) + } + // Sleep to ensure agent detects file before it is written. + time.Sleep(sleepForFlush) + writeLogLines(t, f, linesPerLoop) + time.Sleep(sleepForFlush) + if doRestart { + common.StopAgent() + } + c, _ := filepath.Glob(logFilePath + "*") + assert.Equal(t, 1, len(c)) +} + +// TestAutoRemovalStopAgent configures agent to monitor a file with auto removal on. +// Then it restarts the agent. +// Verify the file is NOT removed. +func TestAutoRemovalStopAgent(t *testing.T) { + defer autoRemovalTestCleanup() + f, _ := os.Create(logFilePath + "1") + defer f.Close() + // Restart the agent multiple times. + loopCount := 5 + linesPerLoop := 1000 + start := time.Now() + for i := 0; i < loopCount; i++ { + writeSleepRestart(t, f, configPathAutoRemoval, linesPerLoop, true) + } + checkData(t, start, loopCount*linesPerLoop*2) +} + +// TestAutoRemovalFileRotation repeatedly creates files matching the monitored pattern. +// After creating each file, write some log lines, sleep and verify previous_file was auto removed. +// Retrieve LogEvents from CWL and verify all log lines were uploaded. +func TestAutoRemovalFileRotation(t *testing.T) { + defer autoRemovalTestCleanup() + common.StartAgent(configPathAutoRemoval, true, false) + loopCount := 5 + linesPerLoop := 1000 + start := time.Now() + for i := 0; i < loopCount; i++ { + // Create new file each minute and run for 5 minutes. + f, _ := os.Create(logFilePath + strconv.Itoa(i)) + defer f.Close() + writeSleepRestart(t, f, configPathAutoRemoval, linesPerLoop, false) + } + checkData(t, start, loopCount*linesPerLoop*2) +} + // TestRotatingLogsDoesNotSkipLines validates https://github.com/aws/amazon-cloudwatch-agent/issues/447 // The following should happen in the test: // 1. A log line of size N should be written @@ -136,11 +210,11 @@ func TestRotatingLogsDoesNotSkipLines(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(agentRuntime) + time.Sleep(sleepForFlush) t.Log("Writing logs and rotating") // execute the script used in the repro case common.RunCommand("/usr/bin/python3 resources/write_and_rotate_logs.py") - time.Sleep(agentRuntime) + time.Sleep(sleepForFlush) common.StopAgent() // These expected log lines are created using resources/write_and_rotate_logs.py, @@ -176,7 +250,7 @@ func TestRotatingLogsDoesNotSkipLines(t *testing.T) { assert.NoError(t, err) } -func writeLogs(t *testing.T, f *os.File, iterations int) { +func writeLogLines(t *testing.T, f *os.File, iterations int) { log.Printf("Writing %d lines to %s", iterations*len(logLineIds), f.Name()) for i := 0; i < iterations; i++ { diff --git a/test/cloudwatchlogs/resources/config_auto_removal.json b/test/cloudwatchlogs/resources/config_auto_removal.json new file mode 100644 index 000000000..acec8efaf --- /dev/null +++ b/test/cloudwatchlogs/resources/config_auto_removal.json @@ -0,0 +1,20 @@ +{ + "agent": { + "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", + "auto_removal": true + } + ] + } + } + } +} \ No newline at end of file diff --git a/test/cloudwatchlogs/resources/config_log.json b/test/cloudwatchlogs/resources/config_log.json index dbaca0a1a..0cb8fbb4f 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/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_filter.json b/test/cloudwatchlogs/resources/config_log_filter.json index 598d35dfc..20d4b7aaa 100644 --- a/test/cloudwatchlogs/resources/config_log_filter.json +++ b/test/cloudwatchlogs/resources/config_log_filter.json @@ -8,7 +8,7 @@ "files": { "collect_list": [ { - "file_path": "/tmp/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/ecs/ecs_metadata/ecs_metadata_test.go b/test/ecs/ecs_metadata/ecs_metadata_test.go index 738e9020a..0e132bd9a 100644 --- a/test/ecs/ecs_metadata/ecs_metadata_test.go +++ b/test/ecs/ecs_metadata/ecs_metadata_test.go @@ -7,12 +7,12 @@ import ( _ "embed" "flag" "fmt" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "log" "strings" "testing" "time" + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/stretchr/testify/assert" "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" diff --git a/test/emf/emf_container_test.go b/test/emf/emf_container_test.go index 5ddf5fdfd..488b1963a 100644 --- a/test/emf/emf_container_test.go +++ b/test/emf/emf_container_test.go @@ -1,15 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + //go:build !windows package emf import ( + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" - "time" ) type EMFTestRunner struct { diff --git a/test/emf/emf_test.go b/test/emf/emf_test.go index 35f45ec69..3ba31cec8 100644 --- a/test/emf/emf_test.go +++ b/test/emf/emf_test.go @@ -7,14 +7,16 @@ package emf import ( "fmt" + "log" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" - "github.com/stretchr/testify/suite" - "log" - "testing" ) type MetricBenchmarkTestSuite struct { diff --git a/test/emf_concurrent/emf_concurrent_test.go b/test/emf_concurrent/emf_concurrent_test.go new file mode 100644 index 000000000..4c7a2802f --- /dev/null +++ b/test/emf_concurrent/emf_concurrent_test.go @@ -0,0 +1,92 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package emf_concurrent + +import ( + "fmt" + "log" + "net" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" + "github.com/aws/amazon-cloudwatch-agent-test/util/common" +) + +const ( + testRuntime = 10 * time.Minute + threadCount = 15 + connectionCount = 5 + interval = 500 * time.Millisecond + emfAddress = "0.0.0.0:25888" +) + +var ( + // queryString checks that both metric values are the same and have the same expected unit. + queryString = fmt.Sprintf("filter ispresent(%[1]s) and ispresent(%[2]s) and (%[1]s != %[2]s or (_aws.CloudWatchMetrics.0.Metrics.0.Unit!=%[3]q) or (_aws.CloudWatchMetrics.0.Metrics.1.Unit!=%[3]q))", metricName1, metricName2, metricUnit) +) + +func init() { + environment.RegisterEnvironmentMetaDataFlags() +} + +func TestConcurrent(t *testing.T) { + env := environment.GetEnvironmentMetaData() + + common.CopyFile(filepath.Join("testdata", "config.json"), common.ConfigOutputPath) + require.NoError(t, common.StartAgent(common.ConfigOutputPath, true, false)) + + // wait for agent to start up + time.Sleep(5 * time.Second) + + e := &emitter{ + interval: interval, + logGroupName: fmt.Sprintf("emf-test-group-%s", env.InstanceId), + logStreamName: fmt.Sprintf("emf-test-stream-%s", env.InstanceId), + dimension: env.CwaCommitSha, + done: make(chan struct{}), + } + + defer awsservice.DeleteLogGroup(e.logGroupName) + + tcpAddr, err := net.ResolveTCPAddr("tcp", emfAddress) + if err != nil { + log.Fatalf("invalid tcp emfAddress (%s): %v", emfAddress, err) + } + + var conns []*net.TCPConn + for i := 0; i < connectionCount; i++ { + var conn *net.TCPConn + conn, err = net.DialTCP("tcp", nil, tcpAddr) + if err != nil { + log.Fatalf("unable to connect to address (%s): %v", emfAddress, err) + } + conns = append(conns, conn) + } + + log.Printf("Starting EMF emitters for log group (%s)/stream (%s)", e.logGroupName, e.logStreamName) + startTime := time.Now() + for i := 0; i < threadCount; i++ { + e.wg.Add(1) + go e.start(conns[i%len(conns)]) + } + time.Sleep(testRuntime) + close(e.done) + log.Println("Stopping EMF emitters") + e.wg.Wait() + common.StopAgent() + endTime := time.Now() + + assert.Lenf(t, awsservice.GetLogStreamNames(e.logGroupName), 1, "Detected corruption: multiple streams found") + log.Printf("Starting query for log group (%s): %s", e.logGroupName, queryString) + got, err := awsservice.GetLogQueryStats(e.logGroupName, startTime.Unix(), endTime.Unix(), queryString) + require.NoError(t, err, "Unable to get log query stats") + assert.NotZero(t, got.RecordsScanned, "No records found in CloudWatch Logs") + assert.Zerof(t, got.RecordsMatched, "Detected corruption: %v/%v records matched", got.RecordsMatched, got.RecordsScanned) +} diff --git a/test/emf_concurrent/emitter.go b/test/emf_concurrent/emitter.go new file mode 100644 index 000000000..0883a697b --- /dev/null +++ b/test/emf_concurrent/emitter.go @@ -0,0 +1,112 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package emf_concurrent + +import ( + "encoding/json" + "fmt" + "math/rand" + "net" + "sync" + "time" +) + +const ( + metadataName = "_aws" + namespace = "ConcurrentEMFTest" + metricName1 = "ExecutionTime" + metricName2 = "DuplicateExecutionTime" + metricValue = 1.23456789 + metricUnit = "Seconds" + dimensionName = "Dimension" + randomName = "Random" + letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +) + +var ( + newLineChar = []byte("\n") +) + +type Metadata struct { + Timestamp int64 `json:"Timestamp"` + LogGroupName string `json:"LogGroupName"` + LogStreamName string `json:"LogStreamName"` + CloudWatchMetrics []CWMetric `json:"CloudWatchMetrics"` +} + +type CWMetric struct { + Namespace string `json:"Namespace"` + Dimensions [][]string `json:"Dimensions"` + Metrics []Metric `json:"Metrics"` +} + +type Metric struct { + Name string `json:"Name"` + Unit string `json:"Unit"` +} + +type emitter struct { + wg sync.WaitGroup + done chan struct{} + interval time.Duration + logGroupName string + logStreamName string + dimension string +} + +func (e *emitter) start(conn *net.TCPConn) { + defer e.wg.Done() + ticker := time.NewTicker(e.interval) + metadata := e.createMetadata() + for { + select { + case <-e.done: + ticker.Stop() + return + case <-ticker.C: + metadata.Timestamp = time.Now().UnixMilli() + _, _ = conn.Write(e.createEmfLog(metadata)) + } + } +} + +func (e *emitter) createMetadata() *Metadata { + return &Metadata{ + Timestamp: time.Now().UnixMilli(), + LogGroupName: e.logGroupName, + LogStreamName: e.logStreamName, + CloudWatchMetrics: []CWMetric{ + { + Namespace: namespace, + Dimensions: [][]string{{dimensionName}}, + Metrics: []Metric{ + {Name: metricName1, Unit: metricUnit}, + {Name: metricName2, Unit: metricUnit}, + }, + }, + }, + } +} + +func (e *emitter) createEmfLog(metadata *Metadata) []byte { + r := rand.Intn(99) + 1 + emfLog := map[string]interface{}{ + metadataName: metadata, + dimensionName: e.dimension, + metricName1: metricValue, + metricName2: metricValue, + // introduces variability in payload size + randomName: fmt.Sprintf("https://www.amazon.com/%s", randString(r)), + } + content, _ := json.Marshal(emfLog) + return append(content, newLineChar...) +} + +func randString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} diff --git a/test/emf_concurrent/testdata/config.json b/test/emf_concurrent/testdata/config.json new file mode 100644 index 000000000..0f5ba8ee3 --- /dev/null +++ b/test/emf_concurrent/testdata/config.json @@ -0,0 +1,11 @@ +{ + "agent": { + "debug": true + }, + "logs": { + "metrics_collected": { + "emf": { + } + } + } +} \ No newline at end of file diff --git a/test/feature/windows/event_logs/agent_config.json b/test/feature/windows/event_logs/agent_config.json new file mode 100644 index 000000000..64f516115 --- /dev/null +++ b/test/feature/windows/event_logs/agent_config.json @@ -0,0 +1,61 @@ +{ + "agent": { + "debug": true + }, + "logs": { + "logs_collected": { + "windows_events": { + "collect_list": [ + { + "event_name": "Security", + "event_levels": [ + "INFORMATION", + "WARNING", + "ERROR", + "CRITICAL", + "VERBOSE" + ], + "log_group_name": "{instance_id}", + "log_stream_name": "SecurityEvent" + }, + { + "event_name": "System", + "event_levels": [ + "INFORMATION", + "WARNING", + "ERROR", + "CRITICAL", + "VERBOSE" + ], + "log_group_name": "{instance_id}", + "log_stream_name": "System" + }, + { + "event_name": "Application", + "event_levels": [ + "INFORMATION", + "WARNING", + "ERROR", + "CRITICAL", + "VERBOSE" + ], + "log_group_name": "{instance_id}", + "log_stream_name": "Application" + } + ] + }, + + "files": { + "collect_list": [ + { + "file_path": "C:/Users/Administrator/AppData/Local/Temp/test1.log", + "log_group_name": "{instance_id}", + "log_stream_name": "test1.log", + "timezone": "UTC" + } + ] + } + }, + "force_flush_interval": 5 + } +} \ No newline at end of file diff --git a/test/feature/windows/event_logs/parameters.yml b/test/feature/windows/event_logs/parameters.yml new file mode 100644 index 000000000..4a2703cc0 --- /dev/null +++ b/test/feature/windows/event_logs/parameters.yml @@ -0,0 +1,74 @@ +# Receivers that agent needs to tests +receivers: [] + +#Test case name +test_case: "win_feature_event_log" +validate_type: "feature" +# Only support metrics/traces/logs, even in this case we validate more than logs, +# we only make this data_type as a placeholder +data_type: "logs" + +# Number of logs being written +number_monitored_logs: 1 +# Number of metrics to be sent or number of log lines being written each minute +values_per_minute: "2" +# Number of seconds the agent should run and collect the metrics. In this case, 1 minutes +agent_collection_period: 60 + +cloudwatch_agent_config: "" + +# Logs that the test needs to validate; moreover, the feature validation already has +# InstanceID as a log group; therefore, does not need to pass it +# https://github.com/aws/amazon-cloudwatch-agent-test/blob/96f576e865b55de5e2aa88e4cf80b79c4d3dad70/validator/validators/feature/feature_validator.go#L108-L111 +# Moreover, the logs are being generated at with the generator +# https://github.com/aws/amazon-cloudwatch-agent-test/blob/96f576e865b55de5e2aa88e4cf80b79c4d3dad70/internal/common/logs.go#L41-L64 +# and being generated with 2 logs line per minute +# https://github.com/aws/amazon-cloudwatch-agent-test/blob/96f576e865b55de5e2aa88e4cf80b79c4d3dad70/test/feature/mac/parameters.yml#L14 +# and the collection period is 60. If X minutes, the logs line would be X * log lines + +log_validation: + - log_value: "Microsoft-Windows-Security-Auditing" + log_lines: 1 + log_stream: "SecurityEvent" + - log_value: "Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'" + log_lines: 1 + log_stream: "SecurityEvent" + - log_value: "This is a log line." + log_lines: 2 + log_stream: "test1.log" + - log_value: "# 0 - This is a log line." + log_lines: 1 + log_stream: "test1.log" + - log_value: "# 1 - This is a log line." + log_lines: 1 + log_stream: "test1.log" + - log_value: "System information log" + log_level: "Information" + log_lines: 1 + log_stream: "System" + log_source: "WindowsEvents" + - log_value: "System warning log" + log_level: "Warning" + log_lines: 1 + log_stream: "System" + log_source: "WindowsEvents" + - log_value: "System error log" + log_level: "Error" + log_lines: 1 + log_stream: "System" + log_source: "WindowsEvents" + - log_value: "Application information log" + log_level: "Information" + log_lines: 1 + log_stream: "Application" + log_source: "WindowsEvents" + - log_value: "Application warning log" + log_level: "Warning" + log_lines: 1 + log_stream: "Application" + log_source: "WindowsEvents" + - log_value: "Application error log" + log_level: "Error" + log_lines: 1 + log_stream: "Application" + log_source: "WindowsEvents" \ No newline at end of file diff --git a/test/fips/fips_test.go b/test/fips/fips_test.go index 1ab918d5a..cb26ff2f1 100644 --- a/test/fips/fips_test.go +++ b/test/fips/fips_test.go @@ -9,12 +9,13 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/util/common" - "github.com/aws/aws-sdk-go-v2/aws" ) const ( diff --git a/test/lvm/lvm_test.go b/test/lvm/lvm_test.go index 569adfd26..306c739ce 100644 --- a/test/lvm/lvm_test.go +++ b/test/lvm/lvm_test.go @@ -9,13 +9,14 @@ import ( "os" "testing" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" "github.com/aws/amazon-cloudwatch-agent-test/util/common" - "github.com/aws/aws-sdk-go-v2/aws" ) const namespace = "LVMTest" diff --git a/test/metric/app_signals_util.go b/test/metric/app_signals_util.go new file mode 100644 index 000000000..4e070e40d --- /dev/null +++ b/test/metric/app_signals_util.go @@ -0,0 +1,127 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +//go:build !windows + +package metric + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" +) + +var ( + AppSignalsMetricNames = []string{ + "Error", + "Fault", + "Latency", + } + + ServerConsumerInstructions = []dimension.Instruction{ + { + Key: "HostedIn.EKS.Cluster", + Value: dimension.UnknownDimensionValue(), + }, + { + Key: "HostedIn.K8s.Namespace", + Value: dimension.ExpectedDimensionValue{Value: aws.String("default")}, + }, + { + Key: "Service", + Value: dimension.ExpectedDimensionValue{Value: aws.String("service-name")}, + }, + { + Key: "Operation", + Value: dimension.ExpectedDimensionValue{Value: aws.String("operation")}, + }, + } + + ClientProducerInstructions = []dimension.Instruction{ + { + Key: "HostedIn.EKS.Cluster", + Value: dimension.UnknownDimensionValue(), + }, + { + Key: "HostedIn.K8s.Namespace", + Value: dimension.ExpectedDimensionValue{Value: aws.String("default")}, + }, + { + Key: "Service", + Value: dimension.ExpectedDimensionValue{Value: aws.String("service-name")}, + }, + { + Key: "RemoteService", + Value: dimension.ExpectedDimensionValue{Value: aws.String("service-name-remote")}, + }, + { + Key: "Operation", + Value: dimension.ExpectedDimensionValue{Value: aws.String("operation")}, + }, + { + Key: "RemoteOperation", + Value: dimension.ExpectedDimensionValue{Value: aws.String("remote-operation")}, + }, + { + Key: "RemoteTarget", + Value: dimension.ExpectedDimensionValue{Value: aws.String("remote-target")}, + }, + } + + ClientProducerReplacedInstructions = []dimension.Instruction{ + { + Key: "HostedIn.EKS.Cluster", + Value: dimension.UnknownDimensionValue(), + }, + { + Key: "HostedIn.K8s.Namespace", + Value: dimension.ExpectedDimensionValue{Value: aws.String("default")}, + }, + { + Key: "Service", + Value: dimension.ExpectedDimensionValue{Value: aws.String("service-name")}, + }, + { + Key: "RemoteService", + Value: dimension.ExpectedDimensionValue{Value: aws.String("service-name-remote")}, + }, + { + Key: "Operation", + Value: dimension.ExpectedDimensionValue{Value: aws.String("operation")}, + }, + { + Key: "RemoteOperation", + Value: dimension.ExpectedDimensionValue{Value: aws.String("remote-operation")}, + }, + { + Key: "RemoteTarget", + Value: dimension.ExpectedDimensionValue{Value: aws.String("replaced")}, + }, + } +) + +func ValidateAppSignalsMetric(dimFactory dimension.Factory, namespace string, metricName string, instructions []dimension.Instruction) status.TestResult { + testResult := status.TestResult{ + Name: metricName, + Status: status.FAILED, + } + + dims, failed := dimFactory.GetDimensions(instructions) + if len(failed) > 0 { + return testResult + } + + fetcher := MetricValueFetcher{} + values, err := fetcher.Fetch(namespace, metricName, dims, SUM, HighResolutionStatPeriod) + if err != nil { + return testResult + } + + if !IsAllValuesGreaterThanOrEqualToExpectedValue(metricName, values, 0) { + return testResult + } + + testResult.Status = status.SUCCESSFUL + return testResult +} diff --git a/test/metric/dimension/container_insights_provider.go b/test/metric/dimension/container_insights_provider.go index 555f01bde..32cb8d8e0 100644 --- a/test/metric/dimension/container_insights_provider.go +++ b/test/metric/dimension/container_insights_provider.go @@ -6,11 +6,13 @@ package dimension import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" - "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" + "log" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" - "log" + + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" ) type ContainerInsightsDimensionProvider struct { diff --git a/test/metric/dimension/emf_ecs_dimension_provider.go b/test/metric/dimension/emf_ecs_dimension_provider.go index 657dacffd..f3c510f41 100644 --- a/test/metric/dimension/emf_ecs_dimension_provider.go +++ b/test/metric/dimension/emf_ecs_dimension_provider.go @@ -6,9 +6,10 @@ package dimension import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" ) type EMFECSDimensionProvider struct { diff --git a/test/metric/dimension/host_provider.go b/test/metric/dimension/host_provider.go index a489e2b03..c90fcf5a6 100644 --- a/test/metric/dimension/host_provider.go +++ b/test/metric/dimension/host_provider.go @@ -6,10 +6,12 @@ package dimension import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "os" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" - "os" + + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" ) type HostDimensionProvider struct { diff --git a/test/metric/dimension/imageid_provider.go b/test/metric/dimension/imageid_provider.go index 8f31f113c..1473bf97f 100644 --- a/test/metric/dimension/imageid_provider.go +++ b/test/metric/dimension/imageid_provider.go @@ -6,10 +6,11 @@ package dimension import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" - "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" ) type LocalImageIdDimensionProvider struct { diff --git a/test/metric/dimension/instanceid_provider.go b/test/metric/dimension/instanceid_provider.go index 218208013..c07b09e38 100644 --- a/test/metric/dimension/instanceid_provider.go +++ b/test/metric/dimension/instanceid_provider.go @@ -6,11 +6,13 @@ package dimension import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" - "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" + "log" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" - "log" + + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" ) type ECSInstanceIdDimensionProvider struct { @@ -79,6 +81,14 @@ func (p *EKSClusterNameProvider) IsApplicable() bool { } func (p *EKSClusterNameProvider) GetDimension(instruction Instruction) types.Dimension { + // For AppSignals metrics, cluster name is under EKS.Cluster dimension + if instruction.Key == "HostedIn.EKS.Cluster" { + return types.Dimension{ + Name: aws.String("HostedIn.EKS.Cluster"), + Value: aws.String(p.env.EKSClusterName), + } + } + if instruction.Key != "ClusterName" || instruction.Value.IsKnown() { return types.Dimension{} } diff --git a/test/metric/dimension/instancetype_provider.go b/test/metric/dimension/instancetype_provider.go index b5374a660..0f241b163 100644 --- a/test/metric/dimension/instancetype_provider.go +++ b/test/metric/dimension/instancetype_provider.go @@ -6,10 +6,11 @@ package dimension import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" - "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" ) type LocalInstanceTypeDimensionProvider struct { diff --git a/test/metric/dimension/provider.go b/test/metric/dimension/provider.go index 10daf8b7b..6d6a200c8 100644 --- a/test/metric/dimension/provider.go +++ b/test/metric/dimension/provider.go @@ -8,8 +8,9 @@ package dimension import ( "log" - "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" ) type ExpectedDimensionValue struct { diff --git a/test/metric/metric_validation_util.go b/test/metric/metric_validation_util.go index 4cd362b97..e6eb4813e 100644 --- a/test/metric/metric_validation_util.go +++ b/test/metric/metric_validation_util.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package metric import "log" diff --git a/test/metric/stat.go b/test/metric/stat.go index ecb9615d3..763028566 100644 --- a/test/metric/stat.go +++ b/test/metric/stat.go @@ -11,5 +11,6 @@ const ( SAMPLE_COUNT Statistics = "SampleCount" MINIMUM Statistics = "Minimum" MAXUMUM Statistics = "Maxmimum" + SUM Statistics = "Sum" HighResolutionStatPeriod = 10 ) diff --git a/test/metric/statsd_util.go b/test/metric/statsd_util.go index 395767342..60a70be35 100644 --- a/test/metric/statsd_util.go +++ b/test/metric/statsd_util.go @@ -6,12 +6,14 @@ package metric import ( - "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" - "github.com/aws/amazon-cloudwatch-agent-test/test/status" - "github.com/aws/aws-sdk-go-v2/aws" "log" "strings" "time" + + "github.com/aws/aws-sdk-go-v2/aws" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" ) const ( diff --git a/test/metric_dimension/aggregation_dimensions_test.go b/test/metric_dimension/aggregation_dimensions_test.go index 8bc340a2a..7c68a441d 100644 --- a/test/metric_dimension/aggregation_dimensions_test.go +++ b/test/metric_dimension/aggregation_dimensions_test.go @@ -8,11 +8,12 @@ package metric_dimension import ( "log" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" - "github.com/aws/aws-sdk-go-v2/aws" ) type AggregationDimensionsTestRunner struct { diff --git a/test/metric_dimension/drop_original_test.go b/test/metric_dimension/drop_original_test.go index 172722ea9..6b66bb83a 100644 --- a/test/metric_dimension/drop_original_test.go +++ b/test/metric_dimension/drop_original_test.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + //go:build !windows package metric_dimension diff --git a/test/metric_dimension/global_append_dimensions_test.go b/test/metric_dimension/global_append_dimensions_test.go index d77776a46..cfc524a07 100644 --- a/test/metric_dimension/global_append_dimensions_test.go +++ b/test/metric_dimension/global_append_dimensions_test.go @@ -6,12 +6,14 @@ package metric_dimension import ( + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" - "github.com/aws/aws-sdk-go-v2/aws" - "log" ) type GlobalAppendDimensionsTestRunner struct { diff --git a/test/metric_dimension/metrics_dimension_test.go b/test/metric_dimension/metrics_dimension_test.go index 0021021f3..097f563ac 100644 --- a/test/metric_dimension/metrics_dimension_test.go +++ b/test/metric_dimension/metrics_dimension_test.go @@ -10,11 +10,12 @@ import ( "log" "testing" + "github.com/stretchr/testify/suite" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" - "github.com/stretchr/testify/suite" ) type MetricsAppendDimensionTestSuite struct { diff --git a/test/metric_value_benchmark/eks_deployment_prometheus_test.go b/test/metric_value_benchmark/eks_deployment_prometheus_test.go index 967b8148e..383d56d05 100644 --- a/test/metric_value_benchmark/eks_deployment_prometheus_test.go +++ b/test/metric_value_benchmark/eks_deployment_prometheus_test.go @@ -6,10 +6,11 @@ package metric_value_benchmark import ( - "github.com/aws/aws-sdk-go-v2/aws" "log" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/cluster.json b/test/metric_value_benchmark/eks_resources/test_schemas/cluster.json index 6041a219f..514e9edb1 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/cluster.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/cluster.json @@ -22,6 +22,5 @@ "Timestamp", "Version", "CloudWatchMetrics" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/cluster_daemonset.json b/test/metric_value_benchmark/eks_resources/test_schemas/cluster_daemonset.json new file mode 100644 index 000000000..e8aa9420c --- /dev/null +++ b/test/metric_value_benchmark/eks_resources/test_schemas/cluster_daemonset.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "structured log schema", + "description": "json schema for the cloudwatch agent k8s structured log", + "type": "object", + "properties": { + "ClusterName":{}, + "Type":{}, + "Sources":{}, + "Timestamp":{}, + "Version":{}, + "AutoScalingGroupName":{}, + "Namespace": {}, + "NodeName": {}, + "PodName": {}, + "replicas_desired": {}, + "replicas_ready": {}, + "status_replicas_available": {}, + "status_replicas_unavailable": {} + }, + "required": [ + "ClusterName", + "Type", + "Sources", + "Timestamp", + "Version", + "AutoScalingGroupName", + "NodeName", + "PodName", + "Namespace" + ] +} \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/cluster_deployment.json b/test/metric_value_benchmark/eks_resources/test_schemas/cluster_deployment.json new file mode 100644 index 000000000..e8aa9420c --- /dev/null +++ b/test/metric_value_benchmark/eks_resources/test_schemas/cluster_deployment.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "structured log schema", + "description": "json schema for the cloudwatch agent k8s structured log", + "type": "object", + "properties": { + "ClusterName":{}, + "Type":{}, + "Sources":{}, + "Timestamp":{}, + "Version":{}, + "AutoScalingGroupName":{}, + "Namespace": {}, + "NodeName": {}, + "PodName": {}, + "replicas_desired": {}, + "replicas_ready": {}, + "status_replicas_available": {}, + "status_replicas_unavailable": {} + }, + "required": [ + "ClusterName", + "Type", + "Sources", + "Timestamp", + "Version", + "AutoScalingGroupName", + "NodeName", + "PodName", + "Namespace" + ] +} \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/cluster_service.json b/test/metric_value_benchmark/eks_resources/test_schemas/cluster_service.json index 5c8294023..2bc4ecfb4 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/cluster_service.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/cluster_service.json @@ -34,6 +34,5 @@ "Service", "kubernetes", "CloudWatchMetrics" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/container.json b/test/metric_value_benchmark/eks_resources/test_schemas/container.json index f12b4e22e..980c23526 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/container.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/container.json @@ -46,6 +46,9 @@ "container_last_termination_reason":{}, "number_of_container_restarts":{}, "container_status":{}, + "container_filesystem_usage":{}, + "container_filesystem_available":{}, + "container_filesystem_utilization":{}, "kubernetes":{ "type": "object", "properties": { @@ -103,6 +106,5 @@ "PodName", "Namespace", "kubernetes" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/container_fs.json b/test/metric_value_benchmark/eks_resources/test_schemas/container_fs.json index 50625cadc..c13fb7ff7 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/container_fs.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/container_fs.json @@ -84,6 +84,5 @@ "device", "fstype", "kubernetes" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/control_plane.json b/test/metric_value_benchmark/eks_resources/test_schemas/control_plane.json new file mode 100644 index 000000000..fe3531447 --- /dev/null +++ b/test/metric_value_benchmark/eks_resources/test_schemas/control_plane.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "structured log schema", + "description": "json schema for the cloudwatch agent k8s structured log", + "type": "object", + "properties": { + "ClusterName": {}, + "Type": {}, + "Sources": {}, + "Timestamp": {}, + "Version": {}, + "apiserver_storage_size_bytes": {}, + "apiserver_storage_db_total_size_in_bytes": {}, + "etcd_db_total_size_in_bytes": {}, + "apiserver_storage_list_duration_seconds": {}, + "apiserver_longrunning_requests": {}, + "apiserver_request_duration_seconds": {}, + "rest_client_request_duration_seconds": {}, + "apiserver_request_total": {}, + "apiserver_request_total_5xx": {}, + "apiserver_admission_controller_admission_duration_seconds": {}, + "apiserver_admission_step_admission_duration_seconds": {}, + "etcd_request_duration_seconds": {}, + "rest_client_requests_total": {}, + "apiserver_current_inflight_requests": {}, + "apiserver_current_inqueue_requests": {}, + "apiserver_admission_webhook_admission_duration_seconds": {}, + "apiserver_requested_deprecated_apis": {}, + "apiserver_flowcontrol_rejected_requests_total": {}, + "apiserver_flowcontrol_request_concurrency_limit": {}, + "CloudWatchMetrics": {} + }, + "required": [ + "ClusterName", + "Type", + "Sources", + "Timestamp", + "Version", + "CloudWatchMetrics" + ] +} \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/node.json b/test/metric_value_benchmark/eks_resources/test_schemas/node.json index 3215d0b37..e112b5f12 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/node.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/node.json @@ -78,6 +78,5 @@ "NodeName", "kubernetes", "CloudWatchMetrics" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/node_disk_io.json b/test/metric_value_benchmark/eks_resources/test_schemas/node_disk_io.json index effa56226..5f190b686 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/node_disk_io.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/node_disk_io.json @@ -47,6 +47,5 @@ "NodeName", "kubernetes", "device" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/node_fs.json b/test/metric_value_benchmark/eks_resources/test_schemas/node_fs.json index e514c13da..5417c80f5 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/node_fs.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/node_fs.json @@ -47,6 +47,5 @@ "kubernetes", "device", "fstype" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/node_net.json b/test/metric_value_benchmark/eks_resources/test_schemas/node_net.json index 4ce5aa371..8072909ef 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/node_net.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/node_net.json @@ -45,6 +45,5 @@ "NodeName", "kubernetes", "interface" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/pod.json b/test/metric_value_benchmark/eks_resources/test_schemas/pod.json index 6e78e6b1a..c202160fc 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/pod.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/pod.json @@ -45,6 +45,17 @@ "pod_number_of_running_containers":{}, "pod_number_of_containers":{}, "pod_number_of_container_restarts":{}, + "pod_container_status_running":{}, + "pod_container_status_terminated":{}, + "pod_container_status_waiting":{}, + "pod_container_status_waiting_reason_crash_loop_back_off":{}, + "pod_container_status_waiting_reason_image_pull_error":{}, + "pod_container_status_waiting_reason_start_error":{}, + "pod_container_status_waiting_reason_create_container_error":{}, + "pod_container_status_waiting_reason_create_container_config_error":{}, + "pod_container_status_terminated_reason_oom_killed":{}, + "pod_interface_network_rx_dropped":{}, + "pod_interface_network_tx_dropped":{}, "pod_status":{}, "kubernetes":{ "type": "object", @@ -94,6 +105,5 @@ "Namespace", "kubernetes", "CloudWatchMetrics" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/test_schemas/pod_net.json b/test/metric_value_benchmark/eks_resources/test_schemas/pod_net.json index f47a6091a..b4e79f6d3 100644 --- a/test/metric_value_benchmark/eks_resources/test_schemas/pod_net.json +++ b/test/metric_value_benchmark/eks_resources/test_schemas/pod_net.json @@ -55,6 +55,5 @@ "Namespace", "interface", "kubernetes" - ], - "additionalProperties": false + ] } \ No newline at end of file diff --git a/test/metric_value_benchmark/eks_resources/util.go b/test/metric_value_benchmark/eks_resources/util.go index 06b90ceed..d4e73912a 100644 --- a/test/metric_value_benchmark/eks_resources/util.go +++ b/test/metric_value_benchmark/eks_resources/util.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package eks_resources import _ "embed" @@ -5,6 +8,10 @@ import _ "embed" var ( //go:embed test_schemas/cluster.json eksClusterSchema string + //go:embed test_schemas/cluster_daemonset.json + eksClusterDaemonsetSchema string + //go:embed test_schemas/cluster_deployment.json + eksClusterDeploymentSchema string //go:embed test_schemas/cluster_namespace.json eksClusterNamespaceSchema string //go:embed test_schemas/cluster_service.json @@ -13,6 +20,8 @@ var ( eksContainerSchema string //go:embed test_schemas/container_fs.json eksContainerFSSchema string + //go:embed test_schemas/control_plane.json + eksControlPlaneSchema string //go:embed test_schemas/node.json eksNodeSchema string //go:embed test_schemas/node_disk_io.json @@ -27,16 +36,19 @@ var ( eksPodNetSchema string EksClusterValidationMap = map[string]string{ - "Cluster": eksClusterSchema, - "ClusterNamespace": eksClusterNamespaceSchema, - "ClusterService": eksClusterServiceSchema, - "Container": eksContainerSchema, - "ContainerFS": eksContainerFSSchema, - "Node": eksNodeSchema, - "NodeDiskIO": eksNodeDiskIOSchema, - "NodeFS": eksNodeFSSchema, - "NodeNet": eksNodeNetSchema, - "Pod": eksPodSchema, - "PodNet": eksPodNetSchema, + "Cluster": eksClusterSchema, + "ClusterDaemonset": eksClusterDaemonsetSchema, + "ClusterDeployment": eksClusterDeploymentSchema, + "ClusterNamespace": eksClusterNamespaceSchema, + "ClusterService": eksClusterServiceSchema, + "Container": eksContainerSchema, + "ContainerFS": eksContainerFSSchema, + "ControlPlane": eksControlPlaneSchema, + "Node": eksNodeSchema, + "NodeDiskIO": eksNodeDiskIOSchema, + "NodeFS": eksNodeFSSchema, + "NodeNet": eksNodeNetSchema, + "Pod": eksPodSchema, + "PodNet": eksPodNetSchema, } ) diff --git a/test/metric_value_benchmark/emf_test.go b/test/metric_value_benchmark/emf_test.go index a69a785c0..ab29f00d3 100644 --- a/test/metric_value_benchmark/emf_test.go +++ b/test/metric_value_benchmark/emf_test.go @@ -10,13 +10,14 @@ import ( "log" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "github.com/aws/amazon-cloudwatch-agent-test/util/common" - "github.com/aws/aws-sdk-go-v2/aws" ) type EMFTestRunner struct { diff --git a/test/metric_value_benchmark/net_test.go b/test/metric_value_benchmark/net_test.go index 89828bca2..daf31182c 100644 --- a/test/metric_value_benchmark/net_test.go +++ b/test/metric_value_benchmark/net_test.go @@ -6,13 +6,13 @@ package metric_value_benchmark import ( - "github.com/aws/amazon-cloudwatch-agent-test/util/common" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" + "github.com/aws/amazon-cloudwatch-agent-test/util/common" ) type NetTestRunner struct { diff --git a/test/metric_value_benchmark/statsd_test.go b/test/metric_value_benchmark/statsd_test.go index 089c87efa..9ecd011cb 100644 --- a/test/metric_value_benchmark/statsd_test.go +++ b/test/metric_value_benchmark/statsd_test.go @@ -6,11 +6,12 @@ package metric_value_benchmark import ( - "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "strings" "time" "github.com/DataDog/datadog-go/statsd" + + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" ) diff --git a/test/otlp/trace_test.go b/test/otlp/trace_test.go index cae894eae..90aa89d9c 100644 --- a/test/otlp/trace_test.go +++ b/test/otlp/trace_test.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package otlp import ( @@ -5,10 +8,12 @@ import ( "testing" "time" - "github.com/aws/amazon-cloudwatch-agent-test/environment" - "github.com/aws/amazon-cloudwatch-agent-test/util/common" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/base" + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/otlp" ) const ( @@ -25,11 +30,11 @@ func TestTraces(t *testing.T) { env := environment.GetEnvironmentMetaData() testCases := map[string]struct { agentConfigPath string - generatorConfig *common.TraceGeneratorConfig + generatorConfig *base.TraceGeneratorConfig }{ "WithOTLP/Simple": { agentConfigPath: filepath.Join("resources", "otlp-config.json"), - generatorConfig: &common.TraceGeneratorConfig{ + generatorConfig: &base.TraceGeneratorConfig{ Interval: loadGeneratorInterval, Annotations: map[string]interface{}{ "test_type": "simple_otlp", @@ -55,13 +60,13 @@ func TestTraces(t *testing.T) { t.Run(name, func(t *testing.T) { - OtlpTestCfg := common.TraceTestConfig{ - Generator: newLoadGenerator(testCase.generatorConfig), + OtlpTestCfg := base.TraceTestConfig{ + Generator: otlp.NewLoadGenerator(testCase.generatorConfig), Name: name, AgentConfigPath: testCase.agentConfigPath, AgentRuntime: agentRuntime, } - err := common.TraceTest(t, OtlpTestCfg) + err := base.TraceTest(t, OtlpTestCfg) require.NoError(t, err, "TraceTest failed because %s", err) }) diff --git a/test/performance/trace/xray/agent_config.json b/test/performance/trace/xray/agent_config.json new file mode 100644 index 000000000..547950f13 --- /dev/null +++ b/test/performance/trace/xray/agent_config.json @@ -0,0 +1,110 @@ +{ + "agent": { + "metrics_collection_interval": 1, + "run_as_user": "root", + "debug": true + }, + "traces": { + "endpoint_override" : "https://127.0.0.1/put-data", + "traces_collected": { + "xray": { + } + } + }, + "insecure": true, + "metrics": { + "namespace": "CloudWatchAgentPerformance", + "append_dimensions": { + "InstanceId": "${aws:InstanceId}" + }, + "metrics_collected": { + "cpu": { + "measurement": [ + "time_active", "time_guest", "time_guest_nice", "time_idle", "time_iowait", "time_irq", + "time_nice", "time_softirq", "time_steal", "time_system", "time_user", + "usage_active", "usage_guest", "usage_guest_nice", "usage_idle", "usage_iowait", "usage_irq", + "usage_nice", "usage_softirq", "usage_steal", "usage_system", "usage_user" + ], + "metrics_collection_interval": 1 + }, + "swap": { + "measurement": [ + "free","used","used_percent" + ], + "metrics_collection_interval": 1 + }, + "processes": { + "measurement": [ + "blocked","running","sleeping","stopped","total","dead","idle","paging","total_threads","zombies" + ], + "metrics_collection_interval": 1 + }, + "netstat": { + "measurement": [ + "tcp_close","tcp_close_wait","tcp_closing", "tcp_established","tcp_fin_wait1","tcp_fin_wait2","tcp_last_ack", + "tcp_listen","tcp_none","tcp_syn_sent","tcp_syn_recv","tcp_time_wait","udp_socket" + ], + "metrics_collection_interval": 1 + }, + "mem": { + "measurement": [ + "active", "available", "available_percent", "buffered", "cached", "free", "inactive", "total", + "used", "used_percent" + ], + "metrics_collection_interval": 1 + }, + "diskio": { + "resources": [ + "*" + ], + "measurement": [ + "iops_in_progress", "io_time", "reads", "read_bytes", "read_time", "writes", "write_bytes", "write_time" + ], + "metrics_collection_interval": 1 + }, + "disk": { + "resources": [ + "*" + ], + "measurement": [ + "free","inodes_free","inodes_total","inodes_used","total","used","used_percent" + ], + "drop_device": true, + "metrics_collection_interval": 1 + }, + "ethtool": { + "interface_include": [ + "eth0", + "ens5" + ], + "metrics_include": [ + "queue_0_tx_cnt","queue_0_rx_cnt" + ] + }, + "net": { + "resources": [ + "eth0" + ], + "measurement": [ + "bytes_sent", "bytes_recv", "drop_in", "drop_out", "err_in", "err_out", "packets_sent", "packets_recv" + ], + "metrics_collection_interval": 1 + }, + "procstat": [ + { + "exe": "cloudwatch-agent", + "measurement": [ + "cpu_usage", + "memory_rss", + "memory_swap", + "memory_vms", + "memory_data", + "num_fds", + "write_bytes" + ], + "metrics_collection_interval": 1 + } + ] + } + } + } \ No newline at end of file diff --git a/test/performance/trace/xray/parameters.yml b/test/performance/trace/xray/parameters.yml new file mode 100644 index 000000000..f9d115133 --- /dev/null +++ b/test/performance/trace/xray/parameters.yml @@ -0,0 +1,70 @@ +receivers: ["xray"] + +test_case: "trace_xray_performance" +validate_type: "performance" +data_type: "traces" +# Number of metrics or traces to be sent or number of log lines being written each minute +values_per_minute: "" +# Number of seconds the agent should run and collect the metrics. In this case, 5 minutes +agent_collection_period: 300 + +commit_hash: +commit_date: + +cloudwatch_agent_config: "" + +# Metric that the test needs to validate +metric_namespace: "CloudWatchAgentPerformance" +metric_validation: + - metric_name: "procstat_cpu_usage" + metric_dimension: + - name: "exe" + value: "cloudwatch-agent" + - name: "process_name" + value: "amazon-cloudwatch-agent" + - metric_name: "procstat_memory_rss" + metric_dimension: + - name: "exe" + value: "cloudwatch-agent" + - name: "process_name" + value: "amazon-cloudwatch-agent" + - metric_name: "procstat_memory_swap" + metric_dimension: + - name: "exe" + value: "cloudwatch-agent" + - name: "process_name" + value: "amazon-cloudwatch-agent" + - metric_name: "procstat_memory_vms" + metric_dimension: + - name: "exe" + value: "cloudwatch-agent" + - name: "process_name" + value: "amazon-cloudwatch-agent" + - metric_name: "procstat_memory_data" + metric_dimension: + - name: "exe" + value: "cloudwatch-agent" + - name: "process_name" + value: "amazon-cloudwatch-agent" + - metric_name: "procstat_num_fds" + metric_dimension: + - name: "exe" + value: "cloudwatch-agent" + - name: "process_name" + value: "amazon-cloudwatch-agent" + - metric_name: "procstat_write_bytes" + metric_dimension: + - name: "exe" + value: "cloudwatch-agent" + - name: "process_name" + value: "amazon-cloudwatch-agent" + - metric_name: "net_bytes_sent" + metric_dimension: + - name: "interface" + value: "eth0" + - metric_name: "net_packets_sent" + metric_dimension: + - name: "interface" + value: "eth0" + - metric_name: "mem_total" + metric_dimension: [] \ No newline at end of file diff --git a/test/proxy/proxy_unix_test.go b/test/proxy/proxy_unix_test.go index 47da5ae56..720df73cc 100644 --- a/test/proxy/proxy_unix_test.go +++ b/test/proxy/proxy_unix_test.go @@ -6,10 +6,11 @@ package proxy import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment" - "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" ) const commonConfigPath = "/opt/aws/amazon-cloudwatch-agent/etc/common-config.toml" diff --git a/test/soak/soak_test.go b/test/soak/soak_test.go index 2594332f2..57a3e6102 100644 --- a/test/soak/soak_test.go +++ b/test/soak/soak_test.go @@ -9,10 +9,11 @@ import ( "strings" "testing" - "github.com/aws/amazon-cloudwatch-agent-test/environment" - "github.com/aws/amazon-cloudwatch-agent-test/util/common" "github.com/shirou/gopsutil/v3/process" "github.com/stretchr/testify/require" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/util/common" ) func init() { diff --git a/test/statsd/default_test.go b/test/statsd/default_test.go index ac18f7ea6..6754895a1 100644 --- a/test/statsd/default_test.go +++ b/test/statsd/default_test.go @@ -6,11 +6,12 @@ package statsd import ( + "strings" + "time" + "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" - "strings" - "time" ) const testRetryCount = 3 diff --git a/test/statsd/statsd_test.go b/test/statsd/statsd_test.go index 0c80c800e..66aac7a98 100644 --- a/test/statsd/statsd_test.go +++ b/test/statsd/statsd_test.go @@ -7,13 +7,13 @@ package statsd import ( "fmt" - "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" "log" "testing" "github.com/stretchr/testify/suite" "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/environment/computetype" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" "github.com/aws/amazon-cloudwatch-agent-test/test/test_runner" diff --git a/test/test_runner/ecs_test_runner.go b/test/test_runner/ecs_test_runner.go index 591ca4e90..e034edced 100644 --- a/test/test_runner/ecs_test_runner.go +++ b/test/test_runner/ecs_test_runner.go @@ -7,13 +7,13 @@ package test_runner import ( "fmt" - "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "log" "os" "time" "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/test/status" + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" ) type IAgentRunStrategy interface { diff --git a/test/test_runner/eks_test_runner.go b/test/test_runner/eks_test_runner.go index dc2f10f51..2d832d584 100644 --- a/test/test_runner/eks_test_runner.go +++ b/test/test_runner/eks_test_runner.go @@ -6,10 +6,11 @@ package test_runner import ( - "github.com/aws/amazon-cloudwatch-agent-test/environment" - "github.com/aws/amazon-cloudwatch-agent-test/test/status" "log" "time" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" ) type EKSTestRunner struct { diff --git a/test/test_runner/test_suite.go b/test/test_runner/test_suite.go index 21cdf8dee..e26f62281 100644 --- a/test/test_runner/test_suite.go +++ b/test/test_runner/test_suite.go @@ -7,6 +7,7 @@ package test_runner import ( "fmt" + "github.com/aws/amazon-cloudwatch-agent-test/test/status" ) diff --git a/test/userdata/userdata_test.go b/test/userdata/userdata_test.go index de0bad257..e11b4e49b 100644 --- a/test/userdata/userdata_test.go +++ b/test/userdata/userdata_test.go @@ -8,9 +8,9 @@ import ( "log" "testing" - "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-cloudwatch-agent-test/environment" "github.com/aws/amazon-cloudwatch-agent-test/test/metric" "github.com/aws/amazon-cloudwatch-agent-test/test/metric/dimension" "github.com/aws/amazon-cloudwatch-agent-test/test/status" diff --git a/test/xray/trace_test.go b/test/xray/trace_test.go index 73f803b02..0de9cb67f 100644 --- a/test/xray/trace_test.go +++ b/test/xray/trace_test.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package xray import ( @@ -5,9 +8,11 @@ import ( "testing" "time" - "github.com/aws/amazon-cloudwatch-agent-test/environment" - "github.com/aws/amazon-cloudwatch-agent-test/util/common" "github.com/stretchr/testify/require" + + "github.com/aws/amazon-cloudwatch-agent-test/environment" + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/base" + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/xray" ) const ( @@ -26,11 +31,11 @@ func TestTraces(t *testing.T) { env := environment.GetEnvironmentMetaData() testCases := map[string]struct { agentConfigPath string - generatorConfig *common.TraceGeneratorConfig + generatorConfig *base.TraceGeneratorConfig }{ "WithXray/Simple": { agentConfigPath: filepath.Join("resources", "xray-config.json"), - generatorConfig: &common.TraceGeneratorConfig{ + generatorConfig: &base.TraceGeneratorConfig{ Interval: loadGeneratorInterval, Annotations: map[string]interface{}{ "test_type": "simple_xray", @@ -53,13 +58,13 @@ func TestTraces(t *testing.T) { for name, testCase := range testCases { t.Run(name, func(t *testing.T) { - XrayTestCfg := common.TraceTestConfig{ - Generator: newLoadGenerator(testCase.generatorConfig), + XrayTestCfg := base.TraceTestConfig{ + Generator: xray.NewLoadGenerator(testCase.generatorConfig), Name: name, AgentConfigPath: testCase.agentConfigPath, AgentRuntime: agentRuntime, } - err := common.TraceTest(t, XrayTestCfg) + err := base.TraceTest(t, XrayTestCfg) require.NoError(t, err, "TraceTest failed because %s", err) }) diff --git a/util/awsservice/cloudformation.go b/util/awsservice/cloudformation.go index 7d36f9683..c67ad0727 100644 --- a/util/awsservice/cloudformation.go +++ b/util/awsservice/cloudformation.go @@ -1,13 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package awsservice import ( "context" + "log" + "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudformation" "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" "github.com/google/uuid" - "log" - "time" ) const instanceIdKey = "InstanceId" diff --git a/util/awsservice/cloudwatchlogs.go b/util/awsservice/cloudwatchlogs.go index 1b0c56e62..5eccc0ca1 100644 --- a/util/awsservice/cloudwatchlogs.go +++ b/util/awsservice/cloudwatchlogs.go @@ -17,7 +17,10 @@ import ( "github.com/qri-io/jsonschema" ) -const logStreamRetry = 20 +const ( + logStreamRetry = 20 + retryInterval = 10 * time.Second +) // catch ResourceNotFoundException when deleting the log group and log stream, as these // are not useful exceptions to log errors on during cleanup @@ -144,6 +147,47 @@ func IsLogGroupExists(logGroupName string) bool { return len(describeLogGroupOutput.LogGroups) > 0 } +// GetLogQueryStats for the log group between start/end (in epoch seconds) for the +// query string. +func GetLogQueryStats(logGroupName string, startTime, endTime int64, queryString string) (*types.QueryStatistics, error) { + output, err := CwlClient.StartQuery(ctx, &cloudwatchlogs.StartQueryInput{ + LogGroupName: aws.String(logGroupName), + StartTime: aws.Int64(startTime), + EndTime: aws.Int64(endTime), + QueryString: aws.String(queryString), + }) + + if err != nil { + return nil, fmt.Errorf("failed to start query for log group (%s): %w", logGroupName, err) + } + + // Sleep a fixed amount of time after making the query to give it time to + // process the request. + time.Sleep(retryInterval) + + var attempts int + for { + results, err := CwlClient.GetQueryResults(ctx, &cloudwatchlogs.GetQueryResultsInput{ + QueryId: output.QueryId, + }) + if err != nil { + return nil, fmt.Errorf("failed to get query results for log group (%s): %w", logGroupName, err) + } + switch results.Status { + case types.QueryStatusScheduled, types.QueryStatusRunning, types.QueryStatusUnknown: + if attempts >= StandardRetries { + return nil, fmt.Errorf("attempted get query results after %s without success. final status: %v", time.Duration(attempts)*retryInterval, results.Status) + } + attempts++ + time.Sleep(retryInterval) + case types.QueryStatusComplete: + return results.Statistics, nil + default: + return nil, fmt.Errorf("unexpected query status: %v", results.Status) + } + } +} + func GetLogStreams(logGroupName string) []types.LogStream { for i := 0; i < logStreamRetry; i++ { describeLogStreamsOutput, err := CwlClient.DescribeLogStreams(ctx, &cloudwatchlogs.DescribeLogStreamsInput{ @@ -162,12 +206,20 @@ func GetLogStreams(logGroupName string) []types.LogStream { return describeLogStreamsOutput.LogStreams } - time.Sleep(10 * time.Second) + time.Sleep(retryInterval) } return []types.LogStream{} } +func GetLogStreamNames(logGroupName string) []string { + var logStreamNames []string + for _, stream := range GetLogStreams(logGroupName) { + logStreamNames = append(logStreamNames, *stream.LogStreamName) + } + return logStreamNames +} + type LogEventValidator func(event types.OutputLogEvent) error type LogEventsValidator func(events []types.OutputLogEvent) error diff --git a/util/awsservice/constant.go b/util/awsservice/constant.go index ff2a313d7..1009a08e2 100644 --- a/util/awsservice/constant.go +++ b/util/awsservice/constant.go @@ -5,11 +5,11 @@ package awsservice import ( "context" - "github.com/aws/aws-sdk-go-v2/service/cloudformation" "time" "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/cloudformation" "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" "github.com/aws/aws-sdk-go-v2/service/dynamodb" diff --git a/util/common/agent_util_unix.go b/util/common/agent_util_unix.go index 03c9a7287..f0c6db3bd 100644 --- a/util/common/agent_util_unix.go +++ b/util/common/agent_util_unix.go @@ -9,10 +9,10 @@ import ( "bytes" "fmt" "log" + "os" "os/exec" "path/filepath" "strings" - "time" "github.com/aws/amazon-cloudwatch-agent-test/environment" ) @@ -166,16 +166,22 @@ func StopAgent() { log.Printf("Agent is stopped") } -func ReadAgentOutput(d time.Duration) string { +func ReadAgentLogfile(logfile string) string { + out, err := os.ReadFile(logfile) + if err != nil { + log.Fatal(fmt.Sprint(err) + string(out)) + } + return string(out) +} + +func RecreateAgentLogfile(logfile string) { out, err := exec.Command("bash", "-c", - fmt.Sprintf("sudo journalctl -u amazon-cloudwatch-agent.service --since \"%s ago\" --no-pager -q", d.String())). + fmt.Sprintf("sudo rm %s", logfile)). Output() if err != nil { log.Fatal(fmt.Sprint(err) + string(out)) } - - return string(out) } func RunShellScript(path string, args ...string) (string, error) { diff --git a/util/common/logs.go b/util/common/logs.go index 92a4e9ca1..60f8ea164 100644 --- a/util/common/logs.go +++ b/util/common/logs.go @@ -14,8 +14,9 @@ import ( "runtime" "time" - "github.com/aws/amazon-cloudwatch-agent-test/validator/models" "go.uber.org/multierr" + + "github.com/aws/amazon-cloudwatch-agent-test/validator/models" ) const logLine = "# %d - This is a log line. \n" diff --git a/util/common/traces.go b/util/common/traces/base/base.go similarity index 84% rename from util/common/traces.go rename to util/common/traces/base/base.go index b22dd1983..7bb59f595 100644 --- a/util/common/traces.go +++ b/util/common/traces/base/base.go @@ -1,4 +1,7 @@ -package common +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package base import ( "context" @@ -7,11 +10,13 @@ import ( "testing" "time" - "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "github.com/aws/aws-sdk-go-v2/service/xray/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" + + "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" + "github.com/aws/amazon-cloudwatch-agent-test/util/common" ) const ( @@ -54,15 +59,15 @@ type TraceGeneratorInterface interface { func TraceTest(t *testing.T, traceTest TraceTestConfig) error { t.Helper() startTime := time.Now() - CopyFile(traceTest.AgentConfigPath, ConfigOutputPath) - require.NoError(t, StartAgent(ConfigOutputPath, true, false), "Couldn't Start the agent") + common.CopyFile(traceTest.AgentConfigPath, common.ConfigOutputPath) + require.NoError(t, common.StartAgent(common.ConfigOutputPath, true, false), "Couldn't Start the agent") go func() { require.NoError(t, traceTest.Generator.StartSendingTraces(context.Background()), "load generator exited with error") }() time.Sleep(traceTest.AgentRuntime) traceTest.Generator.StopSendingTraces() time.Sleep(AGENT_SHUTDOWN_DELAY) - StopAgent() + common.StopAgent() testsGenerated, testsEnded := traceTest.Generator.GetSegmentCount() t.Logf("For %s , Test Cases Generated %d | Test Cases Ended: %d", traceTest.Name, testsGenerated, testsEnded) endTime := time.Now() @@ -112,3 +117,13 @@ func SegmentValidationTest(t *testing.T, traceTest TraceTestConfig, segments []t return nil } +func GenerateTraces(traceTest TraceTestConfig) error { + common.CopyFile(traceTest.AgentConfigPath, common.ConfigOutputPath) + go func() { + traceTest.Generator.StartSendingTraces(context.Background()) + }() + time.Sleep(traceTest.AgentRuntime) + traceTest.Generator.StopSendingTraces() + time.Sleep(AGENT_SHUTDOWN_DELAY) + return nil +} diff --git a/util/common/traces/generate.go b/util/common/traces/generate.go new file mode 100644 index 000000000..04ab964e9 --- /dev/null +++ b/util/common/traces/generate.go @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package traces + +import ( + "fmt" + "time" + + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/base" + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/xray" +) + +func StartTraceGeneration(receiver string, agentConfigPath string, agentRuntime time.Duration, traceSendingInterval time.Duration) error { + cfg := base.TraceTestConfig{ + Generator: nil, + Name: "", + AgentConfigPath: agentConfigPath, + AgentRuntime: agentRuntime, + } + xrayGenCfg := base.TraceGeneratorConfig{ + Interval: traceSendingInterval, + Annotations: map[string]interface{}{ + "test_type": "simple_otlp", + }, + Metadata: map[string]map[string]interface{}{ + "default": { + "nested": map[string]interface{}{ + "key": "value", + }, + }, + "custom_namespace": { + "custom_key": "custom_value", + }, + }, + } + switch receiver { + case "xray": + cfg.Generator = xray.NewLoadGenerator(&xrayGenCfg) + cfg.Name = "xray-performance-test" + case "otlp": + default: + return fmt.Errorf("%s is not supported.", receiver) + } + err := base.GenerateTraces(cfg) + return err +} diff --git a/test/otlp/generator.go b/util/common/traces/otlp/generator.go similarity index 88% rename from test/otlp/generator.go rename to util/common/traces/otlp/generator.go index d530f2910..98e41eca9 100644 --- a/test/otlp/generator.go +++ b/util/common/traces/otlp/generator.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package otlp import ( @@ -5,7 +8,6 @@ import ( "errors" "time" - "github.com/aws/amazon-cloudwatch-agent-test/util/common" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -15,6 +17,8 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" "golang.org/x/exp/maps" + + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/base" ) var generatorError = errors.New("Generator error") @@ -25,8 +29,8 @@ const ( ) type OtlpTracesGenerator struct { - common.TraceGenerator - common.TraceGeneratorInterface + base.TraceGenerator + base.TraceGeneratorInterface } func (g *OtlpTracesGenerator) StartSendingTraces(ctx context.Context) error { @@ -51,9 +55,9 @@ func (g *OtlpTracesGenerator) StartSendingTraces(ctx context.Context) error { func (g *OtlpTracesGenerator) StopSendingTraces() { close(g.Done) } -func newLoadGenerator(cfg *common.TraceGeneratorConfig) *OtlpTracesGenerator { +func NewLoadGenerator(cfg *base.TraceGeneratorConfig) *OtlpTracesGenerator { return &OtlpTracesGenerator{ - TraceGenerator: common.TraceGenerator{ + TraceGenerator: base.TraceGenerator{ Cfg: cfg, Done: make(chan struct{}), SegmentsGenerationCount: 0, @@ -90,7 +94,7 @@ func (g *OtlpTracesGenerator) GetAgentRuntime() time.Duration { func (g *OtlpTracesGenerator) GetName() string { return g.Name } -func (g *OtlpTracesGenerator) GetGeneratorConfig() *common.TraceGeneratorConfig { +func (g *OtlpTracesGenerator) GetGeneratorConfig() *base.TraceGeneratorConfig { return g.Cfg } diff --git a/test/xray/generator.go b/util/common/traces/xray/generator.go similarity index 83% rename from test/xray/generator.go rename to util/common/traces/xray/generator.go index 450af0b1e..872c012b5 100644 --- a/test/xray/generator.go +++ b/util/common/traces/xray/generator.go @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + package xray import ( @@ -7,16 +10,19 @@ import ( "os" "path" "time" - "github.com/aws/amazon-cloudwatch-agent-test/util/common" + "github.com/aws/aws-xray-sdk-go/strategy/sampling" "github.com/aws/aws-xray-sdk-go/xray" "github.com/aws/aws-xray-sdk-go/xraylog" + + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces/base" ) var generatorError = errors.New("Generator error") + type XrayTracesGenerator struct { - common.TraceGenerator - common.TraceGeneratorInterface + base.TraceGenerator + base.TraceGeneratorInterface } func (g *XrayTracesGenerator) StartSendingTraces(ctx context.Context) error { @@ -36,7 +42,7 @@ func (g *XrayTracesGenerator) StartSendingTraces(ctx context.Context) error { func (g *XrayTracesGenerator) StopSendingTraces() { close(g.Done) } -func newLoadGenerator(cfg *common.TraceGeneratorConfig) *XrayTracesGenerator { +func NewLoadGenerator(cfg *base.TraceGeneratorConfig) *XrayTracesGenerator { s, err := sampling.NewLocalizedStrategyFromFilePath( path.Join("resources", "sampling-rule.json")) if err != nil { @@ -45,7 +51,7 @@ func newLoadGenerator(cfg *common.TraceGeneratorConfig) *XrayTracesGenerator { xray.Configure(xray.Config{SamplingStrategy: s}) xray.SetLogger(xraylog.NewDefaultLogger(os.Stdout, xraylog.LogLevelWarn)) return &XrayTracesGenerator{ - TraceGenerator: common.TraceGenerator{ + TraceGenerator: base.TraceGenerator{ Cfg: cfg, Done: make(chan struct{}), SegmentsGenerationCount: 0, @@ -98,6 +104,6 @@ func (g *XrayTracesGenerator) GetAgentRuntime() time.Duration { func (g *XrayTracesGenerator) GetName() string { return g.Name } -func (g *XrayTracesGenerator) GetGeneratorConfig() *common.TraceGeneratorConfig { +func (g *XrayTracesGenerator) GetGeneratorConfig() *base.TraceGeneratorConfig { return g.Cfg } diff --git a/validator/models/validation_config.go b/validator/models/validation_config.go index ed9a96fb6..6d29ee2c7 100644 --- a/validator/models/validation_config.go +++ b/validator/models/validation_config.go @@ -15,7 +15,7 @@ import ( "gopkg.in/yaml.v3" ) -var supportedReceivers = []string{"logs", "statsd", "collectd", "system", "emf"} +var supportedReceivers = []string{"logs", "statsd", "collectd", "system", "emf", "xray"} type ValidateConfig interface { GetPluginsConfig() []string diff --git a/validator/validators/basic/basic_validator.go b/validator/validators/basic/basic_validator.go index a99096504..885b5a61c 100644 --- a/validator/validators/basic/basic_validator.go +++ b/validator/validators/basic/basic_validator.go @@ -16,6 +16,7 @@ import ( "github.com/aws/amazon-cloudwatch-agent-test/util/awsservice" "github.com/aws/amazon-cloudwatch-agent-test/util/common" + "github.com/aws/amazon-cloudwatch-agent-test/util/common/traces" "github.com/aws/amazon-cloudwatch-agent-test/validator/models" "github.com/aws/amazon-cloudwatch-agent-test/validator/validators/util" ) @@ -49,6 +50,8 @@ func (s *BasicValidator) GenerateLoad() error { switch dataType { case "logs": return common.StartLogWrite(agentConfigFilePath, agentCollectionPeriod, metricSendingInterval, dataRate) + case "traces": + return traces.StartTraceGeneration(receiver, agentConfigFilePath, agentCollectionPeriod, metricSendingInterval) default: // Sending metrics based on the receivers; however, for scraping plugin (e.g prometheus), we would need to scrape it instead of sending return common.StartSendingMetrics(receiver, agentCollectionPeriod, metricSendingInterval, dataRate, logGroup, metricNamespace) diff --git a/validator/validators/performance/performance_validator.go b/validator/validators/performance/performance_validator.go index b1e98f032..cb9482e14 100644 --- a/validator/validators/performance/performance_validator.go +++ b/validator/validators/performance/performance_validator.go @@ -5,11 +5,11 @@ package performance import ( "fmt" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "log" "strings" "time" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/aws/aws-sdk-go/aws" "github.com/cenkalti/backoff/v4" diff --git a/validator/validators/validator.go b/validator/validators/validator.go index f8da17d36..e137c6b64 100644 --- a/validator/validators/validator.go +++ b/validator/validators/validator.go @@ -41,7 +41,6 @@ func LaunchValidator(vConfig models.ValidateConfig) error { if err != nil { return err } - log.Printf("Start to sleep %f s for the metric to be available in the beginning of next minute ", durationBeforeNextMinute.Seconds()) time.Sleep(durationBeforeNextMinute)