A presentation at Agile & Automation Days in in Gdańsk, Poland by Alexander Reelsen
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.4 to 7.4.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.4.x 6.8.x 6.8.x
Rolling upgrade tests 7.4.x 6.8.x 6.8.x
Rolling upgrade tests 7.4.x 6.8.x 6.8.x
Rolling upgrade tests upgrade 7.4.x 7.4.x 6.8.x
Rolling upgrade tests 7.4.x 7.4.x 6.8.x
Rolling upgrade tests 7.4.x 7.4.x 6.8.x
Rolling upgrade tests upgrade 7.4.x 7.4.x 7.4.x
Rolling upgrade tests 7.4.x 7.4.x 7.4.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
Thanks for listening! Questions? Alexander Reelsen @spinscale alex@elastic.co
Elasticsearch is well known piece of software. This talk explains the different levels of testing along with packaging and releasing as part of the Elastic Stack.
Testing a well known software like Elasticsearch is not too different to any other software. In this session we will peak into the different testing strategies for unit and integration tests including randomized testing, how we leverage gradle, how we do packaging tests, how we test the REST layer, what our CI infrastructure and tooling around that looks like and finally what happens in order to release Elasticsearch and other parts of the Elastic Stack.
Be prepared to see a fair share of source code and snippets.