diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..4a8a9ecd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +version: 2 +updates: + - package-ecosystem: maven + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 20 + ignore: + - dependency-name: org.elasticsearch.client:elasticsearch-rest-high-level-client + versions: + - 7.11.x + - 7.12.x + - 7.13.x + - 7.14.x + - 7.15.x + - 7.16.x + - 7.17.x + - 8.x + - dependency-name: org.elasticsearch:elasticsearch + versions: + - 7.11.x + - 7.12.x + - 7.13.x + - 7.14.x + - 7.15.x + - 7.16.x + - 7.17.x + - 8.x diff --git a/.github/workflows/maven-matrix.yml b/.github/workflows/maven-matrix.yml index f7562bcf..69ca6998 100644 --- a/.github/workflows/maven-matrix.yml +++ b/.github/workflows/maven-matrix.yml @@ -2,6 +2,11 @@ name: Maven Matrix Build on: push: + branches: + - master + - release + # pull_request to run the pipeline on PRs from external, because "push" does not create this pipeline on external PRs + pull_request: schedule: # * is a special character in YAML so you have to quote this string - cron: '30 5 * * *' @@ -14,15 +19,16 @@ jobs: build-and-test-with-jdk: strategy: matrix: - java: [ 8, 9, 10, 11, 12, 13, 14, 15, 16 ] + java: [ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ] fail-fast: false runs-on: ubuntu-18.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu java-version: ${{ matrix.java }} - name: Cache local Maven repository uses: actions/cache@v2 @@ -40,19 +46,20 @@ jobs: build-and-test-with-es-version: strategy: matrix: - elasticsearchVersion: [ "7.12.0", "7.11.2", "7.10.2", "7.9.3", "7.8.1", - "7.7.1", "7.6.2", "7.5.2" ] + elasticsearchVersion: [ "8.1.2", "8.0.1", "7.17.2", "7.16.3", "7.15.2", "7.14.2", + "7.13.4", "7.12.1", "7.11.2", "7.10.2", "7.9.3", "7.8.1", "7.7.1", "7.6.2", "7.5.2" ] fail-fast: false runs-on: ubuntu-18.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu java-version: 11 - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.m2/repository @@ -68,26 +75,27 @@ jobs: release-dry-run: # this will just build like the real release job, but not do a release (dry run) - needs: [build-and-test-with-jdk, build-and-test-with-es-version] - if: ${{ github.ref != 'refs/heads/release' }} + needs: [ build-and-test-with-jdk, build-and-test-with-es-version ] + if: ${{ github.event_name != 'pull_request' && github.ref != 'refs/heads/release' }} runs-on: ubuntu-18.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Import GPG key to sign maven build artifacts - uses: crazy-max/ghaction-import-gpg@v3 + uses: crazy-max/ghaction-import-gpg@v4 with: - gpg-private-key: ${{ secrets.GPG_SECRET_KEYS }} + gpg_private_key: ${{ secrets.GPG_SECRET_KEYS }} passphrase: ${{ secrets.GPG_PASSPHRASE }} - git-user-signingkey: true - git-commit-gpgsign: true + git_user_signingkey: true + git_commit_gpgsign: true - name: Set up JDK 8 # with JDK 11 the maven-javadoc-plugin > 3.0.1 fails with "cannot find symbol org.elasticsearch.*" - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu java-version: 8 - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.m2/repository @@ -108,26 +116,27 @@ jobs: release: # Release to maven central and create Github release - needs: [build-and-test-with-jdk, build-and-test-with-es-version] + needs: [ build-and-test-with-jdk, build-and-test-with-es-version ] if: ${{ github.repository == 'senacor/elasticsearch-evolution' && github.event_name == 'push' && github.ref == 'refs/heads/release' }} runs-on: ubuntu-18.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Import GPG key to sign maven build artifacts - uses: crazy-max/ghaction-import-gpg@v3 + uses: crazy-max/ghaction-import-gpg@v4 with: - gpg-private-key: ${{ secrets.GPG_SECRET_KEYS }} + gpg_private_key: ${{ secrets.GPG_SECRET_KEYS }} passphrase: ${{ secrets.GPG_PASSPHRASE }} - git-user-signingkey: true - git-commit-gpgsign: true + git_user_signingkey: true + git_commit_gpgsign: true - name: Set up JDK 8 # with JDK 11 the maven-javadoc-plugin > 3.0.1 fails with "cannot find symbol org.elasticsearch.*" - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu java-version: 8 - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.m2/repository diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 42583d34..f7638d8e 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -1,6 +1,11 @@ name: Quality analysis -on: ["push", "pull_request"] +on: + push: + branches: + - master + - release + pull_request: env: MVN_CMD: "./mvnw --settings .cicd.settings.xml -e -B -V" @@ -8,17 +13,19 @@ env: jobs: code-analysis: runs-on: ubuntu-18.04 - + env: + COVERALLS_REPO_TOKEN_EXISTS: ${{ secrets.COVERALLS_REPO_TOKEN != '' }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up JDK 8 # build with JDK 8 because of issue https://github.com/trautonen/coveralls-maven-plugin/issues/112 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu java-version: 8 - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.m2/repository @@ -30,13 +37,9 @@ jobs: - name: Build and test with Maven run: $MVN_CMD install - name: Execute Maven coveralls Plugin + if: ${{ env.COVERALLS_REPO_TOKEN_EXISTS == 'true' }} env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} GITHUB_EVENT_NAME: ${{ github.event_name }} GITHUB_EVENT_PATH: ${{ github.event_path }} - run: $MVN_CMD coveralls:report -DrepoToken=$COVERALLS_REPO_TOKEN -DserviceName=GitHub-Actions - - name: Run codacy-coverage-reporter - uses: codacy/codacy-coverage-reporter-action@master - with: - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: elasticsearch-evolution-core/target/site/jacoco/jacoco.xml,spring-boot-starter-elasticsearch-evolution/target/site/jacoco/jacoco.xml + run: $MVN_CMD coveralls:report -DrepoToken=$COVERALLS_REPO_TOKEN -DserviceName=GitHub-Actions \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index 9cc84ea9..c1dd12f1 100644 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index be4fea70..db95c131 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,18 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/README.md b/README.md index b4a48f67..1f8fceb0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.senacor.elasticsearch.evolution/elasticsearch-evolution-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.senacor.elasticsearch.evolution/elasticsearch-evolution-core) [![Javadocs](https://www.javadoc.io/badge/com.senacor.elasticsearch.evolution/elasticsearch-evolution-core.svg)](https://www.javadoc.io/doc/com.senacor.elasticsearch.evolution/elasticsearch-evolution-core) [![Github build](https://github.com/senacor/elasticsearch-evolution/workflows/Maven%20Matrix%20Build/badge.svg?branch=master)](https://github.com/senacor/elasticsearch-evolution/actions?query=branch%3Amaster) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/a629ba3201104ecc81c6af7671b29b05)](https://www.codacy.com/app/xtermi2/elasticsearch-evolution?utm_source=github.com&utm_medium=referral&utm_content=senacor/elasticsearch-evolution&utm_campaign=Badge_Grade) [![codebeat badge](https://codebeat.co/badges/29dc74db-88e2-4b26-963b-14eb340ae275)](https://codebeat.co/projects/github-com-senacor-elasticsearch-evolution-master) [![Coverage Status](https://coveralls.io/repos/github/senacor/elasticsearch-evolution/badge.svg?branch=master)](https://coveralls.io/github/senacor/elasticsearch-evolution?branch=master) ![Lines of code](https://img.shields.io/tokei/lines/github/senacor/elasticsearch-evolution) @@ -16,25 +15,27 @@ ## 1 Evolve your Elasticsearch mapping easily and reliable across all your instances -Elasticsearch-Evolution executes versioned migration scripts reliable and persists the execution state in an internal Elasticsearch index. +Elasticsearch-Evolution executes versioned migration scripts reliable and persists the execution state in an internal Elasticsearch/Opensearch index. Successful executed migration scripts will not be executed again! ## 2 Features -- tested on Java 8, 9, 10, 11, 12, 13, 14, 15 and 16 -- runs on Spring-Boot 2.1, 2.2, 2.3 and 2.4 (and of course without Spring-Boot) -- runs on Elasticsearch version 7.5.0+ -- highly configurable (e.g. location(s) of your migration files, migration files format pattern) -- placeholder substitution in migration scripts -- easily extendable to your needs -- supports microservices / multiple parallel running instances via logical database locks -- ready to use default configuration -- line comments in migration files - -| Compatibility | Spring Boot | Elasticsearch | -|----------------------------------|------------------------------|----------------------| -| elasticsearch-evolution >= 0.3.0 | 2.1, 2.2, 2.3, 2.4 | 7.5.x and later | -| elasticsearch-evolution 0.2.x | 1.5, 2.0, 2.1, 2.2, 2.3, 2.4 | 7.0.x - 7.4.x, 6.8.x | +- tested on Java 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 and 18 +- runs on Spring-Boot 2.1, 2.2, 2.3, 2.4, 2.5 and 2.6 (and of course without Spring-Boot) +- runs on Elasticsearch version 7.5.x - 8.1.x +- runs on Opensearch version 1.x +- highly configurable (e.g. location(s) of your migration files, migration files format pattern) +- placeholder substitution in migration scripts +- easily extendable to your needs +- supports microservices / multiple parallel running instances via logical database locks +- ready to use default configuration +- line comments in migration files + +| Compatibility | Spring Boot | Elasticsearch | Opensearch | +|----------------------------------|------------------------------|----------------------|------------| +| elasticsearch-evolution >= 0.4.0 | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 | 7.5.x - 8.1.x | 1.x | +| elasticsearch-evolution >= 0.3.0 | 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 | 7.5.x - 7.17.x | | +| elasticsearch-evolution 0.2.x | 1.5, 2.0, 2.1, 2.2, 2.3, 2.4 | 7.0.x - 7.4.x, 6.8.x | | NOTE: When you run on Java 11 and using spring-boot 2.2 or 2.3 and you hit [this issue](https://github.com/ronmamo/reflections/issues/279), you have 2 options: @@ -55,7 +56,7 @@ First add the latest version of Elasticsearch-Evolution spring boot starter as a ``` -Elasticsearch-Evolution uses internally Elastics RestHighLevelClient and requires at minimum version 7.5.2. Spring boot could use a older version, depending on your Spring Boot version, so update it in your pom.xml: +Elasticsearch-Evolution uses internally Elastics `RestClient` and requires at minimum version 7.5.2. Spring boot could use an older version, depending on your Spring Boot version, so update it in your pom.xml: ```xml @@ -65,7 +66,7 @@ Elasticsearch-Evolution uses internally Elastics RestHighLevelClient and require Place your migration scripts in your application classpath at `es/evolution` -That's it. Elasticsearch-Evolution runs at application startup and expects your Elasticsearch at +That's it. Elasticsearch-Evolution runs at application startup and expects your Elasticsearch/Opensearch at ### 3.2 Quickstart with core library @@ -84,12 +85,11 @@ Place your migration scripts in your application classpath at `es/evolution` Create a `ElasticsearchEvolution` instance and execute the migration. ```java -// first create a Elastic RestHighLevelClient -RestHighLevelClient restHighLevelClient = new RestHighLevelClient( - RestClient.builder(HttpHost.create("http://localhost:9200"))); +// first create a Elastic RestClient +RestClient restClient = RestClient.builder(HttpHost.create("http://localhost:9200")).build(); // then create a ElasticsearchEvolution configuration and create a instance of ElasticsearchEvolution with that configuration ElasticsearchEvolution elasticsearchEvolution = ElasticsearchEvolution.configure() - .load(restHighLevelClient); + .load(restClient); // execute the migration elasticsearchEvolution.migrate(); ``` @@ -128,7 +128,7 @@ Content-Type: application/json } ``` -The first line defines the HTTP method `PUT` and the relative path to the Elasticsearch endpoint `_template/my_template` to create a new mapping template. +The first line defines the HTTP method `PUT` and the relative path to the Elasticsearch/Opensearch endpoint `_template/my_template` to create a new mapping template. Followed by a HTTP Header `Content-Type: application/json`. After a blank line the HTTP body is defined. @@ -136,15 +136,15 @@ The pattern is strongly oriented in ordinary HTTP requests and consist of 4 part 1. **The HTTP method (required)**. Supported HTTP methods are `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `OPTIONS` and `PATCH`. The First non-comment line must always start with a HTTP method. -2. **The path to the Elasticsearch endpoint to call (required)**. The path is separated by a _blank_ from the HTTP method. +2. **The path to the Elasticsearch/Opensearch endpoint to call (required)**. The path is separated by a _blank_ from the HTTP method. You can provide any query parameters like in a ordinary browser like this `/my_index_1/_doc/1?refresh=true&op_type=create` 3. **HTTP Header(s) (optional)**. All non-comment lines after the _HTTP method_ line will be interpreted as HTTP headers. Header name and content are separated by `:`. -4. **HTTP Body (optional)**. The HTTP Body is separated by a blank line and can contain any content you want to sent to Elasticsearch. +4. **HTTP Body (optional)**. The HTTP Body is separated by a blank line and can contain any content you want to sent to Elasticsearch/Opensearch. #### 4.1.1 Comments Elasticsearch-Evolution supports line-comments in its migration scripts. Every line starting with `#` or `//` will be interpreted as a comment-line. -Comment-lines are not send to Elasticsearch, they will be filtered by Elasticsearch-Evolution. +Comment-lines are not send to Elasticsearch/Opensearch, they will be filtered by Elasticsearch-Evolution. #### 4.1.2 Placeholders @@ -170,7 +170,7 @@ The filename has to follow a pattern: Elasticsearch-Evolution uses the version for ordering your scripts and enforces strict ordered execution of your scripts. Out-of-Order execution is not supported. Elasticsearch-Evolution interprets the version parts as Integers, so each version part must be between 1 (inclusive) and 2,147,483,647 (inclusive). -Here is and example which indicates the ordering: `1.0.1` < `1.1` < `1.2.1` < (`2.0.0` == `2`). +Here is an example which indicates the ordering: `1.0.1` < `1.1` < `1.2.1` < (`2.0.0` == `2`). In this example version `1.0.1` is the smallest version and is executed first, after that version `1.1`, `1.2.1` and in the end `2`. `2` is the same as `2.0` or `2.0.0` - so leading zeros will be trimed. @@ -195,7 +195,7 @@ Elasticsearch-Evolution can be configured to your needs: ### 5.1 Spring Boot -You can set the above configurations via Spring Boots default configuration way. Just use the prefix `spring.elasticsearch.evolution`. Here is a example `application.properties`: +You can set the above configurations via Spring Boots default configuration way. Just use the prefix `spring.elasticsearch.evolution`. Here is an example `application.properties`: ```properties spring.elasticsearch.evolution.locations[0]=classpath:es/migration @@ -209,9 +209,18 @@ spring.elasticsearch.evolution.historyIndex=es_evolution #### 5.1.1 Elasticsearch AutoConfiguration (since spring boot 2.1) -Since spring boot 2.1 AutoConfiguration for Elasticsearchs REST client is provided (see org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration). -You can configure the RestHighLevelClient, required for Elasticsearch-Evolution, just like that in your `application.properties`: +Since spring boot 2.1 AutoConfiguration for Elasticsearchs REST client is provided (see org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration). +You can configure the `RestClient`, required for Elasticsearch-Evolution, just like that in your `application.properties`: +##### 5.1.1.1 spring boot 2.6+ +```properties +spring.elasticsearch.uris[0]=https://example.com:9200 +spring.elasticsearch.username=my-user-name +spring.elasticsearch.password=my-secret-pw +``` + +##### 5.1.1.2 spring boot < 2.7 +NOTE: these config properties are deprecated since spring boot 2.6 and may be removed in 2.7! See spring-boot 2.6 [release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes#elasticsearch-property-consolidation). ```properties spring.elasticsearch.rest.uris[0]=https://example.com:9200 spring.elasticsearch.rest.username=my-user-name @@ -220,14 +229,14 @@ spring.elasticsearch.rest.password=my-secret-pw #### 5.1.2 Customize Elasticsearch-Evolutions AutoConfiguration -##### 5.1.2.1 Custom RestHighLevelClient +##### 5.1.2.1 Custom RestClient -Elasticsearch-Evolutions just needs a `RestHighLevelClient` as spring bean. -If you don't have spring boot 2.1 or later or you need a special `RestHighLevelClient` configuration e.g. to accept self signed certificates or disable hostname validation, you can provide a custom `RestHighLevelClient` like this: +Elasticsearch-Evolutions just needs a `RestClient` as spring bean. +If you don't have spring boot 2.1 or later or you need a special `RestClient` configuration e.g. to accept self signed certificates or disable hostname validation, you can provide a custom `RestClient` like this: ```java @Bean -public RestHighLevelClient restHighLevelClient() { +public RestClient restClient() { RestClientBuilder builder = RestClient.builder(HttpHost.create("https://localhost:9200")) .setHttpClientConfigCallback(httpClientBuilder -> { CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); @@ -243,13 +252,13 @@ public RestHighLevelClient restHighLevelClient() { return httpClientBuilder; } ); - return new RestHighLevelClient(builder); + return builder.build(); } ``` ##### 5.1.2.2 Custom ElasticsearchEvolutionInitializer -Maybe you want to provide a customised Initializer for Elasticsearch-Evolution e.g with another Order: +Maybe you want to provide a customised Initializer for Elasticsearch-Evolution e.g with another order: ```java @Bean @@ -277,13 +286,20 @@ ElasticsearchEvolution.configure() ## 6 changelog -### v0.3.3-SNAPSHOT +### v0.4.0-SNAPSHOT -- ... +- **breaking change**: drop `org.elasticsearch.client.RestHighLevelClient` and replace with `org.elasticsearch.client.RestClient` (LowLevelClient). This will drop the big transitive dependency `org.elasticsearch:elasticsearch` and opens compatibility to Elasticsearch 8 and OpenSearch. +- version updates (spring-boot 2.6.6) +- added spring boot 2.5 and 2.6 compatibility tests +- added java 17 and 18 compatibility tests +- added Elasticsearch 8.1, 8.0, 7.17, 7.16, 7.15, 7.14 and 7.13 compatibility tests +- added Opensearch 1.0, 1.1, 1.2 and 1.3 compatibility tests +- fixed issue [#114](https://github.com/senacor/elasticsearch-evolution/issues/114) ### v0.3.2 - fixed issue [#36](https://github.com/senacor/elasticsearch-evolution/issues/36) +- version updates (spring-boot 2.4.5) - added java 16 compatibility tests - added Elasticsearch 7.12 compatibility tests diff --git a/elasticsearch-evolution-core/lombok.config b/elasticsearch-evolution-core/lombok.config new file mode 100644 index 00000000..258aa9cf --- /dev/null +++ b/elasticsearch-evolution-core/lombok.config @@ -0,0 +1,4 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true +#required to deserialize a @Value @Builder annotated class with jackson without any other configuration +lombok.anyConstructor.addConstructorProperties = true diff --git a/elasticsearch-evolution-core/pom.xml b/elasticsearch-evolution-core/pom.xml index d4d203e1..3d32865b 100644 --- a/elasticsearch-evolution-core/pom.xml +++ b/elasticsearch-evolution-core/pom.xml @@ -6,7 +6,7 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.3.2 + 0.4.0 ../ elasticsearch-evolution-core @@ -24,9 +24,14 @@ + + org.elasticsearch.client + elasticsearch-rest-client + org.elasticsearch.client elasticsearch-rest-high-level-client + test org.testcontainers @@ -44,13 +49,28 @@ reflections + + com.fasterxml.jackson.core + jackson-databind + + + + org.projectlombok + lombok + provided + + + ch.qos.logback logback-classic test - - + + org.apache.logging.log4j + log4j-core + test + org.junit.jupiter junit-jupiter-params @@ -72,11 +92,6 @@ commons-io test - - com.fasterxml.jackson.core - jackson-databind - test - diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java index e9ac06f1..ef80d791 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java @@ -1,5 +1,7 @@ package com.senacor.elasticsearch.evolution.core; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.senacor.elasticsearch.evolution.core.api.MigrationException; import com.senacor.elasticsearch.evolution.core.api.config.ElasticsearchEvolutionConfig; import com.senacor.elasticsearch.evolution.core.api.migration.HistoryRepository; @@ -15,7 +17,7 @@ import com.senacor.elasticsearch.evolution.core.internal.model.migration.ParsedMigrationScript; import com.senacor.elasticsearch.evolution.core.internal.model.migration.RawMigrationScript; import org.apache.http.entity.ContentType; -import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.RestClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,7 +34,7 @@ *

To get started all you need to do is

*
  * ElasticsearchEvolution esEvolution = ElasticsearchEvolution.configure()
- *   .load(new RestHighLevelClient(RestClient.builder(HttpHost.create(esUrl))));
+ *   .load(RestClient.builder(HttpHost.create(esUrl)).build());
  * esEvolution.migrate();
  * 
*

@@ -44,7 +46,7 @@ public class ElasticsearchEvolution { private static final Logger logger = LoggerFactory.getLogger(ElasticsearchEvolution.class); private final ElasticsearchEvolutionConfig config; - private final RestHighLevelClient restHighLevelClient; + private final RestClient restClient; private final MigrationScriptReader migrationScriptReader; private final MigrationScriptParser migrationScriptParser; @@ -56,7 +58,7 @@ public class ElasticsearchEvolution { *

In its simplest form, this is how you configure Flyway with all defaults to get started:

*
      * ElasticsearchEvolution esEvolution = ElasticsearchEvolution.configure()
-     *     .load(new RestHighLevelClient(RestClient.builder(HttpHost.create(esUrl))));
+     *     .load(RestClient.builder(HttpHost.create(esUrl)).build());
      *  
*

After that you have a fully-configured ElasticsearchEvolution instance at your disposal which can be used to * invoke ElasticsearchEvolution functionality such as migrate().

@@ -71,20 +73,20 @@ public static ElasticsearchEvolutionConfig configure() { * Create ElasticsearchEvolution * * @param elasticsearchEvolutionConfig configuration - * @param restHighLevelClient REST client to interact with Elasticsearch + * @param restClient REST client to interact with Elasticsearch */ public ElasticsearchEvolution(ElasticsearchEvolutionConfig elasticsearchEvolutionConfig, - RestHighLevelClient restHighLevelClient) { + RestClient restClient) { this.config = requireNonNull(elasticsearchEvolutionConfig, "elasticsearchEvolutionConfig must not be null") .validate(); - this.restHighLevelClient = requireNonNull(restHighLevelClient, "restHighLevelClient must not be null"); + this.restClient = requireNonNull(restClient, "restClient must not be null"); this.migrationScriptReader = createMigrationScriptReader(); this.migrationScriptParser = createMigrationScriptParser(); this.migrationService = createMigrationService(); logger.info("Created ElasticsearchEvolution with config='{}' and client='{}'", - this.getConfig(), this.getRestHighLevelClient().getLowLevelClient().getNodes()); + this.getConfig(), this.getRestClient().getNodes()); } /** @@ -121,8 +123,14 @@ protected ElasticsearchEvolutionConfig getConfig() { return config; } - protected RestHighLevelClient getRestHighLevelClient() { - return restHighLevelClient; + protected RestClient getRestClient() { + return restClient; + } + + protected ObjectMapper createObjectMapper(){ + return new ObjectMapper() + // not all search response properties are mapped, so they must be ignored + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } protected MigrationScriptParser createMigrationScriptParser() { @@ -146,10 +154,11 @@ protected MigrationScriptReader createMigrationScriptReader() { protected HistoryRepository createHistoryRepository() { return new HistoryRepositoryImpl( - getRestHighLevelClient(), + getRestClient(), getConfig().getHistoryIndex(), new MigrationScriptProtocolMapper(), - getConfig().getHistoryMaxQuerySize()); + getConfig().getHistoryMaxQuerySize(), + createObjectMapper()); } protected MigrationService createMigrationService() { @@ -157,7 +166,7 @@ protected MigrationService createMigrationService() { createHistoryRepository(), 1_000, 10_000, - getRestHighLevelClient().getLowLevelClient(), + getRestClient(), ContentType.parse(getConfig().getDefaultContentType()), getConfig().getEncoding()); } diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java index b09df08e..03e410db 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java @@ -1,7 +1,7 @@ package com.senacor.elasticsearch.evolution.core.api.config; import com.senacor.elasticsearch.evolution.core.ElasticsearchEvolution; -import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.RestClient; import org.springframework.boot.context.properties.ConfigurationProperties; import java.nio.charset.Charset; @@ -86,11 +86,11 @@ public class ElasticsearchEvolutionConfig { /** * Loads this configuration into a new ElasticsearchEvolution instance. * - * @param restHighLevelClient REST client to interact with Elasticsearch + * @param restClient REST client to interact with Elasticsearch * @return The new fully-configured ElasticsearchEvolution instance. */ - public ElasticsearchEvolution load(RestHighLevelClient restHighLevelClient) { - return new ElasticsearchEvolution(this, restHighLevelClient); + public ElasticsearchEvolution load(RestClient restClient) { + return new ElasticsearchEvolution(this, restClient); } /** diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImpl.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImpl.java index 4d5ddef6..0fcf284b 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImpl.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImpl.java @@ -1,39 +1,28 @@ package com.senacor.elasticsearch.evolution.core.internal.migration.execution; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.senacor.elasticsearch.evolution.core.api.MigrationException; import com.senacor.elasticsearch.evolution.core.api.migration.HistoryRepository; import com.senacor.elasticsearch.evolution.core.internal.model.MigrationVersion; import com.senacor.elasticsearch.evolution.core.internal.model.dbhistory.MigrationScriptProtocol; -import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; -import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.index.IndexResponse; -import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.support.IndicesOptions; +import lombok.Value; +import org.apache.http.util.EntityUtils; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.client.core.CountRequest; -import org.elasticsearch.client.core.CountResponse; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.index.reindex.BulkByScrollResponse; -import org.elasticsearch.index.reindex.UpdateByQueryRequest; -import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.script.Script; -import org.elasticsearch.script.ScriptType; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.client.RestClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.math.BigDecimal; import java.time.OffsetDateTime; import java.util.*; +import java.util.stream.Collectors; import static com.senacor.elasticsearch.evolution.core.internal.utils.AssertionUtils.requireNotBlank; import static java.util.Objects.requireNonNull; -import static org.elasticsearch.client.RequestOptions.DEFAULT; /** * @author Andreas Keefer @@ -45,42 +34,44 @@ public class HistoryRepositoryImpl implements HistoryRepository { private static final MigrationVersion INTERNAL_VERSIONS = MigrationVersion.fromVersion("0"); static final String INDEX_TYPE_DOC = "_doc"; - private final RestHighLevelClient restHighLevelClient; + private final RestClient restClient; private final String historyIndex; private final MigrationScriptProtocolMapper migrationScriptProtocolMapper; private final int querySize; + private final ObjectMapper objectMapper; - public HistoryRepositoryImpl(RestHighLevelClient restHighLevelClient, + public HistoryRepositoryImpl(RestClient restClient, String historyIndex, MigrationScriptProtocolMapper migrationScriptProtocolMapper, - int querySize) { - this.restHighLevelClient = requireNonNull(restHighLevelClient, "restHighLevelClient must not be null"); + int querySize, + ObjectMapper objectMapper) { + this.restClient = requireNonNull(restClient, "restClient must not be null"); this.historyIndex = requireNotBlank(historyIndex, "historyIndex must not be blank: {}", historyIndex); this.migrationScriptProtocolMapper = requireNonNull(migrationScriptProtocolMapper, "migrationScriptProtocolMapper must not be null"); this.querySize = querySize; + this.objectMapper = objectMapper; } @Override public NavigableSet findAll() throws MigrationException { try { - SearchResponse searchResponse = restHighLevelClient.search( - new SearchRequest(historyIndex) - .source(new SearchSourceBuilder() - .size(querySize)) - .indicesOptions(IndicesOptions.lenientExpandOpen()), - DEFAULT); - logger.debug("findAll res: {}", searchResponse); - validateHttpStatus2xxOK(searchResponse.status(), "findAll"); + final Request findAllSearchRequest = new Request("POST", "/" + historyIndex + "/_search"); + findAllSearchRequest.addParameters(indicesOptions(IndexOptions.lenientExpandOpen())); + findAllSearchRequest.setJsonEntity("{\"size\":" + querySize + "}"); + final Response searchResponse = restClient.performRequest(findAllSearchRequest); + final String bodyAsString = EntityUtils.toString(searchResponse.getEntity()); + logger.debug("findAll res: {} (body={})", searchResponse, bodyAsString); + validateHttpStatusIs2xx(searchResponse, "findAll"); + + final SearchResponse body = objectMapper.readValue(bodyAsString, SearchResponse.class); // map and order - TreeSet res = new TreeSet<>(); - Arrays.stream(searchResponse.getHits().getHits()) - .map(SearchHit::getSourceAsMap) + return body.getHits().getHitList().stream() + .map(Hit::getSource) .map(migrationScriptProtocolMapper::mapFromMap) // filter protocols with 0 major version, because they are used internal .filter(protocol -> protocol.getVersion().isMajorNewerThan(INTERNAL_VERSIONS)) - .forEach(res::add); - return res; + .collect(Collectors.toCollection(TreeSet::new)); } catch (IOException e) { throw new MigrationException("findAll failed!", e); } @@ -89,14 +80,16 @@ public NavigableSet findAll() throws MigrationException @Override public void saveOrUpdate(MigrationScriptProtocol migrationScriptProtocol) throws MigrationException { try { - HashMap source = migrationScriptProtocolMapper.mapToMap(migrationScriptProtocol); - IndexResponse res = restHighLevelClient.index( - new IndexRequest(historyIndex) - .id(requireNonNull(migrationScriptProtocol.getVersion(), "migrationScriptProtocol.version must not be null").getVersion()) - .source(source), - DEFAULT); - logger.debug("saveOrUpdate res: {}", res); - validateHttpStatus2xxOK(res.status(), "saveOrUpdate"); + final String id = requireNonNull(migrationScriptProtocol.getVersion(), "migrationScriptProtocol.version must not be null").getVersion(); + final Request indexRequest = new Request("PUT", "/" + historyIndex + "/_doc/" + id); + final Map source = migrationScriptProtocolMapper.mapToMap(migrationScriptProtocol); + indexRequest.setJsonEntity(objectMapper.writeValueAsString(source)); + final Response res = restClient.performRequest(indexRequest); + + if (logger.isDebugEnabled()) { + logger.debug("saveOrUpdate res: {} (body={})", res, EntityUtils.toString(res.getEntity())); + } + validateHttpStatusIs2xx(res, "saveOrUpdate"); } catch (IOException e) { throw new MigrationException(String.format("saveOrUpdate of '%s' failed!", migrationScriptProtocol), e); } @@ -106,32 +99,39 @@ public void saveOrUpdate(MigrationScriptProtocol migrationScriptProtocol) throws public boolean isLocked() throws MigrationException { try { refresh(historyIndex); - CountRequest countRequest = new CountRequest(historyIndex) - .query(QueryBuilders.termQuery(MigrationScriptProtocolMapper.LOCKED_FIELD_NAME, true)) - .indicesOptions(IndicesOptions.lenientExpandOpen()); - CountResponse countResponse = restHighLevelClient.count(countRequest, DEFAULT); - validateHttpStatus2xxOK(countResponse.status(), "isLocked"); - if (countResponse.getCount() == 0L) { + final String countQuery = "{\"query\":{\"term\":{\"" + MigrationScriptProtocolMapper.LOCKED_FIELD_NAME + "\":{\"value\":true}}}}"; + final long count = executeCountRequest(Optional.of(countQuery)); + + if (count == 0L) { logger.debug("index '{}' is not locked: no locked documents in index.", historyIndex); return false; } - logger.debug("index '{}' is locked: {} locked documents found.", historyIndex, countResponse.getCount()); + + logger.debug("index '{}' is locked: {} locked documents found.", historyIndex, count); return true; } catch (IOException e) { throw new MigrationException("isLocked check failed!", e); } } + private long executeCountRequest(Optional countQuery) throws IOException { + final Request countRequest = new Request("GET", "/" + historyIndex + "/_count"); + countRequest.addParameters(indicesOptions(IndexOptions.lenientExpandOpen())); + countQuery.ifPresent(countRequest::setJsonEntity); + final Response countResponse = restClient.performRequest(countRequest); + + validateHttpStatusIs2xx(countResponse, "isLocked"); + + final JsonNode countResBody = objectMapper.readTree(countResponse.getEntity().getContent()); + return countResBody.get("count").asLong(); + } + @Override public boolean lock() { try { - CountRequest countAllReq = new CountRequest(historyIndex) - .indicesOptions(IndicesOptions.lenientExpandOpen()); - CountResponse countAllRes = restHighLevelClient.count(countAllReq, DEFAULT); - validateHttpStatus2xxOK(countAllRes.status(), "lock.count"); - - if (countAllRes.getCount() == 0L) { + final long countAll = executeCountRequest(Optional.empty()); + if (countAll == 0L) { saveOrUpdate(new MigrationScriptProtocol() .setVersion(INTERNAL_LOCK_VERSION) .setScriptName("-") @@ -143,8 +143,7 @@ public boolean lock() { .setIndexName(historyIndex) .setLocked(true)); } else { - BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.updateByQuery(createLockQuery(true), DEFAULT); - logger.debug("lock res: {}", bulkByScrollResponse); + executeLockRequest(true, "lock"); } return true; } catch (IOException e) { @@ -157,20 +156,24 @@ public boolean lock() { public boolean unlock() { try { refresh(historyIndex); - BulkByScrollResponse deleteInternalLockRes = restHighLevelClient.updateByQuery( - new UpdateByQueryRequest(historyIndex) - .setRefresh(true) - .setIndicesOptions(IndicesOptions.lenientExpandOpen()) - .setQuery(QueryBuilders.termQuery(MigrationScriptProtocolMapper.VERSION_FIELD_NAME, INTERNAL_LOCK_VERSION)) - .setScript(new Script(ScriptType.INLINE, - "painless", - "ctx.op = \"delete\"", - Collections.emptyMap())), - DEFAULT); - logger.debug("unlock.deleteLockEntry res: {}", deleteInternalLockRes); - - BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.updateByQuery(createLockQuery(false), DEFAULT); - logger.debug("unlock.removeLock res: {}", bulkByScrollResponse); + + final Request updateByQueryRequest = new Request("POST", "/" + historyIndex + "/_update_by_query"); + updateByQueryRequest.addParameters(indicesOptions(IndexOptions.lenientExpandOpen())); + updateByQueryRequest.addParameter("requests_per_second", "-1"); + updateByQueryRequest.addParameter("refresh", "true"); + updateByQueryRequest.setJsonEntity("{\"script\":{" + + "\"source\":\"ctx.op = \\\"delete\\\"\"," + + "\"lang\":\"painless\"}," + + "\"size\":1000," + + "\"query\":{\"term\":{\"" + MigrationScriptProtocolMapper.VERSION_FIELD_NAME + "\":{\"value\":\"" + INTERNAL_LOCK_VERSION + "\"}}}}"); + + final Response deleteInternalLockRes = restClient.performRequest(updateByQueryRequest); + + if (logger.isDebugEnabled()) { + logger.debug("unlock.deleteLockEntry res: {} (body={})", deleteInternalLockRes, EntityUtils.toString(deleteInternalLockRes.getEntity())); + } + + executeLockRequest(false, "unlock.removeLock"); return true; } catch (IOException e) { logger.warn("unlock failed", e); @@ -178,21 +181,30 @@ public boolean unlock() { } } - private UpdateByQueryRequest createLockQuery(boolean lock) { - return new UpdateByQueryRequest(historyIndex) - .setRefresh(true) - .setIndicesOptions(IndicesOptions.lenientExpandOpen()) - .setQuery(QueryBuilders.termQuery(MigrationScriptProtocolMapper.LOCKED_FIELD_NAME, !lock)) - .setScript(new Script(ScriptType.INLINE, - "painless", - "ctx._source." + MigrationScriptProtocolMapper.LOCKED_FIELD_NAME + " = params.lock", - Collections.singletonMap("lock", lock))); + private void executeLockRequest(boolean lock, String debugContext) throws IOException { + final Request updateByQueryRequest = new Request("POST", "/" + historyIndex + "/_update_by_query"); + updateByQueryRequest.addParameters(indicesOptions(IndexOptions.lenientExpandOpen())); + updateByQueryRequest.addParameter("requests_per_second", "-1"); + updateByQueryRequest.addParameter("refresh", "true"); + updateByQueryRequest.setJsonEntity("{\"script\":" + + "{\"source\":\"ctx._source." + MigrationScriptProtocolMapper.LOCKED_FIELD_NAME + " = params.lock\"," + + "\"lang\":\"painless\"," + + "\"params\":{\"lock\":" + lock + "}" + + "}," + + "\"size\":1000," + + "\"query\":{\"term\":{\"" + MigrationScriptProtocolMapper.LOCKED_FIELD_NAME + "\":{\"value\":" + !lock + "}}}}"); + + final Response updateByQueryResponse = restClient.performRequest(updateByQueryRequest); + + if (logger.isDebugEnabled()) { + logger.debug("{} res: {} (body={})", debugContext, updateByQueryResponse, EntityUtils.toString(updateByQueryResponse.getEntity())); + } } @Override public boolean createIndexIfAbsent() throws MigrationException { try { - Response existsRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("HEAD", "/" + historyIndex)); + Response existsRes = restClient.performRequest(new Request("HEAD", "/" + historyIndex)); boolean exists = 200 == existsRes.getStatusLine().getStatusCode(); if (exists) { logger.debug("Elasticsearch-Evolution history index '{}' already exists.", historyIndex); @@ -201,8 +213,8 @@ public boolean createIndexIfAbsent() throws MigrationException { logger.debug("Elasticsearch-Evolution history index '{}' does not yet exists. Res={}", historyIndex, existsRes); // create index - Response createRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("PUT", "/" + historyIndex)); - if (existsRes.getStatusLine().getStatusCode() < 200 || createRes.getStatusLine().getStatusCode() > 299) { + Response createRes = restClient.performRequest(new Request("PUT", "/" + historyIndex)); + if (hasNotStatusCode2xx(createRes)) { throw new IllegalStateException("Could not create Elasticsearch-Evolution history index '" + historyIndex + "'. Create res=" + createRes); } logger.debug("created Elasticsearch-Evolution history index '{}'", historyIndex); @@ -212,16 +224,20 @@ public boolean createIndexIfAbsent() throws MigrationException { } } - /** - * validates that HTTP status code is a 2xx code. - * - * @param status status - * @param description is used in case of a non 2xx status code in the exception message. - * @throws MigrationException when the given status code is not a 2xx code. - */ - void validateHttpStatus2xxOK(RestStatus status, String description) throws MigrationException { - int statusCode = status.getStatus(); - if (statusCode < 200 || statusCode >= 300) { + private boolean hasNotStatusCode2xx(Response response) { + return isNotStatusCode2xx(response.getStatusLine().getStatusCode()); + } + + private boolean isNotStatusCode2xx(int statusCode) { + return statusCode < 200 || statusCode > 299; + } + + private void validateHttpStatusIs2xx(Response response, String description) throws MigrationException { + validateHttpStatusIs2xx(response.getStatusLine().getStatusCode(), description + " (" + response.getStatusLine().getReasonPhrase() + ")"); + } + + void validateHttpStatusIs2xx(int statusCode, String description) throws MigrationException { + if (isNotStatusCode2xx(statusCode)) { throw new MigrationException(String.format("%s - response status is not OK: %s", description, statusCode)); } } @@ -232,13 +248,131 @@ void validateHttpStatus2xxOK(RestStatus status, String description) throws Migra */ void refresh(String... indices) { try { - RefreshResponse res = restHighLevelClient.indices().refresh( - new RefreshRequest(indices) - .indicesOptions(IndicesOptions.lenientExpandOpen()) - , DEFAULT); - validateHttpStatus2xxOK(res.getStatus(), "refresh"); + final Request refreshRequest = new Request("GET", "/" + expandIndicesForUrl(indices) + "/_refresh"); + refreshRequest.addParameters(indicesOptions(IndexOptions.lenientExpandOpen())); + + Response res = restClient.performRequest(refreshRequest); + + validateHttpStatusIs2xx(res, "refresh"); } catch (IOException e) { throw new MigrationException("refresh failed!", e); } } -} + + private String expandIndicesForUrl(String... indices) { + return String.join(",", indices); + } + + private Map indicesOptions(IndexOptions indicesOptions) { + Map nameValuePairs = new HashMap<>(); + nameValuePairs.put("ignore_unavailable", Boolean.toString(indicesOptions.ignoreUnavailable())); + nameValuePairs.put("allow_no_indices", Boolean.toString(indicesOptions.allowNoIndices())); + nameValuePairs.put("ignore_throttled", Boolean.toString(indicesOptions.ignoreThrottled())); + String expandWildcards; + if (!indicesOptions.expandWildcardsOpen() && !indicesOptions.expandWildcardsClosed()) { + expandWildcards = "none"; + } else { + StringJoiner joiner = new StringJoiner(","); + if (indicesOptions.expandWildcardsOpen()) { + joiner.add("open"); + } + if (indicesOptions.expandWildcardsClosed()) { + joiner.add("closed"); + } + expandWildcards = joiner.toString(); + } + nameValuePairs.put("expand_wildcards", expandWildcards); + return nameValuePairs; + } + + /** + * simple replacement for {@link org.elasticsearch.action.support.IndicesOptions} + */ + private static final class IndexOptions { + + private static final IndexOptions LENIENT_EXPAND_OPEN = new IndexOptions( + true, + true, + false, + true, + false); + + private final boolean ignoreUnavailable; + private final boolean allowNoIndices; + private final boolean ignoreThrottled; + private final boolean expandWildcardsOpen; + private final boolean expandWildcardsClosed; + + public IndexOptions(boolean ignoreUnavailable, + boolean allowNoIndices, + boolean ignoreThrottled, + boolean expandWildcardsOpen, + boolean expandWildcardsClosed) { + this.ignoreUnavailable = ignoreUnavailable; + this.allowNoIndices = allowNoIndices; + this.ignoreThrottled = ignoreThrottled; + this.expandWildcardsOpen = expandWildcardsOpen; + this.expandWildcardsClosed = expandWildcardsClosed; + } + + public static IndexOptions lenientExpandOpen() { + return LENIENT_EXPAND_OPEN; + } + + public boolean ignoreUnavailable() { + return ignoreUnavailable; + } + + public boolean allowNoIndices() { + return allowNoIndices; + } + + public boolean ignoreThrottled() { + return ignoreThrottled; + } + + public boolean expandWildcardsOpen() { + return expandWildcardsOpen; + } + + public boolean expandWildcardsClosed() { + return expandWildcardsClosed; + } + } + + @Value + static class SearchResponse { + long took; + @JsonProperty("timed_out") + boolean timedOut; + Hits hits; + } + + @Value + static class Hits { + TotalHits total; + @JsonProperty("max_score") + BigDecimal maxScore; + @JsonProperty("hits") + List hitList; + } + + @Value + static class TotalHits { + long value; + /** + * Values of relation: + * - "eq" = Accurate + * - "gte" = "Lower bound, including returned documents" + */ + String relation; + } + + @Value + static class Hit { + @JsonProperty("_id") + String id; + @JsonProperty("_source") + Map source; + } +} \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapper.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapper.java index 0850091c..0b26dd3f 100644 --- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapper.java +++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapper.java @@ -23,7 +23,7 @@ public class MigrationScriptProtocolMapper { public static final String INDEX_NAME_FIELD_NAME = "indexName"; public static final String SCRIPT_NAME_FIELD_NAME = "scriptName"; - public HashMap mapToMap(MigrationScriptProtocol migrationScriptProtocol) { + public Map mapToMap(MigrationScriptProtocol migrationScriptProtocol) { HashMap res = new HashMap<>(10); res.put(LOCKED_FIELD_NAME, migrationScriptProtocol.isLocked()); res.put(CHECKSUM_FIELD_NAME, migrationScriptProtocol.getChecksum()); @@ -44,31 +44,31 @@ public HashMap mapToMap(MigrationScriptProtocol migrationScriptP public MigrationScriptProtocol mapFromMap(Map mapData) { MigrationScriptProtocol protocol = new MigrationScriptProtocol(); Optional.ofNullable(mapData.get(LOCKED_FIELD_NAME)) - .map(data -> protocol.setLocked((Boolean) data)); + .ifPresent(data -> protocol.setLocked((Boolean) data)); Optional.ofNullable(mapData.get(CHECKSUM_FIELD_NAME)) - .map(data -> protocol.setChecksum((Integer) data)); + .ifPresent(data -> protocol.setChecksum((Integer) data)); Optional.ofNullable(mapData.get(DESCRIPTION_FIELD_NAME)) - .map(data -> protocol.setDescription((String) data)); + .ifPresent(data -> protocol.setDescription((String) data)); Optional.ofNullable(mapData.get(EXECUTION_RUNTIME_IN_MILLIS_FIELD_NAME)) - .map(data -> protocol.setExecutionRuntimeInMillis((Integer) data)); + .ifPresent(data -> protocol.setExecutionRuntimeInMillis((Integer) data)); Optional.ofNullable(mapData.get(EXECUTION_TIMESTAMP_FIELD_NAME)) - .map(data -> protocol.setExecutionTimestamp(OffsetDateTime.parse((CharSequence) data, DateTimeFormatter.ISO_OFFSET_DATE_TIME))); + .ifPresent(data -> protocol.setExecutionTimestamp(OffsetDateTime.parse((CharSequence) data, DateTimeFormatter.ISO_OFFSET_DATE_TIME))); Optional.ofNullable(mapData.get(SUCCESS_FIELD_NAME)) - .map(data -> protocol.setSuccess((Boolean) data)); + .ifPresent(data -> protocol.setSuccess((Boolean) data)); Optional.ofNullable(mapData.get(VERSION_FIELD_NAME)) - .map(data -> protocol.setVersion((String) data)); + .ifPresent(data -> protocol.setVersion((String) data)); Optional.ofNullable(mapData.get(INDEX_NAME_FIELD_NAME)) - .map(data -> protocol.setIndexName((String) data)); + .ifPresent(data -> protocol.setIndexName((String) data)); Optional.ofNullable(mapData.get(SCRIPT_NAME_FIELD_NAME)) - .map(data -> protocol.setScriptName((String) data)); + .ifPresent(data -> protocol.setScriptName((String) data)); return protocol; } diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java index 2c33c8e6..4c6c4627 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java @@ -1,6 +1,7 @@ package com.senacor.elasticsearch.evolution.core; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.senacor.elasticsearch.evolution.core.api.MigrationException; import com.senacor.elasticsearch.evolution.core.api.config.ElasticsearchEvolutionConfig; @@ -44,17 +45,19 @@ class ElasticsearchEvolutionIT { private static final Logger logger = LoggerFactory.getLogger(ElasticsearchEvolutionIT.class); private HistoryRepository historyRepository; + private final ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void migrate_OK(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { + void migrate_OK(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { ElasticsearchEvolutionConfig elasticsearchEvolutionConfig = ElasticsearchEvolution.configure() .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_OK")); String historyIndex = elasticsearchEvolutionConfig.getHistoryIndex(); - historyRepository = new HistoryRepositoryImpl(restHighLevelClient, historyIndex, new MigrationScriptProtocolMapper(), 1000); + historyRepository = new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), historyIndex, new MigrationScriptProtocolMapper(), 1000, objectMapper); ElasticsearchEvolution underTest = elasticsearchEvolutionConfig - .load(restHighLevelClient); + .load(restHighLevelClient.getLowLevelClient()); assertSoftly(softly -> { softly.assertThat(underTest.migrate()) @@ -117,16 +120,16 @@ void migrate_OK(String esVersion, EsUtils esUtils, RestHighLevelClient restHighL .isEqualTo(3); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void migrate_failed_then_fixed_script_and_re_execute(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void migrate_failed_then_fixed_script_and_re_execute(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { ElasticsearchEvolutionConfig elasticsearchEvolutionConfig = ElasticsearchEvolution.configure() .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_failed_step1")); - historyRepository = new HistoryRepositoryImpl(restHighLevelClient, elasticsearchEvolutionConfig.getHistoryIndex(), new MigrationScriptProtocolMapper(), 1000); + historyRepository = new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), elasticsearchEvolutionConfig.getHistoryIndex(), new MigrationScriptProtocolMapper(), 1000, objectMapper); assertSoftly(softly -> { - softly.assertThatThrownBy(() -> elasticsearchEvolutionConfig.load(restHighLevelClient).migrate()) + softly.assertThatThrownBy(() -> elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate()) .isInstanceOf(MigrationException.class) .hasMessage("execution of script 'FileNameInfoImpl{version=1.1, description='addDocument', scriptName='V001.01__addDocument.http'}' failed"); NavigableSet protocols = historyRepository.findAll(); @@ -144,7 +147,7 @@ void migrate_failed_then_fixed_script_and_re_execute(String esVersion, EsUtils e elasticsearchEvolutionConfig.setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_failed_step2_fixed")); assertSoftly(softly -> { - softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient).migrate()) + softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate()) .as("# of successful executed scripts") .isEqualTo(1); NavigableSet protocols = historyRepository.findAll(); diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionTest.java index 98067eee..b0634621 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionTest.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionTest.java @@ -3,22 +3,19 @@ import com.senacor.elasticsearch.evolution.core.api.MigrationException; import com.senacor.elasticsearch.evolution.core.test.MockitoExtension; import org.apache.http.HttpHost; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.client.*; -import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.search.SearchHit; +import org.elasticsearch.client.Node; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestHighLevelClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.Mockito; import java.io.IOException; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.*; -import static org.elasticsearch.client.RequestOptions.DEFAULT; import static org.mockito.Answers.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.*; @@ -40,44 +37,6 @@ void setUp() { .thenReturn(singletonList(new Node(HttpHost.create("http://localhost:9200")))); } - @Test - void migrate_Failed() throws IOException { - String indexName = "es_evolution"; - ElasticsearchEvolution underTest = ElasticsearchEvolution.configure() - .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_OK")) - .setHistoryIndex(indexName) - .load(restHighLevelClient); - when(restHighLevelClient.indices().refresh(any(), eq(DEFAULT)).getStatus()) - .thenReturn(RestStatus.OK); - Response existsMock = mock(Response.class, Mockito.RETURNS_DEEP_STUBS); - when(restHighLevelClient.getLowLevelClient().performRequest(new Request("HEAD", "/" + indexName))) - .thenReturn(existsMock); - when(existsMock.getStatusLine().getStatusCode()) - .thenReturn(200); - when(restHighLevelClient.count(any(), eq(DEFAULT)).status()) - .thenReturn(RestStatus.OK); - when(restHighLevelClient.index(any(), eq(DEFAULT)).status()) - .thenReturn(RestStatus.OK); - SearchResponse searchResponse = restHighLevelClient.search(any(), eq(DEFAULT)); - when(searchResponse.getHits().getHits()) - .thenReturn(new SearchHit[0]); - when(searchResponse.status()) - .thenReturn(RestStatus.OK); - - assertThatThrownBy(underTest::migrate) - .isInstanceOf(MigrationException.class) - .hasMessageStartingWith("execution of script 'FileNameInfoImpl{version=1, description='createTemplateWithIndexMapping', scriptName='V001.00__createTemplateWithIndexMapping.http'}' failed with HTTP status 0: "); - - InOrder order = inOrder(restHighLevelClient, restClient); - order.verify(restHighLevelClient, times(3)).getLowLevelClient(); - order.verify(restClient).getNodes(); - order.verify(restClient).performRequest(any()); - order.verify(restHighLevelClient).index(any(), eq(DEFAULT)); - order.verify(restHighLevelClient).indices(); - order.verify(restHighLevelClient, times(2)).updateByQuery(any(), eq(DEFAULT)); - order.verifyNoMoreInteractions(); - } - @Test void migrate_historyMaxQuerySizeToLow() throws IOException { String indexName = "es_evolution"; @@ -86,14 +45,14 @@ void migrate_historyMaxQuerySizeToLow() throws IOException { .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_OK")) .setHistoryIndex(indexName) .setHistoryMaxQuerySize(historyMaxQuerySize) - .load(restHighLevelClient); + .load(restHighLevelClient.getLowLevelClient()); assertThatThrownBy(underTest::migrate) .isInstanceOf(MigrationException.class) .hasMessage("configured historyMaxQuerySize of '%s' is to low for the number of migration scripts of '%s'", historyMaxQuerySize, 7); InOrder order = inOrder(restHighLevelClient, restClient); - order.verify(restHighLevelClient, times(3)).getLowLevelClient(); + order.verify(restHighLevelClient, times(2)).getLowLevelClient(); order.verify(restClient).getNodes(); order.verifyNoMoreInteractions(); } @@ -102,7 +61,7 @@ void migrate_historyMaxQuerySizeToLow() throws IOException { void migrate_empty_location() { ElasticsearchEvolution underTest = ElasticsearchEvolution.configure() .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/empty_location")) - .load(restHighLevelClient); + .load(restHighLevelClient.getLowLevelClient()); assertThatCode(underTest::migrate) .doesNotThrowAnyException(); @@ -112,7 +71,7 @@ void migrate_empty_location() { void migrate_non_existing_location() { ElasticsearchEvolution underTest = ElasticsearchEvolution.configure() .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/does_not_exist")) - .load(restHighLevelClient); + .load(restHighLevelClient.getLowLevelClient()); assertThatCode(underTest::migrate) .doesNotThrowAnyException(); @@ -122,13 +81,13 @@ void migrate_non_existing_location() { void elasticsearchEvolutionIsNotEnabled() { int migrations = ElasticsearchEvolution.configure() .setEnabled(false) - .load(restHighLevelClient) + .load(restHighLevelClient.getLowLevelClient()) .migrate(); - assertThat(migrations).isEqualTo(0); + assertThat(migrations).isZero(); InOrder order = inOrder(restHighLevelClient, restClient); - order.verify(restHighLevelClient, times(3)).getLowLevelClient(); + order.verify(restHighLevelClient, times(2)).getLowLevelClient(); order.verify(restClient).getNodes(); order.verifyNoMoreInteractions(); } diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplIT.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplIT.java index e28e76e8..cff6c62f 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplIT.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplIT.java @@ -1,5 +1,7 @@ package com.senacor.elasticsearch.evolution.core.internal.migration.execution; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.senacor.elasticsearch.evolution.core.internal.model.MigrationVersion; import com.senacor.elasticsearch.evolution.core.internal.model.dbhistory.MigrationScriptProtocol; import com.senacor.elasticsearch.evolution.core.test.EmbeddedElasticsearchExtension; @@ -8,7 +10,6 @@ import org.apache.commons.lang3.RandomStringUtils; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.RestHighLevelClient; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,7 +26,6 @@ import java.util.List; import java.util.NavigableSet; -import static com.senacor.elasticsearch.evolution.core.internal.migration.execution.HistoryRepositoryImpl.INDEX_TYPE_DOC; import static com.senacor.elasticsearch.evolution.core.internal.migration.execution.MigrationScriptProtocolMapper.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -42,9 +42,30 @@ class HistoryRepositoryImplIT { @Nested class findAll { - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void doesNotReturnProtocolsWithMajorVersion0(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void can_handle_empty_search_result(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); + underTest.refresh(INDEX); + + NavigableSet all = underTest.findAll(); + + assertThat(all).isEmpty(); + } + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(ElasticsearchArgumentsProvider.class) + void can_handle_when_index_does_not_exist(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); + + NavigableSet all = underTest.findAll(); + + assertThat(all).isEmpty(); + } + + @ParameterizedTest(name = "{0}") + @ArgumentsSource(ElasticsearchArgumentsProvider.class) + void doesNotReturnProtocolsWithMajorVersion0(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.saveOrUpdate(new MigrationScriptProtocol().setVersion("0.1")); underTest.saveOrUpdate(new MigrationScriptProtocol().setVersion("1.0")); @@ -56,9 +77,9 @@ void doesNotReturnProtocolsWithMajorVersion0(String esVersion, EsUtils esUtils, assertThat(all.first().getVersion()).isEqualTo(MigrationVersion.fromVersion("1.0")); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void returnsProtocolsInVersionOrder(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void returnsProtocolsInVersionOrder(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.saveOrUpdate(new MigrationScriptProtocol().setVersion("1.1")); underTest.saveOrUpdate(new MigrationScriptProtocol().setVersion("2.0")); @@ -89,9 +110,9 @@ void returnsProtocolsInVersionOrder(String esVersion, EsUtils esUtils, RestHighL assertThat(all.last().getVersion()).isEqualTo(MigrationVersion.fromVersion("2.0")); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void returnsFullProtocol(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void returnsFullProtocol(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); OffsetDateTime executionTimestamp = OffsetDateTime.of(2019, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); MigrationScriptProtocol protocol = new MigrationScriptProtocol() @@ -126,9 +147,9 @@ void returnsFullProtocol(String esVersion, EsUtils esUtils, RestHighLevelClient @Nested class saveOrUpdate { - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void saveFullDocument(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { + void saveFullDocument(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); OffsetDateTime now = OffsetDateTime.now(); @@ -173,9 +194,9 @@ void saveFullDocument(String esVersion, EsUtils esUtils, RestHighLevelClient res .containsEntry(SUCCESS_FIELD_NAME, true); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void save2DocumentsWithDifferentVersions(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { + void save2DocumentsWithDifferentVersions(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.saveOrUpdate(new MigrationScriptProtocol() @@ -188,9 +209,9 @@ void save2DocumentsWithDifferentVersions(String esVersion, EsUtils esUtils, Rest assertThat(res).hasSize(2); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void updateDocument(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { + void updateDocument(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.saveOrUpdate(new MigrationScriptProtocol() @@ -214,9 +235,9 @@ void updateDocument(String esVersion, EsUtils esUtils, RestHighLevelClient restH @Nested class isLocked { - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void emptyIndex_IsNotLocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void emptyIndex_IsNotLocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.createIndexIfAbsent(); @@ -226,9 +247,9 @@ void emptyIndex_IsNotLocked(String esVersion, EsUtils esUtils, RestHighLevelClie .isFalse(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void noIndex_IsNotLocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void noIndex_IsNotLocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); esUtils.refreshIndices(); @@ -237,9 +258,9 @@ void noIndex_IsNotLocked(String esVersion, EsUtils esUtils, RestHighLevelClient .isFalse(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void existingDocuments_IsNotLocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { + void existingDocuments_IsNotLocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.createIndexIfAbsent(); indexDocumentWithLock(false, esUtils, restHighLevelClient); @@ -250,9 +271,9 @@ void existingDocuments_IsNotLocked(String esVersion, EsUtils esUtils, RestHighLe .isFalse(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void existingDocuments_IsLocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { + void existingDocuments_IsLocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.createIndexIfAbsent(); indexDocumentWithLock(true, esUtils, restHighLevelClient); @@ -267,9 +288,9 @@ void existingDocuments_IsLocked(String esVersion, EsUtils esUtils, RestHighLevel @Nested class lock { - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void noDocumentsInIndex(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void noDocumentsInIndex(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.createIndexIfAbsent(); esUtils.refreshIndices(); @@ -280,9 +301,9 @@ void noDocumentsInIndex(String esVersion, EsUtils esUtils, RestHighLevelClient r assertThat(underTest.isLocked()).isTrue(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void indexDoesNotExist(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void indexDoesNotExist(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); assertThat(underTest.lock()).isTrue(); @@ -291,9 +312,9 @@ void indexDoesNotExist(String esVersion, EsUtils esUtils, RestHighLevelClient re assertThat(underTest.isLocked()).isTrue(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void allExistingLockedDocumentsStayLocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { + void allExistingLockedDocumentsStayLocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); indexDocumentWithLock(true, esUtils, restHighLevelClient); @@ -308,9 +329,9 @@ void allExistingLockedDocumentsStayLocked(String esVersion, EsUtils esUtils, Res assertThat(underTest.isLocked()).isTrue(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void allExistingUnlockedDocumentsGetsLocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { + void allExistingUnlockedDocumentsGetsLocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); indexDocumentWithLock(false, esUtils, restHighLevelClient); @@ -328,9 +349,9 @@ void allExistingUnlockedDocumentsGetsLocked(String esVersion, EsUtils esUtils, R @Nested class unlock { - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void noDocumentsInIndex(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void noDocumentsInIndex(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.createIndexIfAbsent(); esUtils.refreshIndices(); @@ -341,9 +362,9 @@ void noDocumentsInIndex(String esVersion, EsUtils esUtils, RestHighLevelClient r assertThat(underTest.isLocked()).isFalse(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void indexDoesNotExist(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void indexDoesNotExist(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); assertThat(underTest.unlock()).isTrue(); @@ -352,9 +373,9 @@ void indexDoesNotExist(String esVersion, EsUtils esUtils, RestHighLevelClient re assertThat(underTest.isLocked()).isFalse(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void allExistingLockedDocumentsGetsUnlocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { + void allExistingLockedDocumentsGetsUnlocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); underTest.lock(); indexDocumentWithLock(true, esUtils, restHighLevelClient); @@ -370,9 +391,9 @@ void allExistingLockedDocumentsGetsUnlocked(String esVersion, EsUtils esUtils, R assertThat(underTest.isLocked()).isFalse(); } - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void allExistingNonLockedDocumentsStayUnlocked(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { + void allExistingNonLockedDocumentsStayUnlocked(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); indexDocumentWithLock(false, esUtils, restHighLevelClient); esUtils.refreshIndices(); @@ -389,9 +410,9 @@ void allExistingNonLockedDocumentsStayUnlocked(String esVersion, EsUtils esUtils @Nested class createIndexIfAbsent { - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void indexDoesNotExistsYet_indexWillBeCreated(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { + void indexDoesNotExistsYet_indexWillBeCreated(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) { HistoryRepositoryImpl underTest = createHistoryRepositoryImpl(restHighLevelClient); assertThat(underTest.createIndexIfAbsent()).as("new index created").isTrue(); @@ -406,18 +427,15 @@ void indexDoesNotExistsYet_indexWillBeCreated(String esVersion, EsUtils esUtils, private void indexDocumentWithLock(boolean locked, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws Exception { HashMap source = new HashMap<>(); source.put(LOCKED_FIELD_NAME, locked); - - restHighLevelClient.index( - new IndexRequest(INDEX) - .id(RandomStringUtils.randomNumeric(5)) - .source(source), - DEFAULT); + esUtils.indexDocument(INDEX, RandomStringUtils.randomNumeric(5), source); esUtils.refreshIndices(); logger.debug("all documents in index '{}': {}", INDEX, esUtils.fetchAllDocuments(INDEX)); } private HistoryRepositoryImpl createHistoryRepositoryImpl(RestHighLevelClient restHighLevelClient) { - return new HistoryRepositoryImpl(restHighLevelClient, INDEX, new MigrationScriptProtocolMapper(), 1000); + final ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), INDEX, new MigrationScriptProtocolMapper(), 1000, objectMapper); } } \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplTest.java index 1ef48e3b..b5eaa19b 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplTest.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/HistoryRepositoryImplTest.java @@ -1,12 +1,16 @@ package com.senacor.elasticsearch.evolution.core.internal.migration.execution; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.senacor.elasticsearch.evolution.core.api.MigrationException; import com.senacor.elasticsearch.evolution.core.internal.model.dbhistory.MigrationScriptProtocol; import com.senacor.elasticsearch.evolution.core.test.ArgumentProviders; import com.senacor.elasticsearch.evolution.core.test.MockitoExtension; -import org.elasticsearch.client.IndicesClient; +import org.apache.http.ProtocolVersion; +import org.apache.http.message.BasicStatusLine; import org.elasticsearch.client.Request; import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.rest.RestStatus; import org.junit.jupiter.api.BeforeEach; @@ -20,11 +24,10 @@ import java.io.IOException; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** @@ -41,14 +44,16 @@ class HistoryRepositoryImplTest { @BeforeEach void setUp() { - underTest = new HistoryRepositoryImpl(restHighLevelClient, INDEX, new MigrationScriptProtocolMapper(), 1000); + final ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + underTest = new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), INDEX, new MigrationScriptProtocolMapper(), 1000, objectMapper); } @Nested class findAll { @Test void failed() throws IOException { - when(restHighLevelClient.search(any(), eq(RequestOptions.DEFAULT))) + when(restHighLevelClient.getLowLevelClient().performRequest(any())) .thenThrow(new IOException("test error")); assertThatThrownBy(() -> underTest.findAll()) @@ -61,7 +66,7 @@ void failed() throws IOException { class saveOrUpdate { @Test void failed() throws IOException { - when(restHighLevelClient.index(any(), eq(RequestOptions.DEFAULT))) + when(restHighLevelClient.getLowLevelClient().performRequest(any())) .thenThrow(new IOException("test error")); MigrationScriptProtocol protocol = new MigrationScriptProtocol().setVersion("1"); @@ -75,6 +80,9 @@ void failed() throws IOException { class isLocked { @Test void failed() throws IOException { + when( + restHighLevelClient.getLowLevelClient().performRequest(any()).getStatusLine().getStatusCode() + ).thenReturn(200); when(restHighLevelClient.indices().refresh(any(), eq(RequestOptions.DEFAULT)).getStatus()) .thenReturn(RestStatus.OK); when(restHighLevelClient.count(any(), eq(RequestOptions.DEFAULT))) @@ -90,7 +98,7 @@ void failed() throws IOException { class lock { @Test void failed() throws IOException { - when(restHighLevelClient.count(any(), eq(RequestOptions.DEFAULT))) + when(restHighLevelClient.getLowLevelClient().performRequest(any())) .thenThrow(new IOException("test error")); assertThat(underTest.lock()).isFalse(); @@ -101,10 +109,14 @@ void failed() throws IOException { class unlock { @Test void failed() throws IOException { - when(restHighLevelClient.indices().refresh(any(), eq(RequestOptions.DEFAULT)).getStatus()) - .thenReturn(RestStatus.OK); - when(restHighLevelClient.updateByQuery(any(), eq(RequestOptions.DEFAULT))) + final Response responseMock = mock(Response.class); + when(restHighLevelClient.getLowLevelClient().performRequest(any())) + // first call is refresh, which must succeed + .thenReturn(responseMock) + // second call is updateByQuery which should fail .thenThrow(new IOException("test error")); + when(responseMock.getStatusLine()) + .thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); assertThat(underTest.unlock()).isFalse(); } @@ -128,17 +140,18 @@ void failedCheckingIndex() throws IOException { class validateStatus2xxOK { @ParameterizedTest @ArgumentsSource(ArgumentProviders.SuccessHttpCodesProvider.class) - void isOK(RestStatus status) { - underTest.validateHttpStatus2xxOK(status, "isOK"); + void isOK(int httpStatusCode) { + assertThatCode(() -> underTest.validateHttpStatusIs2xx(httpStatusCode, "isOK")) + .doesNotThrowAnyException(); } @ParameterizedTest @ArgumentsSource(ArgumentProviders.FailingHttpCodesProvider.class) - void failed(RestStatus status) { + void failed(int httpStatusCode) { String description = "failed"; - assertThatThrownBy(() -> underTest.validateHttpStatus2xxOK(status, description)) + assertThatThrownBy(() -> underTest.validateHttpStatusIs2xx(httpStatusCode, description)) .isInstanceOf(MigrationException.class) - .hasMessage("%s - response status is not OK: %s", description, status.getStatus()); + .hasMessage("%s - response status is not OK: %s", description, httpStatusCode); } } @@ -146,8 +159,9 @@ void failed(RestStatus status) { class refresh { @Test void allIndices_failed() throws IOException { - IndicesClient indices = restHighLevelClient.indices(); - doThrow(new IOException("foo")).when(indices).refresh(any(), eq(RequestOptions.DEFAULT)); + when( + restHighLevelClient.getLowLevelClient().performRequest(any()) + ).thenThrow(new IOException("foo")); assertThatThrownBy(() -> underTest.refresh()) .isInstanceOf(MigrationException.class) diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapperTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapperTest.java index d3b8322e..f34015e9 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapperTest.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationScriptProtocolMapperTest.java @@ -8,6 +8,7 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.HashMap; +import java.util.Map; import static com.senacor.elasticsearch.evolution.core.internal.migration.execution.MigrationScriptProtocolMapper.*; import static org.assertj.core.api.Assertions.assertThat; @@ -26,7 +27,7 @@ class mapToMap { void emptyProtocol() { MigrationScriptProtocol protocol = new MigrationScriptProtocol(); - HashMap res = underTest.mapToMap(protocol); + Map res = underTest.mapToMap(protocol); assertThat(res).hasSize(9) .containsEntry(CHECKSUM_FIELD_NAME, 0) @@ -53,7 +54,7 @@ void fullProtocol() { .setIndexName("index") .setScriptName("foo.http"); - HashMap res = underTest.mapToMap(protocol); + Map res = underTest.mapToMap(protocol); assertThat(res).hasSize(9) .containsEntry(CHECKSUM_FIELD_NAME, 1) diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java index 0dd133f8..fa37ba49 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java @@ -43,15 +43,15 @@ class MigrationServiceImplIT { @Mock private HistoryRepository historyRepositoryMock; - private Charset encoding = StandardCharsets.UTF_8; - private ContentType defaultContentType = ContentType.APPLICATION_JSON; + private final Charset encoding = StandardCharsets.UTF_8; + private final ContentType defaultContentType = ContentType.APPLICATION_JSON; @Nested class executeScript { - @ParameterizedTest(name = "esVersion: {0}") + @ParameterizedTest(name = "{0}") @ArgumentsSource(ElasticsearchArgumentsProvider.class) - void OK_indexDocumentIsWrittenToElasticsearch(String esVersion, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { + void OK_indexDocumentIsWrittenToElasticsearch(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) throws IOException { String index = "myindex"; ParsedMigrationScript script = createParsedMigrationScript("1.1", index); diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java index 486adfa1..9091c344 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java @@ -14,7 +14,6 @@ import org.apache.http.entity.ContentType; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; -import org.elasticsearch.rest.RestStatus; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -59,8 +58,8 @@ class MigrationServiceImplTest { @Mock private RestClient restClient; - private Charset encoding = StandardCharsets.UTF_8; - private ContentType defaultContentType = ContentType.APPLICATION_JSON; + private final Charset encoding = StandardCharsets.UTF_8; + private final ContentType defaultContentType = ContentType.APPLICATION_JSON; @Nested class waitUntilUnlocked { @@ -130,7 +129,7 @@ void scriptsAndHistoryInSync_noScriptsWillBeReturned() { List res = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts); - assertThat(res).hasSize(0); + assertThat(res).isEmpty(); InOrder order = inOrder(historyRepository); order.verify(historyRepository).findAll(); order.verifyNoMoreInteractions(); @@ -178,7 +177,7 @@ void moreHistoryVersionsThanScripts_warningIsShownAnNoScriptsWillBeReturned() { List res = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts); - assertThat(res).hasSize(0); + assertThat(res).isEmpty(); InOrder order = inOrder(historyRepository); order.verify(historyRepository).findAll(); order.verifyNoMoreInteractions(); @@ -440,9 +439,9 @@ void executeScript_failed_status(Exception handledError) throws IOException { @ParameterizedTest @ArgumentsSource(FailingHttpCodesProvider.class) - void executeScript_failed_status(RestStatus status) throws IOException { + void executeScript_failed_status(int httpStatusCode) throws IOException { ParsedMigrationScript script = createParsedMigrationScript("1.1"); - Response responseMock = createResponseMock(status.getStatus()); + Response responseMock = createResponseMock(httpStatusCode); doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, @@ -456,15 +455,15 @@ void executeScript_failed_status(RestStatus status) throws IOException { .isInstanceOf(MigrationException.class) .hasMessage("execution of script '%s' failed with HTTP status %s: Response(%s)", script.getFileNameInfo(), - status.getStatus(), - status.getStatus()); + httpStatusCode, + httpStatusCode); } @ParameterizedTest @ArgumentsSource(ArgumentProviders.SuccessHttpCodesProvider.class) - void executeScript_OK_status(RestStatus status) throws IOException { + void executeScript_OK_status(int httpStatusCode) throws IOException { ParsedMigrationScript script = createParsedMigrationScript("1.1"); - Response responseMock = createResponseMock(status.getStatus()); + Response responseMock = createResponseMock(httpStatusCode); doReturn(responseMock).when(restClient).performRequest(any()); MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository, @@ -608,7 +607,7 @@ void emptyScriptsCollection() { List res = underTest.executePendingScripts(emptyList()); - assertThat(res).hasSize(0); + assertThat(res).isEmpty(); InOrder order = inOrder(historyRepository, restClient); order.verifyNoMoreInteractions(); } diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/ArgumentProviders.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/ArgumentProviders.java index 70f4ff32..5d79476e 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/ArgumentProviders.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/ArgumentProviders.java @@ -1,11 +1,9 @@ package com.senacor.elasticsearch.evolution.core.test; -import org.elasticsearch.rest.RestStatus; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; -import java.util.Objects; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -17,10 +15,8 @@ public class ArgumentProviders { public static class SuccessHttpCodesProvider implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext context) throws Exception { - return IntStream.range(200, 300).boxed() - .map(RestStatus::fromCode) - .filter(Objects::nonNull) - .map(Arguments::of); + return IntStream.range(200, 300) + .mapToObj(Arguments::of); } } @@ -29,8 +25,6 @@ public static class FailingHttpCodesProvider implements ArgumentsProvider { public Stream provideArguments(ExtensionContext context) throws Exception { Stream status1xx = IntStream.range(100, 200).boxed(); return Stream.concat(status1xx, IntStream.range(300, 600).boxed()) - .map(RestStatus::fromCode) - .filter(Objects::nonNull) .map(Arguments::of); } } diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java index c51854d0..bf38a722 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java @@ -1,8 +1,15 @@ package com.senacor.elasticsearch.evolution.core.test; +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; import org.apache.http.HttpHost; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.*; +import org.elasticsearch.client.indices.GetIndexRequest; +import org.elasticsearch.client.indices.GetIndexResponse; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.TestInstancePostProcessor; @@ -12,16 +19,15 @@ import org.slf4j.LoggerFactory; import org.springframework.util.SocketUtils; import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.shaded.com.google.common.collect.ImmutableMap; import org.testcontainers.utility.DockerImageName; import java.io.IOException; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.*; import java.util.stream.Stream; +import static com.senacor.elasticsearch.evolution.core.test.EmbeddedElasticsearchExtension.SearchContainer.ofElasticsearch; +import static com.senacor.elasticsearch.evolution.core.test.EmbeddedElasticsearchExtension.SearchContainer.ofOpensearch; import static org.elasticsearch.client.RequestOptions.DEFAULT; /** @@ -34,56 +40,75 @@ public class EmbeddedElasticsearchExtension implements TestInstancePostProcessor private static final Logger logger = LoggerFactory.getLogger(EmbeddedElasticsearchExtension.class); private static final Namespace NAMESPACE = Namespace.create(ExtensionContext.class); - private static final SortedSet SUPPORTED_ES_VERSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList( - "7.12.0", - "7.11.2", - "7.10.2", - "7.9.3", - "7.8.1", - "7.7.1", - "7.6.2", - "7.5.2" + private static final SortedSet SUPPORTED_SEARCH_VERSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList( + ofOpensearch("1.3.1"), + ofOpensearch("1.2.4"), + ofOpensearch("1.1.0"), + ofOpensearch("1.0.1"), + + ofElasticsearch("8.1.2"), + ofElasticsearch("8.0.1"), + ofElasticsearch("7.17.2"), + ofElasticsearch("7.16.3"), + ofElasticsearch("7.15.2"), + ofElasticsearch("7.14.2"), + ofElasticsearch("7.13.4"), + ofElasticsearch("7.12.1"), + ofElasticsearch("7.11.2"), + ofElasticsearch("7.10.2"), + ofElasticsearch("7.9.3"), + ofElasticsearch("7.8.1"), + ofElasticsearch("7.7.1"), + ofElasticsearch("7.6.2"), + ofElasticsearch("7.5.2") ))); @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - SUPPORTED_ES_VERSIONS.parallelStream() - .forEach(esVersion -> getStore(context) - .getOrComputeIfAbsent(esVersion, EmbeddedElasticsearchExtension::createElasticsearchContainer, ElasticsearchContainer.class)); + SUPPORTED_SEARCH_VERSIONS.parallelStream() + .forEach(searchContainer -> getStore(context) + .getOrComputeIfAbsent(searchContainer, EmbeddedElasticsearchExtension::createElasticsearchContainer, ElasticsearchContainer.class)); } - private static RestHighLevelClient createRestHighLevelClient(String esVersion, ElasticsearchContainer elasticsearchContainer) { + private static RestHighLevelClient createRestHighLevelClient(String versionInfo, ElasticsearchContainer elasticsearchContainer) { HttpHost host = HttpHost.create(elasticsearchContainer.getHttpHostAddress()); - logger.debug("create RestHighLevelClient for ES {} at {}", esVersion, host); + logger.debug("create RestClient for {} at {}", versionInfo, host); RestClientBuilder builder = RestClient.builder(host); return new RestHighLevelClient(builder); } - private static ElasticsearchContainer createElasticsearchContainer(String esVersion) { - logger.info("creating ElasticsearchContainer {} ...", esVersion); - ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch") - .withTag(esVersion)) - .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m"); - int esHttpPort = SocketUtils.findAvailableTcpPort(5000, 30000); - int esTransportPort = SocketUtils.findAvailableTcpPort(30001, 65535); - container.setPortBindings(Arrays.asList(esHttpPort + ":9200", esTransportPort + ":9300")); - start(container, esVersion); + private static ElasticsearchContainer createElasticsearchContainer(SearchContainer searchContainer) { + logger.info("creating ElasticsearchContainer for {} ...", searchContainer.getInfo()); + ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName.parse(searchContainer.getContainerImage()) + .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch") + .withTag(searchContainer.getVersion())) + .withEnv(searchContainer.getEnv()); + int httpPort = SocketUtils.findAvailableTcpPort(5000, 30000); + int transportPort = SocketUtils.findAvailableTcpPort(30001, 65535); + container.setPortBindings(Arrays.asList(httpPort + ":9200", transportPort + ":" + searchContainer.transportPort)); + start(container, searchContainer.getInfo()); logger.info("ElasticsearchContainer {} started with HttpPort={} and TransportTcpPort={}!", - esVersion, - esHttpPort, - esTransportPort); + searchContainer.getInfo(), + httpPort, + transportPort); return container; } - private static void start(ElasticsearchContainer elasticsearchContainer, String esVersion) { - logger.debug("starting ElasticsearchContainer {}", esVersion); + private static void start(ElasticsearchContainer elasticsearchContainer, String versionInfo) { + logger.debug("starting ElasticsearchContainer {}", versionInfo); elasticsearchContainer.start(); } - private static void cleanup(EsUtils esUtils, String esVersion, RestHighLevelClient restHighLevelClient) { - logger.debug("cleanup ElasticsearchContainer {}", esVersion); + private static void cleanup(EsUtils esUtils, String versionInfo, RestHighLevelClient restHighLevelClient) { + logger.debug("cleanup ElasticsearchContainer {}", versionInfo); try { - restHighLevelClient.indices().delete(new DeleteIndexRequest("*"), DEFAULT); + // get all indices + final GetIndexResponse allIndices = restHighLevelClient.indices().get(new GetIndexRequest("_all") + .indicesOptions(IndicesOptions.lenientExpandOpen()), DEFAULT); + if (allIndices.getIndices().length > 0) { + logger.debug("delete indices {}", Arrays.toString(allIndices.getIndices())); + restHighLevelClient.indices().delete(new DeleteIndexRequest(allIndices.getIndices()), DEFAULT); + } Response deleteRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("DELETE", "/_template/*")); logger.debug("deleted all templates: {}", deleteRes); } catch (IOException e) { @@ -98,42 +123,83 @@ private static ExtensionContext.Store getStore(ExtensionContext context) { /** * provides in this order: - * - Elasticsearch Version + * - Short Version Info * - EsUtils * - RestHighLevelClient */ public static class ElasticsearchArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext context) { - Optional versionFilterPattern = context.getTestMethod() - .map(method -> method.getDeclaredAnnotation(IgnoreEsVersion.class)) - .map(IgnoreEsVersion::value); - - - return SUPPORTED_ES_VERSIONS.stream() - .filter(version -> versionFilterPattern - .map(filterPattern -> !version.matches(filterPattern)) - .orElse(true) - ) - .map(esVersion -> { + return SUPPORTED_SEARCH_VERSIONS.stream() + .map(searchContainer -> { ElasticsearchContainer elasticsearchContainer = getStore(context) - .getOrComputeIfAbsent(esVersion, EmbeddedElasticsearchExtension::createElasticsearchContainer, ElasticsearchContainer.class); - start(elasticsearchContainer, esVersion); - RestHighLevelClient restHighLevelClient = createRestHighLevelClient(esVersion, elasticsearchContainer); + .getOrComputeIfAbsent(searchContainer, EmbeddedElasticsearchExtension::createElasticsearchContainer, ElasticsearchContainer.class); + start(elasticsearchContainer, searchContainer.getInfo()); + RestHighLevelClient restHighLevelClient = createRestHighLevelClient(searchContainer.getInfo(), elasticsearchContainer); EsUtils esUtils = new EsUtils(restHighLevelClient.getLowLevelClient()); - cleanup(esUtils, esVersion, restHighLevelClient); - return Arguments.of(esVersion, esUtils, restHighLevelClient); + cleanup(esUtils, searchContainer.getInfo(), restHighLevelClient); + return Arguments.of(searchContainer.getShortInfo(), esUtils, restHighLevelClient); }); } } - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - public @interface IgnoreEsVersion { + @Value + @Builder + public static class SearchContainer implements Comparable { + @NonNull + String vendor; + @NonNull + String vendorShort; + @NonNull + String version; + @NonNull + String containerImage; + @NonNull + Map env; + int transportPort; + + public static SearchContainer ofElasticsearch(String version) { + return SearchContainer.builder() + .vendor("Elasticsearch") + .vendorShort("ES") + .containerImage("docker.elastic.co/elasticsearch/elasticsearch") + .version(version) + .env(ImmutableMap.of( + "ES_JAVA_OPTS", "-Xms128m -Xmx128m", + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + "xpack.security.enabled", "false" + )) + .transportPort(9300) + .build(); + } + + public static SearchContainer ofOpensearch(String version) { + return SearchContainer.builder() + .vendor("Opensearch") + .vendorShort("OS") + .containerImage("opensearchproject/opensearch") + .version(version) + .env(ImmutableMap.of( + "OPENSEARCH_JAVA_OPTS", "-Xms128m -Xmx128m", + // disable security / https for testing + "plugins.security.disabled", "true", + "DISABLE_INSTALL_DEMO_CONFIG", "true" + )) + .transportPort(9600) + .build(); + } - /** - * The version pattern (regex) to ignore - */ - String value(); + public String getInfo() { + return vendor + " " + version; + } + + public String getShortInfo() { + return vendorShort + " " + version; + } + + @Override + public int compareTo(@NotNull SearchContainer other) { + return version.compareTo(other.version); + } } } diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EsUtils.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EsUtils.java index 78f06c58..31c20aaf 100644 --- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EsUtils.java +++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EsUtils.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -29,7 +30,7 @@ public EsUtils(RestClient restClient) { public void refreshIndices() { try { - restClient.performRequest(new Request("POST", "/_refresh")); + restClient.performRequest(new Request("GET", "/_refresh")); } catch (IOException e) { throw new IllegalStateException("refreshIndices failed", e); } @@ -68,5 +69,20 @@ private Stream parseDocuments(String body) { throw new IllegalStateException("parseDocuments failed. body=" + body, e); } } + + public void indexDocument(String index, String id, HashMap source) { + try { + final Request indexRequest = new Request("PUT", "/" + index + "/_doc/" + id); + indexRequest.setJsonEntity(OBJECT_MAPPER.writeValueAsString(source)); + final Response res = restClient.performRequest(indexRequest); + if (res.getStatusLine().getStatusCode() != 201) { + throw new IllegalStateException(String.format("indexDocument failed with status code %s: %s", + res.getStatusLine().getStatusCode(), + res.getStatusLine().getReasonPhrase())); + } + } catch (IOException e) { + throw new IllegalStateException("indexDocument failed", e); + } + } } diff --git a/elasticsearch-evolution-core/src/test/resources/log4j2.xml b/elasticsearch-evolution-core/src/test/resources/log4j2.xml new file mode 100644 index 00000000..4c31a021 --- /dev/null +++ b/elasticsearch-evolution-core/src/test/resources/log4j2.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/elasticsearch-evolution-core/src/test/resources/logback.xml b/elasticsearch-evolution-core/src/test/resources/logback.xml index ba70764b..6ee3bbd3 100644 --- a/elasticsearch-evolution-core/src/test/resources/logback.xml +++ b/elasticsearch-evolution-core/src/test/resources/logback.xml @@ -8,7 +8,7 @@ - + diff --git a/mvnw b/mvnw index ec72bb9a..5643201c 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Maven Start Up Batch script # # Required ENV vars: # ------------------ @@ -36,6 +36,10 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi @@ -108,13 +112,12 @@ if $cygwin ; then CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi -# For Migwn, ensure paths are in UNIX format before anything is touched +# For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? fi if [ -z "$JAVA_HOME" ]; then @@ -146,7 +149,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`which java`" + JAVACMD="`\\unset -f command; \\command -v java`" fi fi @@ -200,8 +203,89 @@ if [ -z "$BASE_DIR" ]; then exit 1; fi +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -#echo $MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java @@ -216,10 +300,17 @@ if $cygwin; then MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd old mode 100755 new mode 100644 index 019bd74d..8a15b7f3 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -18,7 +18,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @@ -26,7 +26,7 @@ @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -35,7 +35,9 @@ @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME @@ -44,8 +46,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal @@ -115,11 +117,54 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end @@ -129,15 +174,15 @@ set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause +if "%MAVEN_BATCH_PAUSE%"=="on" pause -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% -exit /B %ERROR_CODE% +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 0d481d11..96f6886a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.3.2 + 0.4.0 pom org.springframework.boot spring-boot-dependencies - 2.4.4 + 2.6.6 elasticsearch-evolution @@ -36,6 +36,19 @@ https://github.com/senacor/elasticsearch-evolution + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + readme @@ -80,18 +93,20 @@ 4.3.0 - 0.8.6 - 1.6 + + 0.8.8-SNAPSHOT + 3.0.1 - 3.2.0 - 1.6.8 + 3.3.2 + 1.6.12 - 2.8.0 + 2.11.0 7.5.2 0.9.12 - 1.15.2 + 1.16.3 + 1.18.22
@@ -125,6 +140,17 @@ + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch.version} + + + commons-logging + commons-logging + + + org.testcontainers elasticsearch @@ -137,6 +163,13 @@ commons-io ${commons-io.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + diff --git a/spring-boot-starter-elasticsearch-evolution/pom.xml b/spring-boot-starter-elasticsearch-evolution/pom.xml index 34817f3b..cd4fd13b 100644 --- a/spring-boot-starter-elasticsearch-evolution/pom.xml +++ b/spring-boot-starter-elasticsearch-evolution/pom.xml @@ -6,7 +6,7 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.3.2 + 0.4.0 ../ spring-boot-starter-elasticsearch-evolution @@ -78,6 +78,11 @@ elasticsearch test + + org.elasticsearch.client + elasticsearch-rest-high-level-client + test + org.testcontainers elasticsearch diff --git a/spring-boot-starter-elasticsearch-evolution/src/main/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfiguration.java b/spring-boot-starter-elasticsearch-evolution/src/main/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfiguration.java index f6590a80..c092de2a 100644 --- a/spring-boot-starter-elasticsearch-evolution/src/main/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfiguration.java +++ b/spring-boot-starter-elasticsearch-evolution/src/main/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfiguration.java @@ -5,7 +5,8 @@ import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -19,6 +20,7 @@ import org.springframework.context.annotation.Configuration; import java.util.Arrays; +import java.util.List; /** * {@link EnableAutoConfiguration Auto-configuration} for ElasticsearchEvolution @@ -38,12 +40,14 @@ }) public class ElasticsearchEvolutionAutoConfiguration { + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchEvolutionAutoConfiguration.class); + @Bean @ConditionalOnMissingBean - @ConditionalOnBean(RestHighLevelClient.class) + @ConditionalOnBean(RestClient.class) public ElasticsearchEvolution elasticsearchEvolution(ElasticsearchEvolutionConfig elasticsearchEvolutionConfig, - RestHighLevelClient restHighLevelClient) { - return new ElasticsearchEvolution(elasticsearchEvolutionConfig, restHighLevelClient); + RestClient restClient) { + return new ElasticsearchEvolution(elasticsearchEvolutionConfig, restClient); } @Bean @@ -53,22 +57,46 @@ public ElasticsearchEvolutionInitializer elasticsearchEvolutionInitializer(Elast } @Configuration - @ConditionalOnClass(RestHighLevelClient.class) - public static class RestHighLevelClientConfiguration { + @ConditionalOnClass(RestClient.class) + public static class RestClientConfiguration { /** - * @return default RestHighLevelClient if {@link org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration} is not used - * and no RestHighLevelClient is available. + * @return default RestClientBuilder if {@link org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration} is not used + * and no RestClient is available. */ @Bean - @ConditionalOnMissingBean - public RestHighLevelClient restHighLevelClient(@Value("${spring.elasticsearch.rest.uris:http://localhost:9200}") String... uris) { - HttpHost[] httpHosts = Arrays.stream(uris) + @ConditionalOnMissingBean({RestClientBuilder.class, RestClient.class}) + public RestClientBuilder restClientBuilder( + @Value("${spring.elasticsearch.rest.uris:http://localhost:9200}") String[] urisDeprecated, + @Value("${spring.elasticsearch.uris:}") String[] uris) { + final List urisList; + if (uris != null && uris.length > 0) { + // prefer the new spring-boot (since 2.6) config properties + urisList = Arrays.asList(uris); + } else if (urisDeprecated != null && urisDeprecated.length > 0) { + // fallback to old deprecated spring-boot config properties + urisList = Arrays.asList(urisDeprecated); + } else { + throw new IllegalStateException("spring configuration 'spring.elasticsearch.uris' does not exist"); + } + + logger.info("creating RestClientBuilder with uris {}", urisList); + + HttpHost[] httpHosts = urisList.stream() .map(HttpHost::create) .toArray(HttpHost[]::new); - RestClientBuilder builder = RestClient.builder(httpHosts); - return new RestHighLevelClient(builder); + return RestClient.builder(httpHosts); } + /** + * @return default RestClient no RestClient is available. + */ + @Bean + @ConditionalOnBean(RestClientBuilder.class) + @ConditionalOnMissingBean + public RestClient restClient(RestClientBuilder restClientBuilder) { + logger.info("creating RestClient from {}", restClientBuilder); + return restClientBuilder.build(); + } } } diff --git a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationIT.java b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationIT.java index fb218522..c02ee581 100644 --- a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationIT.java +++ b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationIT.java @@ -2,9 +2,12 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.Node; +import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -29,11 +32,17 @@ class ElasticsearchEvolutionAutoConfigurationIT { @Autowired + private RestClient restClient; private RestHighLevelClient restHighLevelClient; @Autowired private EsUtils esUtils; + @BeforeEach + void setUp() { + restHighLevelClient = new RestHighLevelClient(RestClient.builder(restClient.getNodes().toArray(new Node[0]))); + } + @Test void migrateOnApplicationStartViaInitializer() throws IOException { esUtils.refreshIndices(); diff --git a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationTest.java b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationTest.java index fa6292ce..8870bb6a 100644 --- a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationTest.java +++ b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/ElasticsearchEvolutionAutoConfigurationTest.java @@ -1,7 +1,7 @@ package com.senacor.elasticsearch.evolution.spring.boot.starter.autoconfigure; import com.senacor.elasticsearch.evolution.core.ElasticsearchEvolution; -import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.RestClient; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; @@ -33,8 +33,8 @@ void whenSpringContextIsBootstrapped_thenNoExceptions() { } @Test - void restHighLevelClient_existsAsBean() { - assertThat(applicationContext.getBean(RestHighLevelClient.class)).isNotNull(); + void restClient_existsAsBean() { + assertThat(applicationContext.getBean(RestClient.class)).isNotNull(); } @Test diff --git a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EmbeddedElasticsearchConfiguration.java b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EmbeddedElasticsearchConfiguration.java index 97672c61..d38dd6d3 100644 --- a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EmbeddedElasticsearchConfiguration.java +++ b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EmbeddedElasticsearchConfiguration.java @@ -3,7 +3,6 @@ import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; @@ -17,21 +16,23 @@ public class EmbeddedElasticsearchConfiguration { @Bean(destroyMethod = "stop") public ElasticsearchContainer elasticsearchContainer() { - ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch-oss:7.5.2") - .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m"); + ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.5.2") + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false"); logger.info("starting embedded ElasticSearch..."); container.start(); return container; } @Bean - public RestHighLevelClient restHighLevelClient(ElasticsearchContainer elasticsearchContainer) { + public RestClient restClient(ElasticsearchContainer elasticsearchContainer) { RestClientBuilder builder = RestClient.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())); - return new RestHighLevelClient(builder); + return builder.build(); } @Bean - public EsUtils esUtils(RestHighLevelClient restHighLevelClient) { - return new EsUtils(restHighLevelClient.getLowLevelClient()); + public EsUtils esUtils(RestClient restClient) { + return new EsUtils(restClient); } } diff --git a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EsUtils.java b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EsUtils.java index 260691f6..4207c9bd 100644 --- a/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EsUtils.java +++ b/spring-boot-starter-elasticsearch-evolution/src/test/java/com/senacor/elasticsearch/evolution/spring/boot/starter/autoconfigure/EsUtils.java @@ -18,7 +18,7 @@ public EsUtils(RestClient restClient) { public void refreshIndices() { try { - restClient.performRequest(new Request("POST", "/_refresh")); + restClient.performRequest(new Request("GET", "/_refresh")); } catch (IOException e) { throw new IllegalStateException("refreshIndices failed", e); } diff --git a/tests/migration-scripts/pom.xml b/tests/migration-scripts/pom.xml index 4dbf35d4..f85402b3 100644 --- a/tests/migration-scripts/pom.xml +++ b/tests/migration-scripts/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.senacor.elasticsearch.evolution migration-scripts - 0.3.2 + 0.4.0 jar containing migration files jar diff --git a/tests/pom.xml b/tests/pom.xml index faa7adf6..2e7fd876 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -6,7 +6,7 @@ com.senacor.elasticsearch.evolution elasticsearch-evolution-parent - 0.3.2 + 0.4.0 ../ tests @@ -19,6 +19,8 @@ migration-scripts + test-spring-boot-2.6 + test-spring-boot-2.5-scriptsInJarFile test-spring-boot-2.4 test-spring-boot-2.3 test-spring-boot-2.2 diff --git a/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml b/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml index 02718e86..568c3057 100644 --- a/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml +++ b/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.1-scriptsInJarFile - 0.3.2 + 0.4.0 Demo project for Spring Boot 1.8 - 2.8.0 + 2.11.0 7.5.2 - 1.15.2 + 1.16.3 @@ -62,6 +62,19 @@ + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + @@ -71,7 +84,8 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + + 0.8.8-SNAPSHOT prepare-agent diff --git a/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java b/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java index ebdf0d75..d04a22e7 100644 --- a/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java +++ b/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java @@ -42,8 +42,10 @@ static class Config { public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) { ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName .parse("docker.elastic.co/elasticsearch/elasticsearch") - .withTag(esVersion) - ).withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m"); + .withTag(esVersion)) + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false"); container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200")); container.start(); return container; diff --git a/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java b/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java index 57a73f22..bde1f552 100644 --- a/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java +++ b/tests/test-spring-boot-2.1-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java @@ -29,7 +29,7 @@ public EsUtils(RestClient restClient) { public void refreshIndices() { try { - restClient.performRequest(new Request("POST", "/_refresh")); + restClient.performRequest(new Request("GET", "/_refresh")); } catch (IOException e) { throw new IllegalStateException("refreshIndices failed", e); } diff --git a/tests/test-spring-boot-2.2/pom.xml b/tests/test-spring-boot-2.2/pom.xml index 931d6841..39a2ff19 100644 --- a/tests/test-spring-boot-2.2/pom.xml +++ b/tests/test-spring-boot-2.2/pom.xml @@ -10,15 +10,15 @@ com.senacor.elasticsearch.evolution test-spring-boot-2.2 - 0.3.2 + 0.4.0 Demo project for Spring Boot 1.8 - 2.8.0 + 2.11.0 7.5.2 - 1.15.2 + 1.16.3 @@ -55,6 +55,19 @@ + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + @@ -64,7 +77,8 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + + 0.8.8-SNAPSHOT prepare-agent diff --git a/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/ApplicationTests.java b/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/ApplicationTests.java index 2c85fe4f..d8ca6dec 100644 --- a/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/ApplicationTests.java +++ b/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/ApplicationTests.java @@ -17,7 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(properties = {"spring.elasticsearch.rest.uris=http://localhost:" + ApplicationTests.ELASTICSEARCH_PORT}) -public class ApplicationTests { +class ApplicationTests { static final int ELASTICSEARCH_PORT = 18759; @@ -25,7 +25,7 @@ public class ApplicationTests { private EsUtils esUtils; @Test - public void contextLoads() { + void contextLoads() { esUtils.refreshIndices(); List documents = esUtils.fetchAllDocuments("test_1"); @@ -39,9 +39,10 @@ static class Config { public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) { ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName .parse("docker.elastic.co/elasticsearch/elasticsearch") - .withTag(esVersion) - ) - .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m"); + .withTag(esVersion)) + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false"); container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200")); container.start(); return container; diff --git a/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/EsUtils.java b/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/EsUtils.java index 5c0197bd..b2350948 100644 --- a/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/EsUtils.java +++ b/tests/test-spring-boot-2.2/src/test/java/com/senacor/elasticsearch/evolution/springboot22/EsUtils.java @@ -29,7 +29,7 @@ public EsUtils(RestClient restClient) { public void refreshIndices() { try { - restClient.performRequest(new Request("POST", "/_refresh")); + restClient.performRequest(new Request("GET", "/_refresh")); } catch (IOException e) { throw new IllegalStateException("refreshIndices failed", e); } diff --git a/tests/test-spring-boot-2.3/pom.xml b/tests/test-spring-boot-2.3/pom.xml index efbd24e9..dd16fe6c 100644 --- a/tests/test-spring-boot-2.3/pom.xml +++ b/tests/test-spring-boot-2.3/pom.xml @@ -5,20 +5,20 @@ org.springframework.boot spring-boot-starter-parent - 2.3.9.RELEASE + 2.3.12.RELEASE com.senacor.elasticsearch.evolution test-spring-boot-2.3 - 0.3.2 + 0.4.0 Demo project for Spring Boot 1.8 - 2.8.0 + 2.11.0 7.5.2 - 1.15.2 + 1.16.3 @@ -55,6 +55,19 @@ + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + @@ -64,7 +77,8 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + + 0.8.8-SNAPSHOT prepare-agent diff --git a/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java b/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java index bc2ffee9..b5a0a557 100644 --- a/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java +++ b/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/ApplicationTests.java @@ -17,7 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(properties = {"spring.elasticsearch.rest.uris=http://localhost:" + ApplicationTests.ELASTICSEARCH_PORT}) -public class ApplicationTests { +class ApplicationTests { static final int ELASTICSEARCH_PORT = 18761; @@ -25,7 +25,7 @@ public class ApplicationTests { private EsUtils esUtils; @Test - public void contextLoads() { + void contextLoads() { esUtils.refreshIndices(); List documents = esUtils.fetchAllDocuments("test_1"); @@ -39,8 +39,10 @@ static class Config { public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) { ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName .parse("docker.elastic.co/elasticsearch/elasticsearch") - .withTag(esVersion) - ).withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m"); + .withTag(esVersion)) + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false"); container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200")); container.start(); return container; diff --git a/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java b/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java index 57a73f22..bde1f552 100644 --- a/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java +++ b/tests/test-spring-boot-2.3/src/test/java/com/senacor/elasticsearch/evolution/springboot23/EsUtils.java @@ -29,7 +29,7 @@ public EsUtils(RestClient restClient) { public void refreshIndices() { try { - restClient.performRequest(new Request("POST", "/_refresh")); + restClient.performRequest(new Request("GET", "/_refresh")); } catch (IOException e) { throw new IllegalStateException("refreshIndices failed", e); } diff --git a/tests/test-spring-boot-2.4/pom.xml b/tests/test-spring-boot-2.4/pom.xml index 5e5f46e7..15a336a5 100644 --- a/tests/test-spring-boot-2.4/pom.xml +++ b/tests/test-spring-boot-2.4/pom.xml @@ -5,20 +5,20 @@ org.springframework.boot spring-boot-starter-parent - 2.4.4 + 2.4.13 com.senacor.elasticsearch.evolution test-spring-boot-2.4 - 0.3.2 + 0.4.0 Demo project for Spring Boot 1.8 - 2.8.0 + 2.11.0 7.5.2 - 1.15.2 + 1.16.3 @@ -55,6 +55,19 @@ + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + @@ -64,7 +77,8 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + + 0.8.8-SNAPSHOT prepare-agent diff --git a/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/ApplicationTests.java b/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/ApplicationTests.java index ba06d4ce..a0e5e59c 100644 --- a/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/ApplicationTests.java +++ b/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/ApplicationTests.java @@ -17,7 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(properties = {"spring.elasticsearch.rest.uris=http://localhost:" + ApplicationTests.ELASTICSEARCH_PORT}) -public class ApplicationTests { +class ApplicationTests { static final int ELASTICSEARCH_PORT = 18762; @@ -25,7 +25,7 @@ public class ApplicationTests { private EsUtils esUtils; @Test - public void contextLoads() { + void contextLoads() { esUtils.refreshIndices(); List documents = esUtils.fetchAllDocuments("test_1"); @@ -39,8 +39,10 @@ static class Config { public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) { ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName .parse("docker.elastic.co/elasticsearch/elasticsearch") - .withTag(esVersion) - ).withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m"); + .withTag(esVersion)) + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false"); container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200")); container.start(); return container; diff --git a/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/EsUtils.java b/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/EsUtils.java index 5750fc23..93ad5096 100644 --- a/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/EsUtils.java +++ b/tests/test-spring-boot-2.4/src/test/java/com/senacor/elasticsearch/evolution/springboot24/EsUtils.java @@ -29,7 +29,7 @@ public EsUtils(RestClient restClient) { public void refreshIndices() { try { - restClient.performRequest(new Request("POST", "/_refresh")); + restClient.performRequest(new Request("GET", "/_refresh")); } catch (IOException e) { throw new IllegalStateException("refreshIndices failed", e); } diff --git a/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml b/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml new file mode 100644 index 00000000..15e0f2c0 --- /dev/null +++ b/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.11 + + + com.senacor.elasticsearch.evolution + test-spring-boot-2.5-scriptsInJarFile + 0.4.0 + Demo project for Spring Boot + + + 1.8 + + 2.11.0 + 7.5.2 + 1.16.3 + + + + + + ${project.groupId} + migration-scripts + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + ${project.groupId} + spring-boot-starter-elasticsearch-evolution + ${project.version} + + + + + org.testcontainers + elasticsearch + ${testcontainers.elasticsearch.version} + test + + + commons-io + commons-io + ${commons-io.version} + test + + + + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jacoco + jacoco-maven-plugin + + 0.8.8-SNAPSHOT + + + prepare-agent + + prepare-agent + + + + report + post-integration-test + + report + + + + + + maven-surefire-plugin + + true + + + + + + diff --git a/tests/test-spring-boot-2.5-scriptsInJarFile/src/main/java/com/senacor/elasticsearch/evolution/springboot25/Application.java b/tests/test-spring-boot-2.5-scriptsInJarFile/src/main/java/com/senacor/elasticsearch/evolution/springboot25/Application.java new file mode 100644 index 00000000..15055b27 --- /dev/null +++ b/tests/test-spring-boot-2.5-scriptsInJarFile/src/main/java/com/senacor/elasticsearch/evolution/springboot25/Application.java @@ -0,0 +1,13 @@ +package com.senacor.elasticsearch.evolution.springboot25; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} \ No newline at end of file diff --git a/tests/test-spring-boot-2.5-scriptsInJarFile/src/main/resources/application.properties b/tests/test-spring-boot-2.5-scriptsInJarFile/src/main/resources/application.properties new file mode 100644 index 00000000..5db1f692 --- /dev/null +++ b/tests/test-spring-boot-2.5-scriptsInJarFile/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.elasticsearch.evolution.locations[0]=classpath:es/mig +spring.elasticsearch.evolution.placeholders.index=test_1 +logging.level.com.senacor.elasticsearch.evolution=DEBUG \ No newline at end of file diff --git a/tests/test-spring-boot-2.5-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot25/ApplicationTest.java b/tests/test-spring-boot-2.5-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot25/ApplicationTest.java new file mode 100644 index 00000000..6352ee93 --- /dev/null +++ b/tests/test-spring-boot-2.5-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot25/ApplicationTest.java @@ -0,0 +1,56 @@ +package com.senacor.elasticsearch.evolution.springboot25; + +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.utility.DockerImageName; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(properties = {"spring.elasticsearch.rest.uris=http://localhost:" + ApplicationTest.ELASTICSEARCH_PORT}) +class ApplicationTest { + + static final int ELASTICSEARCH_PORT = 18768; + + @Autowired + private EsUtils esUtils; + + @Test + void contextLoads() { + esUtils.refreshIndices(); + + List documents = esUtils.fetchAllDocuments("test_1"); + + assertThat(documents).hasSize(1); + } + + @TestConfiguration + static class Config { + @Bean(destroyMethod = "stop") + public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) { + ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName + .parse("docker.elastic.co/elasticsearch/elasticsearch") + .withTag(esVersion)) + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false"); + container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200")); + container.start(); + return container; + } + + @Bean + public EsUtils esUtils(ElasticsearchContainer elasticsearchContainer) { + return new EsUtils(RestClient.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build()); + } + } +} \ No newline at end of file diff --git a/tests/test-spring-boot-2.5-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot25/EsUtils.java b/tests/test-spring-boot-2.5-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot25/EsUtils.java new file mode 100644 index 00000000..8f9e5c3a --- /dev/null +++ b/tests/test-spring-boot-2.5-scriptsInJarFile/src/test/java/com/senacor/elasticsearch/evolution/springboot25/EsUtils.java @@ -0,0 +1,72 @@ +package com.senacor.elasticsearch.evolution.springboot25; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * @author Andreas Keefer + */ +public class EsUtils { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final RestClient restClient; + + public EsUtils(RestClient restClient) { + this.restClient = restClient; + } + + public void refreshIndices() { + try { + restClient.performRequest(new Request("GET", "/_refresh")); + } catch (IOException e) { + throw new IllegalStateException("refreshIndices failed", e); + } + } + + public List fetchAllDocuments(String index) { + try { + Request post = new Request("POST", "/" + index + "/_search"); + post.setJsonEntity("{" + + " \"query\": {" + + " \"match_all\": {}" + + " }" + + "}"); + Response response = restClient.performRequest(post); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode < 200 || statusCode >= 300) { + throw new IllegalStateException("fetchAllDocuments(" + index + ") failed with HTTP status " + + statusCode + ": " + response.toString()); + } + String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + + return parseDocuments(body) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IllegalStateException("fetchAllDocuments(" + index + ") failed", e); + } + } + + private Stream parseDocuments(String body) { + try { + JsonNode jsonNode = OBJECT_MAPPER.readTree(body); + return StreamSupport.stream(jsonNode.get("hits").get("hits").spliterator(), false) + .map(hitNode -> hitNode.get("_source")) + .map(JsonNode::toString); + } catch (IOException e) { + throw new IllegalStateException("parseDocuments failed. body=" + body, e); + } + } +} + diff --git a/tests/test-spring-boot-2.6/pom.xml b/tests/test-spring-boot-2.6/pom.xml new file mode 100644 index 00000000..0225149e --- /dev/null +++ b/tests/test-spring-boot-2.6/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.6.6 + + + com.senacor.elasticsearch.evolution + test-spring-boot-2.6 + 0.4.0 + Demo project for Spring Boot + + + 1.8 + + 2.11.0 + 7.5.2 + 1.16.3 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + ${project.groupId} + spring-boot-starter-elasticsearch-evolution + ${project.version} + + + + + org.testcontainers + elasticsearch + ${testcontainers.elasticsearch.version} + test + + + commons-io + commons-io + ${commons-io.version} + test + + + + + + oss.sonatype.org-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jacoco + jacoco-maven-plugin + + 0.8.8-SNAPSHOT + + + prepare-agent + + prepare-agent + + + + report + post-integration-test + + report + + + + + + maven-surefire-plugin + + true + + + + + + diff --git a/tests/test-spring-boot-2.6/src/main/java/com/senacor/elasticsearch/evolution/springboot24/Application.java b/tests/test-spring-boot-2.6/src/main/java/com/senacor/elasticsearch/evolution/springboot24/Application.java new file mode 100644 index 00000000..6778bd46 --- /dev/null +++ b/tests/test-spring-boot-2.6/src/main/java/com/senacor/elasticsearch/evolution/springboot24/Application.java @@ -0,0 +1,14 @@ +package com.senacor.elasticsearch.evolution.springboot24; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + +} \ No newline at end of file diff --git a/tests/test-spring-boot-2.6/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http b/tests/test-spring-boot-2.6/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http new file mode 100644 index 00000000..fbd7df47 --- /dev/null +++ b/tests/test-spring-boot-2.6/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http @@ -0,0 +1,35 @@ +PUT _template/test_1 +Content-Type: application/json + +{ + "index_patterns" : [ + "test_*" + ], + "order" : 1, + "version" : 1, + "settings" : { + "number_of_shards" : 1 + }, + "mappings" : { + "dynamic" : "strict", + "properties" : { + "doc" : { + "dynamic" : false, + "properties" : {} + }, + "searchable" : { + "dynamic" : false, + "properties" : { + "version" : { + "type" : "keyword", + "ignore_above" : 20, + "similarity" : "boolean" + }, + "locked" : { + "type" : "boolean" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/test-spring-boot-2.6/src/main/resources/es/migration/V001.01__addDocument.http b/tests/test-spring-boot-2.6/src/main/resources/es/migration/V001.01__addDocument.http new file mode 100644 index 00000000..4f18ba0a --- /dev/null +++ b/tests/test-spring-boot-2.6/src/main/resources/es/migration/V001.01__addDocument.http @@ -0,0 +1,17 @@ +PUT /test_1/_doc/1?refresh +Content-Type: application/json + +{ + "searchable": { + "version": "1", + "locked": false + }, + "doc": { + "version": "1", + "locked": false, + "success": true, + "a": "a a a", + "b": true, + "c": "c" + } +} \ No newline at end of file diff --git a/tests/test-spring-boot-2.6/src/test/java/com/senacor/elasticsearch/evolution/springboot24/ApplicationTests.java b/tests/test-spring-boot-2.6/src/test/java/com/senacor/elasticsearch/evolution/springboot24/ApplicationTests.java new file mode 100644 index 00000000..cc5f8b5a --- /dev/null +++ b/tests/test-spring-boot-2.6/src/test/java/com/senacor/elasticsearch/evolution/springboot24/ApplicationTests.java @@ -0,0 +1,56 @@ +package com.senacor.elasticsearch.evolution.springboot24; + +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.utility.DockerImageName; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(properties = {"spring.elasticsearch.uris=http://localhost:" + ApplicationTests.ELASTICSEARCH_PORT}) +class ApplicationTests { + + static final int ELASTICSEARCH_PORT = 18772; + + @Autowired + private EsUtils esUtils; + + @Test + void contextLoads() { + esUtils.refreshIndices(); + + List documents = esUtils.fetchAllDocuments("test_1"); + + assertThat(documents).hasSize(1); + } + + @TestConfiguration + static class Config { + @Bean(destroyMethod = "stop") + public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) { + ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName + .parse("docker.elastic.co/elasticsearch/elasticsearch") + .withTag(esVersion)) + .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m") + // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled + .withEnv("xpack.security.enabled", "false"); + container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200")); + container.start(); + return container; + } + + @Bean + public EsUtils esUtils(ElasticsearchContainer elasticsearchContainer) { + return new EsUtils(RestClient.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build()); + } + } +} \ No newline at end of file diff --git a/tests/test-spring-boot-2.6/src/test/java/com/senacor/elasticsearch/evolution/springboot24/EsUtils.java b/tests/test-spring-boot-2.6/src/test/java/com/senacor/elasticsearch/evolution/springboot24/EsUtils.java new file mode 100644 index 00000000..93ad5096 --- /dev/null +++ b/tests/test-spring-boot-2.6/src/test/java/com/senacor/elasticsearch/evolution/springboot24/EsUtils.java @@ -0,0 +1,72 @@ +package com.senacor.elasticsearch.evolution.springboot24; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * @author Andreas Keefer + */ +public class EsUtils { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final RestClient restClient; + + public EsUtils(RestClient restClient) { + this.restClient = restClient; + } + + public void refreshIndices() { + try { + restClient.performRequest(new Request("GET", "/_refresh")); + } catch (IOException e) { + throw new IllegalStateException("refreshIndices failed", e); + } + } + + public List fetchAllDocuments(String index) { + try { + Request post = new Request("POST", "/" + index + "/_search"); + post.setJsonEntity("{" + + " \"query\": {" + + " \"match_all\": {}" + + " }" + + "}"); + Response response = restClient.performRequest(post); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode < 200 || statusCode >= 300) { + throw new IllegalStateException("fetchAllDocuments(" + index + ") failed with HTTP status " + + statusCode + ": " + response.toString()); + } + String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + + return parseDocuments(body) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new IllegalStateException("fetchAllDocuments(" + index + ") failed", e); + } + } + + private Stream parseDocuments(String body) { + try { + JsonNode jsonNode = OBJECT_MAPPER.readTree(body); + return StreamSupport.stream(jsonNode.get("hits").get("hits").spliterator(), false) + .map(hitNode -> hitNode.get("_source")) + .map(JsonNode::toString); + } catch (IOException e) { + throw new IllegalStateException("parseDocuments failed. body=" + body, e); + } + } +} +