From 5ba3747a560b6579fbcf5bcbdae03d5ca2addf14 Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Fri, 27 Dec 2024 17:38:00 +0100 Subject: [PATCH] add functionaltests framework Add utility packages to implement functional tests and Terraform code to bootstrap a Elastic Cloud deployment. Implement first test verifying upgrade from 8.15.4 to 8.16.0 for a fresh Elastic Cloud deployment. --- .github/workflows/functional-tests.yml | 2 +- functionaltests/8_15_test.go | 125 ++++++++++++++++ .../TestUpgrade_8_15_4_to_8_16_0/main.tf | 27 ++++ .../TestUpgrade_8_15_4_to_8_16_0/outputs.tf | 23 +++ .../TestUpgrade_8_15_4_to_8_16_0/tags.tf | 20 +++ .../TestUpgrade_8_15_4_to_8_16_0/terraform.tf | 21 +++ .../TestUpgrade_8_15_4_to_8_16_0/vars.tf | 42 ++++++ functionaltests/go.mod | 42 ++++++ functionaltests/go.sum | 119 +++++++++++++++ functionaltests/internal/esclient/client.go | 138 ++++++++++++++++++ functionaltests/internal/esclient/config.go | 60 ++++++++ functionaltests/internal/terraform/logger.go | 33 +++++ functionaltests/internal/terraform/runner.go | 92 ++++++++++++ functionaltests/internal/terraform/var.go | 29 ++++ functionaltests/main_test.go | 109 ++++++++++++++ functionaltests/utils_test.go | 40 +++++ 16 files changed, 921 insertions(+), 1 deletion(-) create mode 100644 functionaltests/8_15_test.go create mode 100644 functionaltests/TestUpgrade_8_15_4_to_8_16_0/main.tf create mode 100644 functionaltests/TestUpgrade_8_15_4_to_8_16_0/outputs.tf create mode 100644 functionaltests/TestUpgrade_8_15_4_to_8_16_0/tags.tf create mode 100644 functionaltests/TestUpgrade_8_15_4_to_8_16_0/terraform.tf create mode 100644 functionaltests/TestUpgrade_8_15_4_to_8_16_0/vars.tf create mode 100644 functionaltests/go.mod create mode 100644 functionaltests/go.sum create mode 100644 functionaltests/internal/esclient/client.go create mode 100644 functionaltests/internal/esclient/config.go create mode 100644 functionaltests/internal/terraform/logger.go create mode 100644 functionaltests/internal/terraform/runner.go create mode 100644 functionaltests/internal/terraform/var.go create mode 100644 functionaltests/main_test.go create mode 100644 functionaltests/utils_test.go diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index 5194f02376..12cae93377 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -34,7 +34,7 @@ jobs: secrets: |- EC_API_KEY:elastic-observability/elastic-cloud-observability-team-${{ matrix.environment }}-api-key - - run: cd functionaltests && go test -v ./ + - run: cd functionaltests && go test -v -timeout=20m -target "${{ matrix.environment }}" ./ notify: if: always() diff --git a/functionaltests/8_15_test.go b/functionaltests/8_15_test.go new file mode 100644 index 0000000000..ef56a023be --- /dev/null +++ b/functionaltests/8_15_test.go @@ -0,0 +1,125 @@ +package functionaltests + +import ( + "context" + "testing" + "time" + + "github.com/elastic/apm-server/functionaltests/internal/esclient" + "github.com/elastic/apm-server/functionaltests/internal/terraform" + "github.com/elastic/go-elasticsearch/v8/typedapi/types" + "github.com/stretchr/testify/require" +) + +func TestUpgrade_8_15_4_to_8_16_0(t *testing.T) { + require.NoError(t, ecAPICheck(t)) + + start := time.Now() + ctx := context.Background() + + t.Log("creating deploment with terraform") + tf, err := terraform.New(t, t.Name()) + require.NoError(t, err) + ecTarget := terraform.Var("ec_target", *target) + version := terraform.Var("stack_version", "8.15.4") + name := terraform.Var("name", t.Name()) + require.NoError(t, tf.Apply(ctx, ecTarget, version, name)) + t.Logf("time elapsed: %s", time.Now().Sub(start)) + + t.Cleanup(func() { + if !t.Failed() || (t.Failed() && *cleanupOnFailure) { + t.Log("cleanup terraform resources") + require.NoError(t, tf.Destroy(ctx, ecTarget, name, version)) + } else { + t.Log("test failed and cleanup-on-failure is false, skipping cleanup") + } + }) + + var deploymentID string + var escfg esclient.Config + tf.Output("deployment_id", &deploymentID) + tf.Output("apm_url", &escfg.APMServerURL) + tf.Output("es_url", &escfg.ElasticsearchURL) + tf.Output("username", &escfg.Username) + tf.Output("password", &escfg.Password) + tf.Output("kb_url", &escfg.KibanaURL) + + t.Logf("created deployment %s", deploymentID) + + ac, err := esclient.New(escfg) + require.NoError(t, err) + + t.Log("creating APM API key") + apikey, err := ac.CreateAPMAPIKey(ctx, t.Name()) + require.NoError(t, err) + + ingest(t, escfg.APMServerURL, apikey) + + // Wait few seconds before proceeding to ensure ES indexed all our documents. + // Manual tests had failures due to only 4 data streams being reported + // when no delay was used. Manual inspection always revealed the correct + // number of data streams. + time.Sleep(1 * time.Minute) + + oldCount, err := ac.ApmDocCount(ctx) + require.NoError(t, err) + + t.Log("check data streams") + var dss []types.DataStream + dss, err = ac.GetDataStream(ctx, "*apm*") + require.NoError(t, err) + assertDatastreams(t, checkDatastreamWant{ + Quantity: 8, + PreferIlm: false, + DSManagedBy: "Data stream lifecycle", + IndicesPerDs: 1, + IndicesManagedBy: []string{"Data stream lifecycle"}, + }, dss) + t.Logf("time elapsed: %s", time.Now().Sub(start)) + + t.Log("upgrade to 8.16.0") + require.NoError(t, tf.Apply(ctx, ecTarget, name, terraform.Var("stack_version", "8.16.0"))) + t.Logf("time elapsed: %s", time.Now().Sub(start)) + + t.Log("check number of documents") + newCount, err := ac.ApmDocCount(ctx) + require.NoError(t, err) + assertDocCountEqual(t, oldCount, newCount) + + t.Log("check data streams after upgrade, no rollover expected") + dss, err = ac.GetDataStream(ctx, "*apm*") + require.NoError(t, err) + assertDatastreams(t, checkDatastreamWant{ + Quantity: 8, + PreferIlm: false, + DSManagedBy: "Data stream lifecycle", + IndicesPerDs: 1, + IndicesManagedBy: []string{"Data stream lifecycle"}, + }, dss) + + ingest(t, escfg.APMServerURL, apikey) + time.Sleep(1 * time.Minute) + + t.Log("check number of documents") + newCount2, err := ac.ApmDocCount(ctx) + require.NoError(t, err) + assertDocCountGreaterThan(t, oldCount, newCount2) + + // Confirm datastreams are + // v managed by DSL if created after 8.15.0 + // x managed by ILM if created before 8.15.0 + t.Log("check data streams and verify lazy rollover happened") + dss2, err := ac.GetDataStream(ctx, "*apm*") + require.NoError(t, err) + assertDatastreams(t, checkDatastreamWant{ + Quantity: 8, + PreferIlm: false, + DSManagedBy: "Data stream lifecycle", + IndicesPerDs: 2, + IndicesManagedBy: []string{"Data stream lifecycle", "Data stream lifecycle"}, + }, dss2) + t.Logf("time elapsed: %s", time.Now().Sub(start)) + + // check ES logs, there should be no errors + // TODO: how to get these from Elastic Cloud? Is it possible? +} diff --git a/functionaltests/TestUpgrade_8_15_4_to_8_16_0/main.tf b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/main.tf new file mode 100644 index 0000000000..3a8b86239b --- /dev/null +++ b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/main.tf @@ -0,0 +1,27 @@ +resource "ec_deployment" "example_minimal" { + name = var.name + + region = "aws-eu-west-1" + version = var.stack_version + deployment_template_id = "aws-storage-optimized" + + elasticsearch = { + hot = { + autoscaling = {} + } + + ml = { + autoscaling = { + autoscale = true + } + } + } + + kibana = { + topology = {} + } + + integrations_server = {} + + tags = merge(local.ci_tags, module.tags.tags) +} diff --git a/functionaltests/TestUpgrade_8_15_4_to_8_16_0/outputs.tf b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/outputs.tf new file mode 100644 index 0000000000..7726dff580 --- /dev/null +++ b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/outputs.tf @@ -0,0 +1,23 @@ +output "deployment_id" { + value = ec_deployment.example_minimal.id +} + +output "apm_url" { + value = ec_deployment.example_minimal.integrations_server.endpoints.apm +} + +output "es_url" { + value = ec_deployment.example_minimal.elasticsearch.https_endpoint +} + +output "username" { + value = ec_deployment.example_minimal.elasticsearch_username +} +output "password" { + value = ec_deployment.example_minimal.elasticsearch_password + sensitive = true +} + +output "kb_url" { + value = ec_deployment.example_minimal.kibana.https_endpoint +} diff --git a/functionaltests/TestUpgrade_8_15_4_to_8_16_0/tags.tf b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/tags.tf new file mode 100644 index 0000000000..f59920ac32 --- /dev/null +++ b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/tags.tf @@ -0,0 +1,20 @@ +resource "time_static" "created_date" {} + +locals { + ci_tags = { + environment = var.ENVIRONMENT + repo = var.REPO + branch = var.BRANCH + build = var.BUILD_ID + created_date = coalesce(var.CREATED_DATE, time_static.created_date.unix) + } + project = "apm-server-functionaltest" +} + +module "tags" { + source = "../../testing/infra/terraform/modules/tags" + # use the convention for team/shared owned resources if we are running in CI. + # assume this is an individually owned resource otherwise. + project = local.project +} + diff --git a/functionaltests/TestUpgrade_8_15_4_to_8_16_0/terraform.tf b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/terraform.tf new file mode 100644 index 0000000000..cf2f02f9bd --- /dev/null +++ b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/terraform.tf @@ -0,0 +1,21 @@ +terraform { + required_version = ">= 0.12.29" + + required_providers { + ec = { + source = "elastic/ec" + version = "0.12.2" + } + } +} + +locals { + api_endpoints = { + qa = "https://public-api.qa.cld.elstc.co" + pro = "https://api.elastic-cloud.com" + } +} + +provider "ec" { + endpoint = local.api_endpoints[var.ec_target] +} diff --git a/functionaltests/TestUpgrade_8_15_4_to_8_16_0/vars.tf b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/vars.tf new file mode 100644 index 0000000000..4dbc0592f5 --- /dev/null +++ b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/vars.tf @@ -0,0 +1,42 @@ +variable "ec_target" { + type = string + description = "The Elastic Cloud environment to target" + validation { + condition = contains(["qa", "pro"], var.ec_target) + error_message = "Valid values are (qa, pro)." + } +} + +variable "name" { + type = string + description = "The deployment name" +} + +variable "stack_version" { + type = string + description = "The Elasticsearch version to bootstrap" +} + +# CI variables +variable "BRANCH" { + description = "Branch name or pull request for tagging purposes" + default = "unknown-branch" +} + +variable "BUILD_ID" { + description = "Build ID in the CI for tagging purposes" + default = "unknown-build" +} + +variable "CREATED_DATE" { + description = "Creation date in epoch time for tagging purposes" + default = "" +} + +variable "ENVIRONMENT" { + default = "unknown-environment" +} + +variable "REPO" { + default = "unknown-repo-name" +} diff --git a/functionaltests/go.mod b/functionaltests/go.mod new file mode 100644 index 0000000000..f300717443 --- /dev/null +++ b/functionaltests/go.mod @@ -0,0 +1,42 @@ +module github.com/elastic/apm-server/functionaltests + +go 1.23.2 + +// FIXME: use apm-perf from main after https://github.com/elastic/apm-perf/pull/197 is merged +require ( + github.com/elastic/apm-perf v0.0.0-20241213094810-d1d7602614f5 + github.com/elastic/go-elasticsearch/v8 v8.16.0 + github.com/hashicorp/terraform-exec v0.21.0 + github.com/stretchr/testify v1.10.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/zclconf/go-cty v1.14.4 // indirect + go.elastic.co/apm/v2 v2.6.2 // indirect + go.elastic.co/fastjson v1.4.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.8.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/functionaltests/go.sum b/functionaltests/go.sum new file mode 100644 index 0000000000..1940d8d732 --- /dev/null +++ b/functionaltests/go.sum @@ -0,0 +1,119 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/apm-perf v0.0.0-20241213094810-d1d7602614f5 h1:W7I6yPe/DqVHQaQ7SL9l7jBRYJQ/gF7wDgQqA7/ihCE= +github.com/elastic/apm-perf v0.0.0-20241213094810-d1d7602614f5/go.mod h1:Q1Tm237o37q6gJpYZ99Vpk8zlGaSenuiJmcE1uu9VLc= +github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA= +github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= +github.com/elastic/go-elasticsearch/v8 v8.16.0 h1:f7bR+iBz8GTAVhwyFO3hm4ixsz2eMaEy0QroYnXV3jE= +github.com/elastic/go-elasticsearch/v8 v8.16.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0= +github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= +github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= +github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.elastic.co/apm/v2 v2.6.2 h1:VBplAxgbOgTv+Giw/FS91xJpHYw/q8fz/XKPvqC+7/o= +go.elastic.co/apm/v2 v2.6.2/go.mod h1:33rOXgtHwbgZcDgi6I/GtCSMZQqgxkHC0IQT3gudKvo= +go.elastic.co/fastjson v1.4.0 h1:a4BXUKXZHAzjVOPrqtEx2FDsIRBCMek01vCnrtyutWs= +go.elastic.co/fastjson v1.4.0/go.mod h1:ZD5um63l0/8TIdddZbL2znD83FAr2IckYa3KR7VcdNA= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/functionaltests/internal/esclient/client.go b/functionaltests/internal/esclient/client.go new file mode 100644 index 0000000000..a26bb5e6cf --- /dev/null +++ b/functionaltests/internal/esclient/client.go @@ -0,0 +1,138 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 esclient + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/elastic/go-elasticsearch/v8" + "github.com/elastic/go-elasticsearch/v8/typedapi/esql/query" + "github.com/elastic/go-elasticsearch/v8/typedapi/security/createapikey" + "github.com/elastic/go-elasticsearch/v8/typedapi/types" +) + +type Client struct { + es *elasticsearch.TypedClient +} + +// New returns a new Client for querying APM data. +func New(cfg Config) (*Client, error) { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: cfg.TLSSkipVerify} + + es, err := elasticsearch.NewTypedClient(elasticsearch.Config{ + Addresses: []string{cfg.ElasticsearchURL}, + Username: cfg.Username, + APIKey: cfg.APIKey, + Password: cfg.Password, + Transport: transport, + }) + if err != nil { + return nil, fmt.Errorf("error creating Elasticsearch client: %w", err) + } + return &Client{ + es: es, + }, nil +} + +var elasticsearchTimeUnits = []struct { + Duration time.Duration + Unit string +}{ + {time.Hour, "h"}, + {time.Minute, "m"}, + {time.Second, "s"}, + {time.Millisecond, "ms"}, + {time.Microsecond, "micros"}, +} + +// formatDurationElasticsearch formats a duration using +// Elasticsearch supported time units. +// +// See https://www.elastic.co/guide/en/elasticsearch/reference/current/api-conventions.html#time-units +func formatDurationElasticsearch(d time.Duration) string { + for _, tu := range elasticsearchTimeUnits { + if d%tu.Duration == 0 { + return fmt.Sprintf("%d%s", d/tu.Duration, tu.Unit) + } + } + return fmt.Sprintf("%dnanos", d) +} + +// CreateAgentAPIKey creates an agent API Key, and returns it in the +// base64-encoded form that agents should provide. +// +// If expiration is less than or equal to zero, then the API Key never expires. +func (c *Client) CreateAPIKey(ctx context.Context, name string, expiration time.Duration, roles map[string]types.RoleDescriptor) (string, error) { + var maybeExpiration types.Duration + if expiration > 0 { + maybeExpiration = formatDurationElasticsearch(expiration) + } + resp, err := c.es.Security.CreateApiKey().Request(&createapikey.Request{ + Name: &name, + Expiration: maybeExpiration, + RoleDescriptors: roles, + Metadata: map[string]json.RawMessage{ + "creator": []byte(`"apmclient"`), + }, + }).Do(ctx) + if err != nil { + return "", fmt.Errorf("error creating API Key: %w", err) + } + return resp.Encoded, nil +} + +func (c *Client) CreateAPMAPIKey(ctx context.Context, name string) (string, error) { + return c.CreateAPIKey(context.Background(), + name, -1, map[string]types.RoleDescriptor{}, + ) +} + +func (c *Client) GetDataStream(ctx context.Context, name string) ([]types.DataStream, error) { + resp, err := c.es.Indices.GetDataStream().Name(name).Do(ctx) + if err != nil { + return []types.DataStream{}, fmt.Errorf("cannot GET datastream: %w", err) + } + + return resp.DataStreams, nil +} + +type ApmDocCount struct { + Count int + Datastream string +} + +func (c *Client) ApmDocCount(ctx context.Context) ([]ApmDocCount, error) { + q := `FROM traces-apm*,apm-*,traces-*.otel-*,logs-apm*,apm-*,logs-*.otel-*,metrics-apm*,apm-*,metrics-*.otel-* +| EVAL datastream = CONCAT(data_stream.type, "-", data_stream.dataset, "-", data_stream.namespace) +| STATS count = COUNT(*) BY datastream +| SORT count DESC` + + qry := c.es.Esql.Query().Query(q) + res, err := query.Helper[ApmDocCount](ctx, qry) + if err != nil { + return []ApmDocCount{}, fmt.Errorf("cannot retrieve APM doc count: %w", err) + } + + return res, nil +} diff --git a/functionaltests/internal/esclient/config.go b/functionaltests/internal/esclient/config.go new file mode 100644 index 0000000000..a166cfed19 --- /dev/null +++ b/functionaltests/internal/esclient/config.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 esclient + +type Config struct { + // ElasticsearchURL holds the Elasticsearch URL. + ElasticsearchURL string + + // Username holds the Elasticsearch username for basic auth. + Username string + + // Password holds the Elasticsearch password for basic auth. + Password string + + // APIKey holds an Elasticsearch API Key. + // + // This will be set from $ELASTICSEARCH_API_KEY if specified. + APIKey string + + // APMServerURL holds the APM Server URL. + // + // If this is unspecified, it will be derived from + // ElasticsearchURL if that is an Elastic Cloud URL. + APMServerURL string + + // KibanaURL holds the Kibana URL. + // + // If this is unspecified, it will be derived from + // ElasticsearchURL if that is an Elastic Cloud URL. + KibanaURL string + + // TLSSkipVerify determines if TLS certificate + // verification is skipped or not. Default to false. + // + // If not specified the value will be take from + // TLS_SKIP_VERIFY env var. + // Any value different from "" is considered true. + TLSSkipVerify bool +} + +// NewConfig returns a Config intialised from environment variables. +// func NewConfig() (Config, error) { +// cfg := Config{} +// return cfg, err +// } diff --git a/functionaltests/internal/terraform/logger.go b/functionaltests/internal/terraform/logger.go new file mode 100644 index 0000000000..c5f56005b1 --- /dev/null +++ b/functionaltests/internal/terraform/logger.go @@ -0,0 +1,33 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 terraform + +import ( + "testing" +) + +// tfLoggerv2 wraps a testing.TB to implement the printfer interface +// required by terraform-exec logger. +type tfLoggerv2 struct { + testing.TB +} + +// Printf implements terraform-exec.printfer interface +func (l *tfLoggerv2) Printf(format string, v ...interface{}) { + l.Logf(format, v...) +} diff --git a/functionaltests/internal/terraform/runner.go b/functionaltests/internal/terraform/runner.go new file mode 100644 index 0000000000..4d88216003 --- /dev/null +++ b/functionaltests/internal/terraform/runner.go @@ -0,0 +1,92 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 terraform + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec" +) + +type Runner struct { + initialized bool + outputs map[string]tfexec.OutputMeta + tf *tfexec.Terraform +} + +func New(t *testing.T, workingDir string) (*Runner, error) { + tr := Runner{} + + tf, err := tfexec.NewTerraform(workingDir, "terraform") + if err != nil { + return &tr, fmt.Errorf("error instantiating terraform runner: %w", err) + } + tf.SetLogger(&tfLoggerv2{t}) + tr.tf = tf + if err := tr.init(); err != nil { + return &tr, fmt.Errorf("cannot run terraform init: %w", err) + } else { + tr.initialized = true + } + + return &tr, nil +} + +func (t *Runner) init() error { + return t.tf.Init(context.Background(), tfexec.Upgrade(true)) +} + +func (t *Runner) Apply(ctx context.Context, vars ...tfexec.ApplyOption) error { + if !t.initialized { + if err := t.init(); err != nil { + return fmt.Errorf("cannot init before apply: %w", err) + } + } + if err := t.tf.Apply(ctx, vars...); err != nil { + return fmt.Errorf("cannot apply: %w", err) + } + + output, err := t.tf.Output(ctx) + if err != nil { + return fmt.Errorf("cannot run terraform output: %w", err) + } + + t.outputs = output + return nil +} + +func (t *Runner) Destroy(ctx context.Context, vars ...tfexec.DestroyOption) error { + if !t.initialized { + if err := t.init(); err != nil { + return fmt.Errorf("cannot init before apply: %w", err) + } + } + + return t.tf.Destroy(ctx, vars...) +} + +func (t *Runner) Output(name string, res any) error { + o := t.outputs[name] + if err := json.Unmarshal(o.Value, res); err != nil { + return fmt.Errorf("cannot unmarshal output: %w", err) + } + return nil +} diff --git a/functionaltests/internal/terraform/var.go b/functionaltests/internal/terraform/var.go new file mode 100644 index 0000000000..a31c94f696 --- /dev/null +++ b/functionaltests/internal/terraform/var.go @@ -0,0 +1,29 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 terraform + +import ( + "fmt" + + "github.com/hashicorp/terraform-exec/tfexec" +) + +// Var is a helper to simplify creating Terraform vars to pass to terraform-exec. +func Var(name, value string) *tfexec.VarOption { + return tfexec.Var(fmt.Sprintf("%s=%s", name, value)) +} diff --git a/functionaltests/main_test.go b/functionaltests/main_test.go new file mode 100644 index 0000000000..257b727a90 --- /dev/null +++ b/functionaltests/main_test.go @@ -0,0 +1,109 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 functionaltests + +import ( + "context" + "flag" + "net/url" + "testing" + + "github.com/elastic/apm-perf/pkg/telemetrygen" + "github.com/elastic/apm-server/functionaltests/internal/esclient" + "github.com/elastic/go-elasticsearch/v8/typedapi/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +var cleanupOnFailure *bool = flag.Bool("cleanup-on-failure", true, "Whether to run cleanup even if the test failed.") +var target *string = flag.String("target", "production", "The target environment where to run tests againts. Valid values are: qa, production") + +const testRegion = "aws-eu-west-1" + +// assertDocCountEqual asserts that document counts in each data stream are equal. +func assertDocCountEqual(t *testing.T, want []esclient.ApmDocCount, actual []esclient.ApmDocCount) { + t.Helper() + + assert.Len(t, want, len(actual)) + for i, v := range actual { + assert.Equal(t, v.Count, want[i].Count, "expected doc count in %s to be equal to previous value", v.Datastream) + } +} + +// assertDocCountGreaterThan verifies if document count in datastreams is greated than expected. +func assertDocCountGreaterThan(t *testing.T, want []esclient.ApmDocCount, actual []esclient.ApmDocCount) { + t.Helper() + + assert.Len(t, want, len(actual)) + for i, v := range actual { + assert.Greater(t, v.Count, want[i].Count, "expected doc count in %s to be greater than previous value", v.Datastream) + } +} + +// ingest creates and run a telemetrygen that replays multiple APM agents events to the cluster +// a single time. +func ingest(t *testing.T, apmURL string, apikey string) { + t.Helper() + + t.Log("ingest data") + cfg := telemetrygen.DefaultConfig() + cfg.APIKey = apikey + + u, err := url.Parse(apmURL) + require.NoError(t, err) + cfg.ServerURL = u + + cfg.EventRate.Set("1000/s") + g, err := telemetrygen.New(cfg) + g.Logger = zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel)) + require.NoError(t, err) + + err = g.RunBlocking(context.Background()) +} + +type checkDatastreamWant struct { + Quantity int + DSManagedBy string + IndicesPerDs int + PreferIlm bool + IndicesManagedBy []string +} + +// assertDatastreams assert expected values on specific data streams in a cluster. +func assertDatastreams(t *testing.T, expected checkDatastreamWant, actual []types.DataStream) { + t.Helper() + + require.Len(t, actual, expected.Quantity, "number of APM datastream differs from expectations") + for _, v := range actual { + if expected.PreferIlm { + assert.True(t, v.PreferIlm) + } else { + assert.False(t, v.PreferIlm) + } + assert.Equal(t, expected.DSManagedBy, v.NextGenerationManagedBy.Name) + assert.Len(t, v.Indices, expected.IndicesPerDs) + + for i, index := range v.Indices { + assert.Equal(t, expected.IndicesManagedBy[i], index.ManagedBy.Name) + } + } + +} diff --git a/functionaltests/utils_test.go b/functionaltests/utils_test.go new file mode 100644 index 0000000000..20ad54ffba --- /dev/null +++ b/functionaltests/utils_test.go @@ -0,0 +1,40 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 functionaltests + +import ( + "fmt" + "os" + "testing" +) + +// ecAPICheck verifies if EC_API_KEY env var is set. +// This is a simple check to alert users if this necessary env var +// is not available. +// +// Functional tests are expected to run Terraform code to operate +// on infrastructure required for each tests and to query Elastic +// Cloud APIs. In both cases a valid API key is required. +func ecAPICheck(t *testing.T) error { + t.Helper() + apiKey := os.Getenv("EC_API_KEY") + if apiKey == "" { + return fmt.Errorf("unable to obtain value from EC_API_KEY environment variable") + } + return nil +}