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..3cac67b68d --- /dev/null +++ b/functionaltests/TestUpgrade_8_15_4_to_8_16_0/main.tf @@ -0,0 +1,74 @@ +terraform { + required_version = ">= 0.12.29" + + required_providers { + ec = { + source = "elastic/ec" + version = "0.12.2" + } + } +} + +# variable "name" { +# type = string +# description = "The deployment name" +# } + +variable "stack_version" { + type = string + description = "The Elasticsearch version to bootstrap" +} + +provider "ec" { + endpoint = "https://public-api.qa.cld.elstc.co" +} + +resource "ec_deployment" "example_minimal" { + name = "my_example_deployment" + + 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 = {} +} + +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/go.mod b/functionaltests/go.mod new file mode 100644 index 0000000000..4b8ed9e3ac --- /dev/null +++ b/functionaltests/go.mod @@ -0,0 +1,44 @@ +module github.com/elastic/apm-server/functionaltests + +go 1.23.2 + +require ( + github.com/elastic/apm-perf v0.0.0-20241205133854-c5983a7ff908 + 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 +) + +// FIXME: remove this when https://github.com/elastic/apm-perf/pull/197 is merged +replace github.com/elastic/apm-perf => ../../apm-perf diff --git a/functionaltests/go.sum b/functionaltests/go.sum new file mode 100644 index 0000000000..d7a7c5ff37 --- /dev/null +++ b/functionaltests/go.sum @@ -0,0 +1,117 @@ +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/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..796b9ea094 --- /dev/null +++ b/functionaltests/internal/esclient/client.go @@ -0,0 +1,115 @@ +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) 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..2b96080925 --- /dev/null +++ b/functionaltests/internal/esclient/config.go @@ -0,0 +1,43 @@ +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/runner.go b/functionaltests/internal/terraform/runner.go new file mode 100644 index 0000000000..635078f2bd --- /dev/null +++ b/functionaltests/internal/terraform/runner.go @@ -0,0 +1,63 @@ +package terraform + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform-exec/tfexec" +) + +type Runner struct { + initialized bool + outputs map[string]tfexec.OutputMeta + tf *tfexec.Terraform +} + +func New(workingDir string) (*Runner, error) { + t := Runner{} + + tf, err := tfexec.NewTerraform(workingDir, "terraform") + if err != nil { + return &t, fmt.Errorf("error instantiating terraform runner: %w", err) + } + t.tf = tf + if err := t.init(); err != nil { + return &t, fmt.Errorf("cannot run terraform init: %w", err) + } else { + t.initialized = true + } + + return &t, 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) 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/main_test.go b/functionaltests/main_test.go new file mode 100644 index 0000000000..a900874219 --- /dev/null +++ b/functionaltests/main_test.go @@ -0,0 +1,188 @@ +// 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" + "fmt" + "net/url" + "testing" + + "github.com/elastic/apm-perf/pkg/telemetrygen" + "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/hashicorp/terraform-exec/tfexec" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +const testRegion = "aws-eu-west-1" + +const ( + ecAPIEndpoint = "api.elastic-cloud.com" + ecAPIEndpointQA = "https://public-api.qa.cld.elstc.co" +) + +func TestUpgrade_8_15_4_to_8_16_0(t *testing.T) { + require.NoError(t, ecAPICheck(t)) + + ctx := context.Background() + + // create Elastic Cloud Deployment at version 8.15.4 + t.Log("creating deploment with terraform") + tf, err := terraform.New(t.Name()) + require.NoError(t, err) + version := tfexec.Var(fmt.Sprintf("stack_version=%s", "8.15.4")) + require.NoError(t, tf.Apply(ctx, version)) + + 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) + fmt.Printf("%+v\n", escfg) + + // Create APM Key + t.Log("creating API key") + ac, err := esclient.New(escfg) + require.NoError(t, err) + + apikey, err := ac.CreateAPIKey(context.Background(), + t.Name(), -1, map[string]types.RoleDescriptor{}, + ) + require.NoError(t, err) + + t.Log("ingest data") + // Ingest data through elastic/apm-perf docker image using apmtelemetrygen + // This is actually using an extracted telemetrygen as Go package. + // See https://github.com/elastic/apm-perf/pull/197 + ingest(t, escfg.APMServerURL, apikey) + + oldCount, err := ac.ApmDocCount(ctx) + require.NoError(t, err) + + var dss []types.DataStream + // check data streams + t.Log("check data streams") + // GET _data_stream/*apm* + dss, err = ac.GetDataStream(ctx, "*apm*") + require.NoError(t, err) + + require.Len(t, dss, 8) + for _, v := range dss { + assert.False(t, v.PreferIlm) + assert.Equal(t, "Data stream lifecycle", v.NextGenerationManagedBy.Name) + + assert.Len(t, v.Indices, 1) + assert.Equal(t, "Data stream lifecycle", v.Indices[0].ManagedBy.Name) + } + + // Upgrade to 8.16.0 + // FIXME: the update failed because it took more than 10m + t.Log("upgrade to 8.16.0") + require.NoError(t, tf.Apply(context.Background(), + tfexec.Var(fmt.Sprintf("stack_version=%s", "8.16.0")))) + + // check data + newCount, err := ac.ApmDocCount(ctx) + require.NoError(t, err) + + fmt.Printf("%+v\n", newCount) + assertDocCount(t, oldCount, newCount) + + // check rollover happened + t.Log("check data streams") + dss, err = ac.GetDataStream(ctx, "*apm*") + require.NoError(t, err) + + require.Len(t, dss, 8) + for _, v := range dss { + assert.False(t, v.PreferIlm) + assert.Equal(t, "Data stream lifecycle", v.NextGenerationManagedBy.Name) + + // NO rollover here, expected? + // assert.Len(t, v.Indices, 2) + assert.Equal(t, "Data stream lifecycle", v.Indices[0].ManagedBy.Name) + } + + // ingest more + t.Log("ingest more") + ingest(t, escfg.APMServerURL, apikey) + + // check rollover + // 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") + dss, err = ac.GetDataStream(ctx, "*apm*") + require.NoError(t, err) + + require.Len(t, dss, 8) + for _, v := range dss { + assert.False(t, v.PreferIlm) + assert.Equal(t, "Data stream lifecycle", v.NextGenerationManagedBy.Name) + + assert.Len(t, v.Indices, 2) + assert.Equal(t, "Data stream lifecycle", v.Indices[0].ManagedBy.Name) + } + + // check ES logs, there should be no errors + // TODO: how to get these from Elastic Cloud? Is it possible? + + // cleanup + t.Log("cleanup") + require.NoError(t, tf.Apply(context.Background(), + version, tfexec.Destroy(true))) +} + +// assertDocCount asserts that count and datastream names in each ApmDocCount slice are equal. +func assertDocCount(t *testing.T, expected []esclient.ApmDocCount, want []esclient.ApmDocCount) { + t.Helper() + + assert.Len(t, want, len(expected)) + for i, v := range expected { + assert.Equal(t, v.Count, want[i].Count) + assert.Equal(t, v.Count, want[i].Count) + } +} + +// 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) { + 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 = zap.Must(zap.NewDevelopment()) + require.NoError(t, err) + + err = g.RunBlocking(context.Background()) +} diff --git a/functionaltests/utils_test.go b/functionaltests/utils_test.go new file mode 100644 index 0000000000..2fb8d8ac0f --- /dev/null +++ b/functionaltests/utils_test.go @@ -0,0 +1,16 @@ +package functionaltests + +import ( + "fmt" + "os" + "testing" +) + +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 +}