Testing & Releasing Elasticsearch & the Elastic Stack Alexander Reelsen @spinscale alex@elastic.co

Today’s goal Transfer Elasticsearch testing & automation knowledge into your own project

main topic

Elasticsearch in 10 seconds Search Engine (FTS, Analytics, Geo), real-time Distributed, scalable, highly available, resilient Interface: HTTP & JSON Heart of the Elastic Stack

Agenda Unit Testing Integration Testing Package Testing BWC Testing Documentation Testing CI: OS/JVM Testing Releasing

Unit Testing ESTestcase

ESTestCase

ESTestCase

ESTestCase

ESTestCase

Random methods public void testRandomStringLength() { String randomString = randomAlphaOfLength(10); assertThat(randomString.length(), is(10)); boolean randomBoolean = randomBoolean(); assertThat(randomBoolean, is(anyOf(is(true), is(false)))); final Integer randomInt = randomFrom(1, 22, 11, 41, 58); assertThat(randomInt, is(allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(58)))); final int randomIntBetween = randomIntBetween(1, 10); assertThat(randomIntBetween, is(allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(10)))); final long nonNegativeLong = randomNonNegativeLong(); assertThat(nonNegativeLong, is(greaterThanOrEqualTo(0L))); final ZoneId zoneId = randomZone(); assertThat(ZoneId.getAvailableZoneIds(), hasItem(zoneId.toString())); final Locale locale = randomLocale(random()); assertThat(Arrays.asList(Locale.getAvailableLocales()), hasItem(locale)); }

Random methods public void testRandomStringLength() { String randomString = randomAlphaOfLength(10); assertThat(randomString.length(), is(10)); }

Random methods public void testRandomStringLength() { String randomString = randomAlphaOfLength(10); assertThat(randomString.length(), is(10)); } public void testRandomStringLength() { String randomString = randomAlphaOfLength(10); assertThat(randomString.length(), is(Integer.MAX_VALUE)); }

Randomization Example private int add (int a, int b) { assert a > 0; assert b > 0; return a + b; }

Randomization Example private int add (int a, int b) { assert a > 0; assert b > 0; return a + b; } public void testAdd() { assertThat(add(1, 1), is(2)); assertThat(add(2, 2), is(4)); }

Randomization Example private int add (int a, int b) { assert a > 0; assert b > 0; return a + b; } public void testAdd() { final int a = randomIntBetween(0, Integer.MAX_VALUE); final int b = randomIntBetween(0, Integer.MAX_VALUE); assertThat(add(a, b), allOf(greaterThanOrEqualTo(a), greaterThanOrEqualTo(b))); }

Randomization Example private int add (int a, int b) { assert a > 0; assert b > 0; return a + b; } @Repeat(iterations = 1000) public void testAdd() { final int a = randomIntBetween(0, Integer.MAX_VALUE); final int b = randomIntBetween(0, Integer.MAX_VALUE); assertThat(add(a, b), allOf(greaterThanOrEqualTo(a), greaterThanOrEqualTo(b))); }

Randomization Example private int add (int a, int b) { assert a > 0; assert b > 0; return a + b; } @Repeat(iterations = 1000) public void testAdd() { final int a = randomIntBetween(0, Integer.MAX_VALUE); final int b = randomIntBetween(0, Integer.MAX_VALUE); assertThat(add(a, b), allOf(greaterThanOrEqualTo(a), greaterThanOrEqualTo(b))); } java.lang.AssertionError: Expected: (a value equal to or greater than <1667057423> and a value equal to or greater than <1800656534>) but: a value equal to or greater than <1667057423> <-827253339> was less than <1667057423>

Randomization Example private int add (int a, int b) { assert a > 0; assert b > 0; return a + b; } @Repeat(iterations = 1000) public void testAdd() { final int a = randomIntBetween(0, Integer.MAX_VALUE); final int b = randomIntBetween(0, Integer.MAX_VALUE); assertThat(add(a, b), allOf(greaterThanOrEqualTo(a), greaterThanOrEqualTo(b))); } REPRODUCE WITH: ./gradlew ‘:server:test’ —tests “de.spinscale.MyTest.testAdd” -Dtests.seed=EC29A319F9128F9A -Dtests.locale=pl-PL -Dtests.timezone=America/St_Kitts

Randomization Example private int add (int a, int b) { assert a > 0; assert b > 0; return a + b; } @Seed(“EC29A319F9128F9A”) public void testAdd() { final int a = randomIntBetween(0, Integer.MAX_VALUE); final int b = randomIntBetween(0, Integer.MAX_VALUE); assertThat(add(a, b), allOf(greaterThanOrEqualTo(a), greaterThanOrEqualTo(b))); }

ESTestCase goodies Thread leak detection Change log level for single test/package Checks for excessive sysout/err writes Test method order is randomized Test class order is randomized Deprecation logging checks No inflight search contexts

Integration Testing YAML ftw!

HTTP Integration tests Run tests against Elasticsearch HTTP interface Tests are written in YAML API definition is written in JSON ESClientYamlSuiteTestCase to extend All official clients are using the same spec to guarantee feature parity

API definition { } “get”:{ “documentation”:{ “url”:”https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-get.html”, “description”:”Returns a document.” }, “stability”:”stable”, “url”:{ “paths”:[ { “path”:”/{index}/_doc/{id}”, “methods”:[ “GET” ], “parts”:{ “id”:{ “type”:”string”, “description”:”The document ID” }, “index”:{ “type”:”string”, “description”:”The name of the index” } } } ] }, “params”:{ “preference”:{ “type”:”string”, “description”:”Specify the node or shard the operation should be performed on (default: random)” } … } }

Test definition —”Basic”: - do: index: index: test_1 id: 中⽂文 body: { “foo”: “Hello: 中⽂文” } - do: get: index: test_1 id: 中⽂文 - match: { _index: - match: { _id: - match: { _source: test_1 } 中⽂文 } { foo: “Hello: 中⽂文” } }

More integration testing Test against kerberos/samba/openldap/shibboleth-idp using docker and docker-compose S3 integration testing using minio (via dockercompose) Testing the docker image via docker-compose

Package Testing Containers

Elasticsearch: A lot of flavours tar.gz, zip, deb, rpm apt / yum repositories docker images osx brew tap Windows MSI helm chart, ECK (K8s operator) puppet, ansible, chef

Testing packages Use vagrant to download/install distributions ubuntu-1604, ubuntu-1804, debian-8, debian-9, centos-6, centos-7, oel-6, oel-7, fedora-28, fedora-29, opensuse-42, sles-12, rhel-8, windows-2012r2, windows-2016

Testing packages bats is used for testing bats: bash automated testing package installation/start/stop/uninstall Long term plan: Moving away from bats to java tests https://github.com/elastic/elasticsearch/tree/master/qa/os

BWC Testing You get an upgrade, everyone gets an upgrade!

BWC Testing Upgrades must be supported and tested! Full cluster restart Rolling restarts between minor versions Rolling restarts from the latest old major version to the current major version, i.e. 6.8.7 to 7.6.1

Rolling upgrade tests 6.8.x 6.8.x 6.8.x

Rolling upgrade tests 6.8.x 6.8.x 6.8.x

Rolling upgrade tests upgrade 7.6.x 6.8.x 6.8.x

Rolling upgrade tests 7.6.x 6.8.x 6.8.x

Rolling upgrade tests 7.6.x 6.8.x 6.8.x

Rolling upgrade tests upgrade 7.6.x 7.6.x 6.8.x

Rolling upgrade tests 7.6.x 7.6.x 6.8.x

Rolling upgrade tests 7.6.x 7.6.x 6.8.x

Rolling upgrade tests upgrade 7.6.x 7.6.x 7.6.x

Rolling upgrade tests 7.6.x 7.6.x 7.6.x

Documentation Testing Y’all doing this, right?

Documentation Testing All documentation is written in asciidoc Fire up a test cluster Extract snippets from documentation Run snippets against cluster Optionally: check response contents

Sample snippet [source,console] ————————————————————————-GET twitter/_doc/0 ————————————————————————-// TEST[setup:twitter]

Sample snippet [source,console-result] ————————————————————————-{ “_index” : “twitter”, “_id” : “0”, “_version” : 1, “_seq_no” : 10, “_primary_term” : 1, “found”: true, “_source” : { “user” : “kimchy”, “date” : “2009-11-15T14:12:12”, “likes”: 0, “message” : “trying out Elasticsearch” } } ————————————————————————-// TESTRESPONSE[s/”_seq_no” : \d+/”_seq_no” : $body._seq_no/ s/”_primary_term” : 1/”_primary_term” : $body._primary_term/]

build.gradle FTW One build system to rule them all

All you need is gradle Elasticsearch uses gradle for everything All tests can be triggered via gradle Try to reduce dependency external tools

PR testing Immediate feedback…

Github PR testing Every commit to an open PR triggers a build Trigger test run via github Trigger merge and test run via github

Github PR testing

Verification after merge

CI setup JVM/OS matrix based testing

CI stats All active branches are tested Every pull request commit gets tested several thousand builds per day

Nightly benchmarks

Nightly benchmarks

CI stats

Links https://elasticsearch-ci.elastic.co https://kibana-ci.elastic.co https://logstash-ci.elastic.co https://beats-ci.elastic.co https://benchmarks.elastic.co/ https://github.com/elastic/rally

Release it! Shipping products is

Embrace the difference Every product in the stack will test differently Java, JRuby, Ruby, Go, Javascript, TypeScript, Scala, .NET, perl, PHP, python, bash, Groovy, C++, Haskell, Clojure JSON, YAML, terraform, hcl

Everyone be ready! Releases are time based Every product’s branch should be ready Build of release candidate artifacts Testing of these artifacts Repeat until done across products: Fix all blockers Releasing of those artifacts

Release manager A person responsible for the release Triggers the build of all artifacts Performs follow up tasks for each team after a release

Trust the process! Staged artifacts are the final release artifacts, no rebuilds! Push artifacts to their final destinations Official links, OSS sonatype, rubygems, docker images, package repositories

Trust the process! +200 artifacts are promoted to release bucket Using Tekton Pipelines instead of single process started on release manager machine promoting artifacts: 4x faster 3rd party promotion: 6x faster > 40 pods are doing a single release

More Work begins after releasing website communication release blog posts ensuring everything has been published

Support it! Don’t forget to phase out…

Products have a lifecycle Stop supporting old products Be consistent with support guarantees Provide maintenance policy Distinguish between end of life & fixing bugs

Summary Automate all the things!

Summary Automate early, automate often Automate your tests, all of them Manual testing will still find bugs Automate your releases Ensure everyone can do a release Non-technical aspects are much harder to automate

Thanks for listening! Questions? Alexander Reelsen @spinscale alex@elastic.co