diff --git a/README.md b/README.md index 7f4ef0046e..cf7c54c354 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,8 @@ Note: This store requires a [job to aggregate](https://github.com/openzipkin/zip ### Elasticsearch The [Elasticsearch](zipkin-server#elasticsearch-storage) component uses -Elasticsearch 5+ features, but is tested against Elasticsearch 7-8.x. +Elasticsearch 5+ features, but is tested against Elasticsearch 7-8.x and +OpenSearch 2.x. It stores spans as Zipkin v2 json so that integration with other tools is straightforward. To help with scale, this uses a combination of custom diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 1ca0332b3c..ac1a9163cc 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT benchmarks diff --git a/pom.xml b/pom.xml index 59387978df..1605858fed 100755 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT pom diff --git a/zipkin-collector/activemq/pom.xml b/zipkin-collector/activemq/pom.xml index b10d49d89f..444fe6027d 100644 --- a/zipkin-collector/activemq/pom.xml +++ b/zipkin-collector/activemq/pom.xml @@ -11,7 +11,7 @@ io.zipkin.zipkin2 zipkin-collector-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-collector-activemq diff --git a/zipkin-collector/core/pom.xml b/zipkin-collector/core/pom.xml index d189c98083..74e21c456b 100644 --- a/zipkin-collector/core/pom.xml +++ b/zipkin-collector/core/pom.xml @@ -11,7 +11,7 @@ io.zipkin.zipkin2 zipkin-collector-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-collector diff --git a/zipkin-collector/kafka/pom.xml b/zipkin-collector/kafka/pom.xml index 39c11e4418..3a505b38e4 100644 --- a/zipkin-collector/kafka/pom.xml +++ b/zipkin-collector/kafka/pom.xml @@ -11,7 +11,7 @@ io.zipkin.zipkin2 zipkin-collector-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-collector-kafka diff --git a/zipkin-collector/pom.xml b/zipkin-collector/pom.xml index 8bc2c0178b..c49bdf98f4 100644 --- a/zipkin-collector/pom.xml +++ b/zipkin-collector/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT io.zipkin.zipkin2 diff --git a/zipkin-collector/rabbitmq/pom.xml b/zipkin-collector/rabbitmq/pom.xml index 0a077c87f4..348528caa7 100644 --- a/zipkin-collector/rabbitmq/pom.xml +++ b/zipkin-collector/rabbitmq/pom.xml @@ -11,7 +11,7 @@ io.zipkin.zipkin2 zipkin-collector-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-collector-rabbitmq diff --git a/zipkin-collector/scribe/pom.xml b/zipkin-collector/scribe/pom.xml index b305104638..d96288cfb8 100644 --- a/zipkin-collector/scribe/pom.xml +++ b/zipkin-collector/scribe/pom.xml @@ -11,7 +11,7 @@ io.zipkin.zipkin2 zipkin-collector-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-collector-scribe diff --git a/zipkin-junit5/pom.xml b/zipkin-junit5/pom.xml index aa0a177d4d..c421a2f293 100644 --- a/zipkin-junit5/pom.xml +++ b/zipkin-junit5/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT io.zipkin.zipkin2 diff --git a/zipkin-lens/pom.xml b/zipkin-lens/pom.xml index 622eb789f3..e179116aed 100644 --- a/zipkin-lens/pom.xml +++ b/zipkin-lens/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-lens diff --git a/zipkin-server/README.md b/zipkin-server/README.md index 3e2755965a..74485db96a 100644 --- a/zipkin-server/README.md +++ b/zipkin-server/README.md @@ -251,7 +251,8 @@ $ STORAGE_TYPE=cassandra3 java -jar zipkin.jar \ ### Elasticsearch Storage Zipkin's [Elasticsearch storage component](../zipkin-storage/elasticsearch) -supports versions 7-8.x and applies when `STORAGE_TYPE` is set to `elasticsearch` +supports versions Elasticsearch 7-8.x and OpenSearch 2.x and applies when +`STORAGE_TYPE` is set to `elasticsearch` The following apply when `STORAGE_TYPE` is set to `elasticsearch`: @@ -259,7 +260,7 @@ The following apply when `STORAGE_TYPE` is set to `elasticsearch`: Defaults to "http://localhost:9200". * `ES_PIPELINE`: Indicates the ingest pipeline used before spans are indexed. No default. * `ES_TIMEOUT`: Controls the connect, read and write socket timeouts (in milliseconds) for - Elasticsearch API. Defaults to 10000 (10 seconds) + Elasticsearch / OpenSearch API. Defaults to 10000 (10 seconds) * `ES_INDEX`: The index prefix to use when generating daily index names. Defaults to zipkin. * `ES_DATE_SEPARATOR`: The date separator to use when generating daily index names. Defaults to '-'. * `ES_INDEX_SHARDS`: The number of shards to split the index into. Each shard and its replicas @@ -278,9 +279,9 @@ The following apply when `STORAGE_TYPE` is set to `elasticsearch`: you set this to false, you choose to troubleshoot your own data or migration problems as opposed to relying on the community for this. Defaults to true. - * `ES_USERNAME` and `ES_PASSWORD`: Elasticsearch basic authentication, which defaults to empty string. + * `ES_USERNAME` and `ES_PASSWORD`: Elasticsearch / OpenSearch basic authentication, which defaults to empty string. Use when X-Pack security (formerly Shield) is in place. - * `ES_CREDENTIALS_FILE`: The location of a file containing Elasticsearch basic authentication + * `ES_CREDENTIALS_FILE`: The location of a file containing Elasticsearch / OpenSearch basic authentication credentials, as properties. The username property is `zipkin.storage.elasticsearch.username`, password `zipkin.storage.elasticsearch.password`. This file is reloaded periodically, using `ES_CREDENTIALS_REFRESH_INTERVAL` @@ -289,7 +290,7 @@ The following apply when `STORAGE_TYPE` is set to `elasticsearch`: * `ES_CREDENTIALS_REFRESH_INTERVAL`: Credentials refresh interval in seconds, which defaults to 1 second. This is the maximum amount of time spans will drop due to stale credentials. Any errors reading the credentials file occur in logs at this rate. - * `ES_HTTP_LOGGING`: When set, controls the volume of HTTP logging of the Elasticsearch API. + * `ES_HTTP_LOGGING`: When set, controls the volume of HTTP logging of the Elasticsearch / OpenSearch API. Options are BASIC, HEADERS, BODY * `ES_SSL_NO_VERIFY`: When true, disables the verification of server's key certificate chain. This is not appropriate for production. Defaults to false. @@ -303,13 +304,13 @@ To connect normally: $ STORAGE_TYPE=elasticsearch ES_HOSTS=http://myhost:9200 java -jar zipkin.jar ``` -To log Elasticsearch API requests: +To log Elasticsearch / OpenSearch API requests: ```bash $ STORAGE_TYPE=elasticsearch ES_HTTP_LOGGING=BASIC java -jar zipkin.jar ``` #### Using a custom Key Store or Trust Store (SSL) -If your Elasticsearch endpoint customized SSL configuration (for example self-signed) certificates, +If your Elasticsearch / OpenSearch endpoint customized SSL configuration (for example self-signed) certificates, you can use any of the following [subset of JSSE properties](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#T6) to connect. * javax.net.ssl.keyStore @@ -326,13 +327,13 @@ $ STORAGE_TYPE=elasticsearch java $JAVA_OPTS -jar zipkin.jar ``` Under the scenes, these map to properties prefixed `zipkin.storage.elasticsearch.ssl.`, which affect -the Armeria client used to connect to Elasticsearch. +the Armeria client used to connect to Elasticsearch / OpenSearch. The above properties allow the most common SSL setup to work out of box. If you need more customization, please make a comment in [this issue](https://github.com/openzipkin/zipkin/issues/2774). #### Automatic Index Creation -Zipkin will automatically create new indices as needed. Elasticsearch by default [allows](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-creation) automatic creation of said indices, though your local install may have been configured to disallow it. You can verify this in the cluster settings: `action.auto_create_index: false`. +Zipkin will automatically create new indices as needed. Elasticsearch / OpenSearch by default [allows](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-creation) automatic creation of said indices, though your local install may have been configured to disallow it. You can verify this in the cluster settings: `action.auto_create_index: false`. ### Legacy (v1) storage components The following components are no longer encouraged, but exist to help aid diff --git a/zipkin-server/pom.xml b/zipkin-server/pom.xml index b3b3531a1f..76c456e571 100644 --- a/zipkin-server/pom.xml +++ b/zipkin-server/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-server diff --git a/zipkin-storage/cassandra/README.md b/zipkin-storage/cassandra/README.md index 463856b2a9..c214e68115 100644 --- a/zipkin-storage/cassandra/README.md +++ b/zipkin-storage/cassandra/README.md @@ -21,7 +21,7 @@ If you want to see requests and latency, set the logging category "com.datastax.oss.driver.internal.core.tracker.RequestLogger" to DEBUG. TRACE includes query values. -See [Request Logger](https://docs.datastax.com/en/developer/java-driver/4.9/manual/core/request_tracker/#request-logger) for more details. +See [Request Logger](https://github.com/apache/cassandra-java-driver/tree/4.x/manual/core/request_tracker#request-logger) for more details. ## Testing This module conditionally runs integration tests against a local Cassandra instance. diff --git a/zipkin-storage/cassandra/pom.xml b/zipkin-storage/cassandra/pom.xml index 61b5d381da..25e7d4fd85 100644 --- a/zipkin-storage/cassandra/pom.xml +++ b/zipkin-storage/cassandra/pom.xml @@ -11,7 +11,7 @@ io.zipkin.zipkin2 zipkin-storage-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-storage-cassandra diff --git a/zipkin-storage/elasticsearch/README.md b/zipkin-storage/elasticsearch/README.md index dfb9446fa2..1e7cd569bd 100644 --- a/zipkin-storage/elasticsearch/README.md +++ b/zipkin-storage/elasticsearch/README.md @@ -3,7 +3,7 @@ This is a plugin to the Elasticsearch storage component, which uses HTTP by way of [Armeria](https://github.com/line/armeria) and [Jackson](https://github.com/FasterXML/jackson). This uses Elasticsearch 5+ -features, but is tested against Elasticsearch 7-8.x. +features, but is tested against Elasticsearch 7-8.x and OpenSearch 2.x. ## Multiple hosts Most users will supply a DNS name that's mapped to multiple A or AAAA @@ -26,9 +26,9 @@ with one difference described below. We add a "timestamp_millis" field to aid in integration with other tools. ### Timestamps -Zipkin's timestamps are in epoch microseconds, which is not a supported date type in Elasticsearch. +Zipkin's timestamps are in epoch microseconds, which is not a supported date type in Elasticsearch / OpenSearch. In consideration of tools like like Kibana, this component adds "timestamp_millis" when writing -spans. This is mapped to the Elasticsearch date type, so can be used to any date-based queries. +spans. This is mapped to the Elasticsearch / OpenSearch date type, so can be used to any date-based queries. ## Indexes Spans are stored into daily indices, for example spans with a timestamp @@ -68,8 +68,9 @@ $ curl -s 'localhost:9200/zipkin*span-2017-08-11/_search?q=_q:error=500' The reason for special casing is around dotted name constraints. Tags are stored as a dictionary. Some keys include inconsistent number of dots -(ex "error" and "error.message"). Elasticsearch cannot index these as it -inteprets them as fields, and dots in fields imply an object path. +(ex "error" and "error.message"). Elasticsearch / OpenSearch cannot index +these as it inteprets them as fields, and dots in fields imply an object +path. ### Trace Identifiers Unless `ElasticsearchStorage.Builder.strictTraceId` is set to false, @@ -122,9 +123,9 @@ be written, nor analyzed. ### Composable Index Template Elasticsearch 7.8 introduces [composable templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html) and -deprecates [legacy/v1 templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) used in version prior. -Merging of multiple templates with matching index patterns is no longer allowed, and Elasticsearch will return error on PUT of the second template -with matching index pattern and priority. Templates with matching index patterns are required to have different priorities, and Elasticsearch will +deprecates [legacy/v1 templates](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) used in version prior (fully supported by OpenSearch). +Merging of multiple templates with matching index patterns is no longer allowed, and Elasticsearch / OpenSearch will return error on PUT of the second template +with matching index pattern and priority. Templates with matching index patterns are required to have different priorities, and Elasticsearch / OpenSearch will only use the template with the highest priority. This also means that [secondary template](https://gist.github.com/codefromthecrypt/1af1259102e7a2da1b3c9103565165d7) is no longer achievable. @@ -133,8 +134,16 @@ providing `ES_TEMPLATE_PRIORITY` environment variable. ## Customizing the ingest pipeline +### Elasticsearch + You can setup an [ingest pipeline](https://www.elastic.co/guide/en/elasticsearch/reference/master/pipeline.html) to perform custom processing. +### OpenSearch + +You can setup an [ingest pipeline](https://opensearch.org/docs/latest/ingest-pipelines/) to perform custom processing. + +### Setting up ingest pipeline + Here's an example, which you'd setup prior to configuring Zipkin to use it via `ElasticsearchStorage.Builder.pipeline` @@ -162,7 +171,12 @@ to reduce load. This is implemented by [DelayLimiter](../../zipkin/src/main/java/zipkin2/internal/DelayLimiter.java) ## Data retention -Zipkin-server does not handle retention management of the trace data. Use the tools recommended by ElasticSearch to manage data retention, or your cluster -will grow indefinitely! +Zipkin-server does not handle retention management of the trace data. Use the tools recommended by to manage data retention, or your cluster will grow indefinitely! + +### Elasticsearch * [Curator](https://www.elastic.co/guide/en/elasticsearch/client/curator/current/index.html) * [Index Lifecycle Management](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/index-lifecycle-management.html) + +### OpenSearch + * [Curator](https://github.com/flant/curator-opensearch) + * [Index Lifecycle Management](https://opensearch.org/docs/latest/im-plugin/ism/index/) \ No newline at end of file diff --git a/zipkin-storage/elasticsearch/pom.xml b/zipkin-storage/elasticsearch/pom.xml index b256deda4e..249ec846ed 100644 --- a/zipkin-storage/elasticsearch/pom.xml +++ b/zipkin-storage/elasticsearch/pom.xml @@ -11,11 +11,11 @@ io.zipkin.zipkin2 zipkin-storage-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-storage-elasticsearch - Storage: Elasticsearch (V2) + Storage: Elasticsearch / OpenSearch (V2) ${project.basedir}/../.. diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/BaseVersion.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/BaseVersion.java new file mode 100644 index 0000000000..98fd4e54cc --- /dev/null +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/BaseVersion.java @@ -0,0 +1,108 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.elasticsearch; + +import static zipkin2.elasticsearch.internal.JsonReaders.enterPath; + +import java.io.IOException; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.linecorp.armeria.common.AggregatedHttpRequest; +import com.linecorp.armeria.common.HttpMethod; + +import zipkin2.elasticsearch.internal.client.HttpCall; + +/** + * Base version for both Elasticsearch and OpenSearch distributions. + */ +public abstract class BaseVersion { + final int major, minor; + + BaseVersion(int major, int minor) { + this.major = major; + this.minor = minor; + } + + /** + * Gets the version for particular distribution, returns either {@link ElasticsearchVersion} + * or {@link OpensearchVersion} instance. + * @param http the HTTP client + * @return either {@link ElasticsearchVersion} or {@link OpensearchVersion} instance + * @throws IOException in case of I/O errors + */ + static BaseVersion get(HttpCall.Factory http) throws IOException { + return Parser.INSTANCE.get(http); + } + + /** + * Does this version of Elasticsearch / OpenSearch still support mapping types? + * @return "true" if mapping types are supported, "false" otherwise + */ + public abstract boolean supportsTypes(); + + enum Parser implements HttpCall.BodyConverter { + INSTANCE; + + final Pattern REGEX = Pattern.compile("(\\d+)\\.(\\d+).*"); + + BaseVersion get(HttpCall.Factory callFactory) throws IOException { + AggregatedHttpRequest getNode = AggregatedHttpRequest.of(HttpMethod.GET, "/"); + BaseVersion version = callFactory.newCall(getNode, this, "get-node").execute(); + if (version == null) { + throw new IllegalArgumentException("No content reading Elasticsearch/OpenSearch version"); + } + return version; + } + + @Override + public BaseVersion convert(JsonParser parser, Supplier contentString) { + String version = null; + String distribution = null; + try { + if (enterPath(parser, "version") != null) { + while (parser.nextToken() != null) { + if (parser.currentToken() == JsonToken.VALUE_STRING) { + if (parser.currentName() == "distribution") { + distribution = parser.getText(); + } else if (parser.currentName() == "number") { + version = parser.getText(); + } + } + } + } + } catch (RuntimeException | IOException possiblyParseException) { + // EmptyCatch ignored + } + + if (version == null) { + throw new IllegalArgumentException( + ".version.number not found in response: " + contentString.get()); + } + + Matcher matcher = REGEX.matcher(version); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid .version.number: " + version); + } + + try { + int major = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + if ("opensearch".equalsIgnoreCase(distribution)) { + return new OpensearchVersion(major, minor); + } else { + return new ElasticsearchVersion(major, minor); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid .version.number: " + version + + ", for .version.distribution:" + distribution); + } + } + } +} diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchSpecificTemplates.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchSpecificTemplates.java new file mode 100644 index 0000000000..cc535a4618 --- /dev/null +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchSpecificTemplates.java @@ -0,0 +1,107 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.elasticsearch; + +import static zipkin2.elasticsearch.ElasticsearchVersion.V5_0; +import static zipkin2.elasticsearch.ElasticsearchVersion.V6_0; +import static zipkin2.elasticsearch.ElasticsearchVersion.V6_7; +import static zipkin2.elasticsearch.ElasticsearchVersion.V7_0; +import static zipkin2.elasticsearch.ElasticsearchVersion.V7_8; +import static zipkin2.elasticsearch.ElasticsearchVersion.V9_0; + +import zipkin2.internal.Nullable; + +final class ElasticsearchSpecificTemplates extends VersionSpecificTemplates { + static class DistributionTemplate extends DistributionSpecificTemplates { + private final ElasticsearchVersion version; + + DistributionTemplate(ElasticsearchVersion version) { + this.version = version; + } + + @Override String indexTemplatesUrl(String indexPrefix, String type, @Nullable Integer templatePriority) { + if (version.compareTo(V7_8) >= 0 && templatePriority != null) { + return "/_index_template/" + indexPrefix + type + "_template"; + } + if (version.compareTo(V6_7) >= 0 && version.compareTo(V7_0) < 0) { + // because deprecation warning on 6 to prepare for 7: + // + // [types removal] The parameter include_type_name should be explicitly specified in get + // template requests to prepare for 7.0. In 7.0 include_type_name will default to 'false', + // which means responses will omit the type name in mapping definitions. + // + // The parameter include_type_name was added in 6.7. Using this with ES older than + // 6.7 will result in unrecognized parameter: [include_type_name]. + return "/_template/" + indexPrefix + type + "_template?include_type_name=true"; + } + + return "/_template/" + indexPrefix + type + "_template"; + } + + @Override char indexTypeDelimiter() { + return ElasticsearchSpecificTemplates.indexTypeDelimiter(version); + } + + @Override + IndexTemplates get(String indexPrefix, int indexReplicas, int indexShards, + boolean searchEnabled, boolean strictTraceId, Integer templatePriority) { + return new ElasticsearchSpecificTemplates(indexPrefix, indexReplicas, indexShards, + searchEnabled, strictTraceId, templatePriority).get(version); + } + } + + ElasticsearchSpecificTemplates(String indexPrefix, int indexReplicas, int indexShards, + boolean searchEnabled, boolean strictTraceId, Integer templatePriority) { + super(indexPrefix, indexReplicas,indexShards, searchEnabled, strictTraceId, templatePriority); + } + + @Override String indexPattern(String type, ElasticsearchVersion version) { + return '"' + + (version.compareTo(V6_0) < 0 ? "template" : "index_patterns") + + "\": \"" + + indexPrefix + + indexTypeDelimiter(version) + + type + + "-*" + + "\""; + } + + @Override boolean useComposableTemplate(ElasticsearchVersion version) { + return (version.compareTo(V7_8) >= 0 && templatePriority != null); + } + + /** + * This returns a delimiter based on what's supported by the Elasticsearch version. + * + *

Starting in Elasticsearch 7.x, colons are no longer allowed in index names. This logic will + * make sure the pattern in our index template doesn't use them either. + * + *

See https://github.com/openzipkin/zipkin/issues/2219 + */ + static char indexTypeDelimiter(ElasticsearchVersion version) { + return version.compareTo(V7_0) < 0 ? ':' : '-'; + } + + @Override String maybeWrap(String type, ElasticsearchVersion version, String json) { + // ES 7.x defaults include_type_name to false https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes-7.0.html#_literal_include_type_name_literal_now_defaults_to_literal_false_literal + if (version.compareTo(V7_0) >= 0) return json; + return " \"" + type + "\": {\n " + json.replace("\n", "\n ") + " }\n"; + } + + @Override IndexTemplates get(ElasticsearchVersion version) { + if (version.compareTo(V5_0) < 0 || version.compareTo(V9_0) >= 0) { + throw new IllegalArgumentException( + "Elasticsearch versions 5-8.x are supported, was: " + version); + } + return IndexTemplates.newBuilder() + .version(version) + .indexTypeDelimiter(indexTypeDelimiter(version)) + .span(spanIndexTemplate(version)) + .dependency(dependencyTemplate(version)) + .autocomplete(autocompleteTemplate(version)) + .build(); + } +} diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchStorage.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchStorage.java index 0658681a95..067226ce49 100644 --- a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchStorage.java +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchStorage.java @@ -36,9 +36,6 @@ import zipkin2.storage.Traces; import static com.linecorp.armeria.common.HttpMethod.GET; -import static zipkin2.elasticsearch.ElasticsearchVersion.V6_7; -import static zipkin2.elasticsearch.ElasticsearchVersion.V7_0; -import static zipkin2.elasticsearch.ElasticsearchVersion.V7_8; import static zipkin2.elasticsearch.EnsureIndexTemplate.ensureIndexTemplate; import static zipkin2.elasticsearch.VersionSpecificTemplates.TYPE_AUTOCOMPLETE; import static zipkin2.elasticsearch.VersionSpecificTemplates.TYPE_DEPENDENCY; @@ -240,17 +237,17 @@ public final Builder dateSeparator(char dateSeparator) { return new ElasticsearchSpanConsumer(this); } - /** Returns the Elasticsearch version of the connected cluster. Internal use only */ - @Memoized public ElasticsearchVersion version() { + /** Returns the Elasticsearch / OpenSearch version of the connected cluster. Internal use only */ + @Memoized public BaseVersion version() { try { - return ElasticsearchVersion.get(http()); + return BaseVersion.get(http()); } catch (IOException e) { throw new UncheckedIOException(e); } } char indexTypeDelimiter() { - return VersionSpecificTemplates.indexTypeDelimiter(version()); + return VersionSpecificTemplates.forVersion(version()).indexTypeDelimiter(); } /** This is an internal blocking call, only used in tests. */ @@ -337,35 +334,20 @@ IndexTemplates doEnsureIndexTemplates() { } } - IndexTemplates versionSpecificTemplates(ElasticsearchVersion version) { - return new VersionSpecificTemplates( + IndexTemplates versionSpecificTemplates(BaseVersion version) { + return VersionSpecificTemplates.forVersion(version).get( indexNameFormatter().index(), indexReplicas(), indexShards(), searchEnabled(), strictTraceId(), templatePriority() - ).get(version); + ); } String buildUrl(IndexTemplates templates, String type) { String indexPrefix = indexNameFormatter().index() + templates.indexTypeDelimiter(); - - if (version().compareTo(V7_8) >= 0 && templatePriority() != null) { - return "/_index_template/" + indexPrefix + type + "_template"; - } - if (version().compareTo(V6_7) >= 0 && version().compareTo(V7_0) < 0) { - // because deprecation warning on 6 to prepare for 7: - // - // [types removal] The parameter include_type_name should be explicitly specified in get - // template requests to prepare for 7.0. In 7.0 include_type_name will default to 'false', - // which means responses will omit the type name in mapping definitions. - // - // The parameter include_type_name was added in 6.7. Using this with ES older than - // 6.7 will result in unrecognized parameter: [include_type_name]. - return "/_template/" + indexPrefix + type + "_template?include_type_name=true"; - } - return "/_template/" + indexPrefix + type + "_template"; + return VersionSpecificTemplates.forVersion(version()).indexTemplatesUrl(indexPrefix, type, templatePriority()); } @Override public final String toString() { diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchVersion.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchVersion.java index e7d17ce04c..e7c821e0c9 100644 --- a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchVersion.java +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/ElasticsearchVersion.java @@ -4,20 +4,10 @@ */ package zipkin2.elasticsearch; -import com.fasterxml.jackson.core.JsonParser; -import com.linecorp.armeria.common.AggregatedHttpRequest; -import com.linecorp.armeria.common.HttpMethod; -import java.io.IOException; import java.util.Objects; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import zipkin2.elasticsearch.internal.client.HttpCall; - -import static zipkin2.elasticsearch.internal.JsonReaders.enterPath; /** Helps avoid problems comparing versions by number. Ex 7.10 should be > 7.9 */ -public final class ElasticsearchVersion implements Comparable { +public final class ElasticsearchVersion extends BaseVersion implements Comparable { public static final ElasticsearchVersion V5_0 = new ElasticsearchVersion(5, 0); public static final ElasticsearchVersion V6_0 = new ElasticsearchVersion(6, 0); public static final ElasticsearchVersion V6_7 = new ElasticsearchVersion(6, 7); @@ -25,15 +15,12 @@ public final class ElasticsearchVersion implements Comparable { - INSTANCE; - - final Pattern REGEX = Pattern.compile("(\\d+)\\.(\\d+).*"); - ElasticsearchVersion get(HttpCall.Factory callFactory) throws IOException { - AggregatedHttpRequest getNode = AggregatedHttpRequest.of(HttpMethod.GET, "/"); - ElasticsearchVersion version = callFactory.newCall(getNode, this, "get-node").execute(); - if (version == null) { - throw new IllegalArgumentException("No content reading Elasticsearch version"); - } - return version; - } - - @Override - public ElasticsearchVersion convert(JsonParser parser, Supplier contentString) { - String version = null; - try { - if (enterPath(parser, "version", "number") != null) version = parser.getText(); - } catch (RuntimeException | IOException possiblyParseException) { - // EmptyCatch ignored - } - if (version == null) { - throw new IllegalArgumentException( - ".version.number not found in response: " + contentString.get()); - } - - Matcher matcher = REGEX.matcher(version); - if (!matcher.matches()) { - throw new IllegalArgumentException("Invalid .version.number: " + version); - } - - try { - int major = Integer.parseInt(matcher.group(1)); - int minor = Integer.parseInt(matcher.group(2)); - return new ElasticsearchVersion(major, minor); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid .version.number: " + version); - } - } - } } diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/IndexTemplates.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/IndexTemplates.java index 13b056e095..eb61a49973 100644 --- a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/IndexTemplates.java +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/IndexTemplates.java @@ -12,7 +12,7 @@ static Builder newBuilder() { return new AutoValue_IndexTemplates.Builder(); } - abstract ElasticsearchVersion version(); + abstract BaseVersion version(); abstract char indexTypeDelimiter(); @@ -24,7 +24,7 @@ static Builder newBuilder() { @AutoValue.Builder interface Builder { - Builder version(ElasticsearchVersion version); + Builder version(BaseVersion version); Builder indexTypeDelimiter(char indexTypeDelimiter); diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/OpensearchSpecificTemplates.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/OpensearchSpecificTemplates.java new file mode 100644 index 0000000000..cb1c4b3632 --- /dev/null +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/OpensearchSpecificTemplates.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package zipkin2.elasticsearch; + +import static zipkin2.elasticsearch.OpensearchVersion.V1_0; +import static zipkin2.elasticsearch.OpensearchVersion.V4_0; + +import zipkin2.internal.Nullable; + +final class OpensearchSpecificTemplates extends VersionSpecificTemplates { + static class DistributionTemplate extends DistributionSpecificTemplates { + private final OpensearchVersion version; + + DistributionTemplate(OpensearchVersion version) { + this.version = version; + } + + @Override String indexTemplatesUrl(String indexPrefix, String type, @Nullable Integer templatePriority) { + if (version.compareTo(V1_0) >= 0 && templatePriority != null) { + return "/_index_template/" + indexPrefix + type + "_template"; + } + + return "/_template/" + indexPrefix + type + "_template"; + } + + @Override char indexTypeDelimiter() { + return OpensearchSpecificTemplates.indexTypeDelimiter(version); + } + + @Override + IndexTemplates get(String indexPrefix, int indexReplicas, int indexShards, + boolean searchEnabled, boolean strictTraceId, Integer templatePriority) { + return new OpensearchSpecificTemplates(indexPrefix, indexReplicas, indexShards, + searchEnabled, strictTraceId, templatePriority).get(version); + } + } + + OpensearchSpecificTemplates(String indexPrefix, int indexReplicas, int indexShards, + boolean searchEnabled, boolean strictTraceId, Integer templatePriority) { + super(indexPrefix, indexReplicas,indexShards, searchEnabled, strictTraceId, templatePriority); + } + + @Override String indexPattern(String type, OpensearchVersion version) { + return '"' + + "index_patterns" + + "\": \"" + + indexPrefix + + indexTypeDelimiter(version) + + type + + "-*" + + "\""; + } + + static char indexTypeDelimiter(OpensearchVersion version) { + return '-'; + } + + @Override boolean useComposableTemplate(OpensearchVersion version) { + return (templatePriority != null); + } + + @Override String maybeWrap(String type, OpensearchVersion version, String json) { + return json; + } + + @Override IndexTemplates get(OpensearchVersion version) { + if (version.compareTo(V1_0) < 0 || version.compareTo(V4_0) >= 0) { + throw new IllegalArgumentException( + "OpenSearch versions 1-3.x are supported, was: " + version); + } + return IndexTemplates.newBuilder() + .version(version) + .indexTypeDelimiter(indexTypeDelimiter(version)) + .span(spanIndexTemplate(version)) + .dependency(dependencyTemplate(version)) + .autocomplete(autocompleteTemplate(version)) + .build(); + } +} diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/OpensearchVersion.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/OpensearchVersion.java new file mode 100644 index 0000000000..b7f1e1714d --- /dev/null +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/OpensearchVersion.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.elasticsearch; + +import java.util.Objects; + +/** Helps avoid problems comparing versions by number. Ex 2.10 should be > 2.9 */ +public final class OpensearchVersion extends BaseVersion implements Comparable { + public static final OpensearchVersion V1_0 = new OpensearchVersion(1, 0); + public static final OpensearchVersion V2_0 = new OpensearchVersion(2, 0); + public static final OpensearchVersion V3_0 = new OpensearchVersion(2, 0); + public static final OpensearchVersion V4_0 = new OpensearchVersion(4, 0); + + OpensearchVersion(int major, int minor) { + super(major, minor); + } + + @Override public boolean supportsTypes() { + return compareTo(V2_0) < 0; + } + + @Override public int compareTo(OpensearchVersion other) { + if (major < other.major) return -1; + if (major > other.major) return 1; + return Integer.compare(minor, other.minor); + } + + @Override public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof OpensearchVersion)) return false; + OpensearchVersion that = (OpensearchVersion) o; + return this.major == that.major && this.minor == that.minor; + } + + @Override public int hashCode() { + return Objects.hash(major, minor); + } + + @Override public String toString() { + return major + "." + minor; + } +} diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/VersionSpecificTemplates.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/VersionSpecificTemplates.java index b40f66423b..1307cef465 100644 --- a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/VersionSpecificTemplates.java +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/VersionSpecificTemplates.java @@ -4,17 +4,13 @@ */ package zipkin2.elasticsearch; -import static zipkin2.elasticsearch.ElasticsearchVersion.V5_0; -import static zipkin2.elasticsearch.ElasticsearchVersion.V6_0; -import static zipkin2.elasticsearch.ElasticsearchVersion.V7_0; -import static zipkin2.elasticsearch.ElasticsearchVersion.V7_8; -import static zipkin2.elasticsearch.ElasticsearchVersion.V9_0; +import zipkin2.internal.Nullable; /** Returns version-specific index templates */ // TODO: make a main class that spits out the index template using ENV variables for the server, // a parameter for the version, and a parameter for the index type. Ex. // java -cp zipkin-storage-elasticsearch.jar zipkin2.elasticsearch.VersionSpecificTemplates 6.7 span -final class VersionSpecificTemplates { +abstract class VersionSpecificTemplates { /** Maximum character length constraint of most names, IP literals and IDs. */ static final int SHORT_STRING_LENGTH = 256; static final String TYPE_AUTOCOMPLETE = "autocomplete"; @@ -42,18 +38,7 @@ final class VersionSpecificTemplates { this.templatePriority = templatePriority; } - String indexPattern(String type, ElasticsearchVersion version) { - return '"' - + (version.compareTo(V6_0) < 0 ? "template" : "index_patterns") - + "\": \"" - + indexPrefix - + indexTypeDelimiter(version) - + type - + "-*" - + "\""; - } - - String indexProperties(ElasticsearchVersion version) { + String indexProperties(V version) { // 6.x _all disabled https://www.elastic.co/guide/en/elasticsearch/reference/6.7/breaking-changes-6.0.html#_the_literal__all_literal_meta_field_is_now_disabled_by_default // 7.x _default disallowed https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes-7.0.html#_the_literal__default__literal_mapping_is_no_longer_allowed String result = " \"index.number_of_shards\": " + indexShards + ",\n" @@ -62,7 +47,7 @@ String indexProperties(ElasticsearchVersion version) { return result + "\n"; } - String indexTemplate(ElasticsearchVersion version) { + String indexTemplate(V version) { if (useComposableTemplate(version)) { return "\"template\": {\n"; } @@ -70,7 +55,7 @@ String indexTemplate(ElasticsearchVersion version) { return ""; } - String indexTemplateClosing(ElasticsearchVersion version) { + String indexTemplateClosing(V version) { if (useComposableTemplate(version)) { return "},\n"; } @@ -78,7 +63,7 @@ String indexTemplateClosing(ElasticsearchVersion version) { return ""; } - String templatePriority(ElasticsearchVersion version) { + String templatePriority(V version) { if (useComposableTemplate(version)) { return "\"priority\": " + templatePriority + "\n"; } @@ -86,7 +71,7 @@ String templatePriority(ElasticsearchVersion version) { return ""; } - String beginTemplate(String type, ElasticsearchVersion version) { + String beginTemplate(String type, V version) { return "{\n" + " " + indexPattern(type, version) + ",\n" + indexTemplate(version) @@ -94,14 +79,14 @@ String beginTemplate(String type, ElasticsearchVersion version) { + indexProperties(version); } - String endTemplate(ElasticsearchVersion version) { + String endTemplate(V version) { return indexTemplateClosing(version) + templatePriority(version) + "}"; } /** Templatized due to version differences. Only fields used in search are declared */ - String spanIndexTemplate(ElasticsearchVersion version) { + String spanIndexTemplate(V version) { String result = beginTemplate(TYPE_SPAN, version); String traceIdMapping = KEYWORD; @@ -187,7 +172,7 @@ String spanIndexTemplate(ElasticsearchVersion version) { } /** Templatized due to version differences. Only fields used in search are declared */ - String dependencyTemplate(ElasticsearchVersion version) { + String dependencyTemplate(V version) { return beginTemplate(TYPE_DEPENDENCY, version) + " },\n" + " \"mappings\": {\n" @@ -198,7 +183,7 @@ String dependencyTemplate(ElasticsearchVersion version) { // The key filed of a autocompleteKeys is intentionally names as tagKey since it clashes with the // BodyConverters KEY - String autocompleteTemplate(ElasticsearchVersion version) { + String autocompleteTemplate(V version) { return beginTemplate(TYPE_AUTOCOMPLETE, version) + " },\n" + " \"mappings\": {\n" @@ -211,40 +196,84 @@ String autocompleteTemplate(ElasticsearchVersion version) { + endTemplate(version); } - IndexTemplates get(ElasticsearchVersion version) { - if (version.compareTo(V5_0) < 0 || version.compareTo(V9_0) >= 0) { - throw new IllegalArgumentException( - "Elasticsearch versions 5-8.x are supported, was: " + version); - } - return IndexTemplates.newBuilder() - .version(version) - .indexTypeDelimiter(indexTypeDelimiter(version)) - .span(spanIndexTemplate(version)) - .dependency(dependencyTemplate(version)) - .autocomplete(autocompleteTemplate(version)) - .build(); - } + /** + * Returns index pattern + * @param type type + * @param version distribution version + * @return index pattern + */ + abstract String indexPattern(String type, V version); - boolean useComposableTemplate(ElasticsearchVersion version) { - return (version.compareTo(V7_8) >= 0 && templatePriority != null); - } + /** + * Returns index templates + * @param version distribution version + * @return index templates + */ + abstract IndexTemplates get(V version); + + /** + * Should composable templates be used or not + * @param version distribution version + * @return {@code true} if composable templates should be used, + * {@code false} otherwise + */ + abstract boolean useComposableTemplate(V version); /** - * This returns a delimiter based on what's supported by the Elasticsearch version. - * - *

Starting in Elasticsearch 7.x, colons are no longer allowed in index names. This logic will - * make sure the pattern in our index template doesn't use them either. - * - *

See https://github.com/openzipkin/zipkin/issues/2219 + * Wraps the JSON payload if needed + * @param type type + * @param version distribution version + * @param json JSON payload + * @return wrapped JSON payload if needed */ - static char indexTypeDelimiter(ElasticsearchVersion version) { - return version.compareTo(V7_0) < 0 ? ':' : '-'; + abstract String maybeWrap(String type, V version, String json); + + /** + * Returns distribution specific templates (index templates URL, index + * type delimiter, {@link IndexTemplates}); + */ + abstract static class DistributionSpecificTemplates { + /** + * Returns distribution specific index templates URL + * @param indexPrefix index prefix + * @param type type + * @param templatePriority index template priority + * @return index templates URL + */ + abstract String indexTemplatesUrl(String indexPrefix, String type, @Nullable Integer templatePriority); + + /** + * Returns distribution specific index type delimiter + * @return index type delimiter + */ + abstract char indexTypeDelimiter(); + + /** + * Returns distribution specific index templates + * @param indexPrefix index prefix + * @param indexReplicas number of replicas + * @param indexShards number of shards + * @param searchEnabled search is enabled or disabled + * @param strictTraceId strict trace ID + * @param templatePriority index template priority + * @return index templates + */ + abstract IndexTemplates get(String indexPrefix, int indexReplicas, int indexShards, + boolean searchEnabled, boolean strictTraceId, Integer templatePriority); } - static String maybeWrap(String type, ElasticsearchVersion version, String json) { - // ES 7.x defaults include_type_name to false https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes-7.0.html#_literal_include_type_name_literal_now_defaults_to_literal_false_literal - if (version.compareTo(V7_0) >= 0) return json; - return " \"" + type + "\": {\n " + json.replace("\n", "\n ") + " }\n"; + /** + * Creates a new {@link DistributionSpecificTemplates} instance based on the distribution + * @param version distribution version + * @return {@link OpensearchSpecificTemplates} or {@link ElasticsearchSpecificTemplates} instance + */ + static DistributionSpecificTemplates forVersion(BaseVersion version) { + if (version instanceof ElasticsearchVersion) { + return new ElasticsearchSpecificTemplates.DistributionTemplate((ElasticsearchVersion) version); + } else if (version instanceof OpensearchVersion) { + return new OpensearchSpecificTemplates.DistributionTemplate((OpensearchVersion) version); + } else { + throw new IllegalArgumentException("The distribution version is not supported: " + version); + } } } - diff --git a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/internal/BulkCallBuilder.java b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/internal/BulkCallBuilder.java index 1c66a40eff..57236d2b4b 100644 --- a/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/internal/BulkCallBuilder.java +++ b/zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/internal/BulkCallBuilder.java @@ -27,13 +27,13 @@ import java.util.List; import java.util.concurrent.RejectedExecutionException; import java.util.function.Supplier; + +import zipkin2.elasticsearch.BaseVersion; import zipkin2.elasticsearch.ElasticsearchStorage; -import zipkin2.elasticsearch.ElasticsearchVersion; import zipkin2.elasticsearch.internal.client.HttpCall; import zipkin2.elasticsearch.internal.client.HttpCall.BodyConverter; import static zipkin2.Call.propagateIfFatal; -import static zipkin2.elasticsearch.ElasticsearchVersion.V7_0; import static zipkin2.elasticsearch.internal.JsonSerializers.OBJECT_MAPPER; import static zipkin2.elasticsearch.internal.client.HttpCall.maybeRootCauseReason; @@ -79,9 +79,9 @@ public final class BulkCallBuilder { // Mutated for each call to index final List> entries = new ArrayList<>(); - public BulkCallBuilder(ElasticsearchStorage es, ElasticsearchVersion version, String tag) { + public BulkCallBuilder(ElasticsearchStorage es, BaseVersion version, String tag) { this.tag = tag; - shouldAddType = version.compareTo(V7_0) < 0; + shouldAddType = version.supportsTypes(); http = Internal.instance.http(es); pipeline = es.pipeline(); waitForRefresh = es.flushOnWrites(); diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/BaseVersionTest.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/BaseVersionTest.java new file mode 100644 index 0000000000..7b893bc2f9 --- /dev/null +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/BaseVersionTest.java @@ -0,0 +1,90 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.elasticsearch; + +import com.linecorp.armeria.client.WebClient; +import com.linecorp.armeria.common.AggregatedHttpResponse; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.MediaType; +import com.linecorp.armeria.testing.junit5.server.mock.MockWebServerExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +class BaseVersionTest { + static final AggregatedHttpResponse OPENSEARCH_RESPONSE = AggregatedHttpResponse.of( + HttpStatus.OK, MediaType.JSON_UTF_8, """ + { + "name" : "PV-NhJd", + "cluster_name" : "CollectorDBCluster", + "cluster_uuid" : "UjZaM0fQRC6tkHINCg9y8w", + "version" : { + "distribution" : "opensearch", + "number" : "2.11.1", + "build_type" : "tar", + "build_hash" : "6b1986e964d440be9137eba1413015c31c5a7752", + "build_date" : "2023-11-29T21:43:10.135035992Z", + "build_snapshot" : false, + "lucene_version" : "9.7.0", + "minimum_wire_compatibility_version" : "7.10.0", + "minimum_index_compatibility_version" : "7.0.0" + }, + "tagline" : "The OpenSearch Project: https://opensearch.org/" + } + """); + + static final AggregatedHttpResponse ELASTICSEARCH_RESPONSE = AggregatedHttpResponse.of( + HttpStatus.OK, MediaType.JSON_UTF_8, """ + { + "name" : "zipkin-elasticsearch", + "cluster_name" : "docker-cluster", + "cluster_uuid" : "wByRPgSgTryYl0TZXW4MsA", + "version" : { + "number" : "7.0.1", + "build_flavor" : "default", + "build_type" : "tar", + "build_hash" : "e4efcb5", + "build_date" : "2019-04-29T12:56:03.145736Z", + "build_snapshot" : false, + "lucene_version" : "8.0.0", + "minimum_wire_compatibility_version" : "6.7.0", + "minimum_index_compatibility_version" : "6.0.0-beta1" + }, + "tagline" : "You Know, for Search" + } + """); + + @RegisterExtension static MockWebServerExtension server = new MockWebServerExtension(); + + @BeforeEach void setUp() { + storage = ElasticsearchStorage.newBuilder(() -> WebClient.of(server.httpUri())).build(); + } + + @AfterEach void tearDown() { + storage.close(); + } + + ElasticsearchStorage storage; + + + @Test void opensearch() throws Exception { + server.enqueue(OPENSEARCH_RESPONSE); + + assertThat(storage.version()) + .isInstanceOf(OpensearchVersion.class) + .isEqualTo(new OpensearchVersion(2, 11)); + } + + @Test void elasticsearch() throws Exception { + server.enqueue(ELASTICSEARCH_RESPONSE); + + assertThat(storage.version()) + .isInstanceOf(ElasticsearchVersion.class) + .isEqualTo(new ElasticsearchVersion(7, 0)); + } +} diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/VersionSpecificTemplatesTest.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/ElasticsearchSpecificTemplatesTest.java similarity index 87% rename from zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/VersionSpecificTemplatesTest.java rename to zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/ElasticsearchSpecificTemplatesTest.java index 13d925eb77..df784d3ca5 100644 --- a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/VersionSpecificTemplatesTest.java +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/ElasticsearchSpecificTemplatesTest.java @@ -14,7 +14,7 @@ import static zipkin2.elasticsearch.ElasticsearchVersion.V7_0; import static zipkin2.elasticsearch.ElasticsearchVersion.V7_8; -class VersionSpecificTemplatesTest { +class ElasticsearchSpecificTemplatesTest { static final ElasticsearchVersion V2_4 = new ElasticsearchVersion(2, 4); static final ElasticsearchVersion V6_7 = new ElasticsearchVersion(6, 7); static final ElasticsearchVersion V7_9 = new ElasticsearchVersion(7, 9); @@ -243,4 +243,34 @@ class VersionSpecificTemplatesTest { assertThat(template.span()).contains("analysis"); } + + @Test void indexTemplatesUrl_6x() { + assertThat(VersionSpecificTemplates.forVersion(V6_7).indexTemplatesUrl("idx", "_doc", null)) + .isEqualTo("/_template/idx_doc_template?include_type_name=true"); + } + + @Test void indexTemplatesUrl_6x_withPriority() { + assertThat(VersionSpecificTemplates.forVersion(V6_7).indexTemplatesUrl("idx", "_doc", 1)) + .isEqualTo("/_template/idx_doc_template?include_type_name=true"); + } + + @Test void indexTemplatesUrl_78() { + assertThat(VersionSpecificTemplates.forVersion(V7_8).indexTemplatesUrl("idx", "_doc", null)) + .isEqualTo("/_template/idx_doc_template"); + } + + @Test void indexTemplatesUrl_78_withPriority() { + assertThat(VersionSpecificTemplates.forVersion(V7_8).indexTemplatesUrl("idx", "_doc", 1)) + .isEqualTo("/_index_template/idx_doc_template"); + } + + @Test void indexTemplatesUrl_79() { + assertThat(VersionSpecificTemplates.forVersion(V7_9).indexTemplatesUrl("idx", "_doc", null)) + .isEqualTo("/_template/idx_doc_template"); + } + + @Test void indexTemplatesUrl_79_withPriority() { + assertThat(VersionSpecificTemplates.forVersion(V7_9).indexTemplatesUrl("idx", "_doc", 1)) + .isEqualTo("/_index_template/idx_doc_template"); + } } diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/ElasticsearchStorageTest.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/ElasticsearchStorageTest.java index ab6c5a72b9..5ae22600c9 100644 --- a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/ElasticsearchStorageTest.java +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/ElasticsearchStorageTest.java @@ -134,7 +134,7 @@ class ElasticsearchStorageTest { CheckResult result = storage.check(); assertThat(result.ok()).isFalse(); assertThat(result.error().getMessage()) - .isEqualTo("No content reading Elasticsearch version"); + .isEqualTo("No content reading Elasticsearch/OpenSearch version"); } // makes sure we don't NPE @@ -146,7 +146,7 @@ class ElasticsearchStorageTest { CheckResult result = storage.check(); assertThat(result.ok()).isFalse(); assertThat(result.error().getMessage()) - .isEqualTo("No content reading Elasticsearch version"); + .isEqualTo("No content reading Elasticsearch/OpenSearch version"); } // TODO: when Armeria's mock server supports it, add a test for IOException diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/OpensearchSpecificTemplatesTest.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/OpensearchSpecificTemplatesTest.java new file mode 100644 index 0000000000..bd30af1c97 --- /dev/null +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/OpensearchSpecificTemplatesTest.java @@ -0,0 +1,116 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.elasticsearch; + +import com.linecorp.armeria.client.WebClient; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +class OpensearchSpecificTemplatesTest { + static final OpensearchVersion V0_3 = new OpensearchVersion(0, 3); + static final OpensearchVersion V1_3 = new OpensearchVersion(1, 3); + static final OpensearchVersion V2_0 = new OpensearchVersion(2, 0); + + ElasticsearchStorage storage = + ElasticsearchStorage.newBuilder(() -> mock(WebClient.class)).build(); + + /** Unsupported, but we should test that parsing works */ + @Test void version_unsupported() { + assertThatThrownBy(() -> storage.versionSpecificTemplates(V0_3)) + .hasMessage("OpenSearch versions 1-3.x are supported, was: 0.3"); + } + + @Test void version2() { + IndexTemplates template = storage.versionSpecificTemplates(V2_0); + + assertThat(template.version()).isEqualTo(V2_0); + assertThat(template.autocomplete()) + .withFailMessage("Starting at v7.x, we delimit index and type with hyphen") + .contains("\"index_patterns\": \"zipkin-autocomplete-*\""); + assertThat(template.autocomplete()) + .withFailMessage("7.x does not support the key index.mapper.dynamic") + .doesNotContain("\"index.mapper.dynamic\": false"); + } + + @Test void version2_doesntWrapPropertiesWithType() { + IndexTemplates template = storage.versionSpecificTemplates(V2_0); + + assertThat(template.dependency()).contains(""" + "mappings": { + "enabled": false + }\ + """); + + assertThat(template.autocomplete()).contains(""" + "mappings": { + "enabled": true, + "properties": { + "tagKey": { "type": "keyword", "norms": false }, + "tagValue": { "type": "keyword", "norms": false } + } + }\ + """); + } + + @Test void searchEnabled_minimalSpanIndexing_1x() { + storage = ElasticsearchStorage.newBuilder(() -> mock(WebClient.class)) + .searchEnabled(false) + .build(); + + IndexTemplates template = storage.versionSpecificTemplates(V1_3); + + // doesn't wrap in a type name + assertThat(template.span()) + .contains(""" + "mappings": { + "properties": { + "traceId": { "type": "keyword", "norms": false }, + "annotations": { "enabled": false }, + "tags": { "enabled": false } + } + }\ + """); + } + + @Test void strictTraceId_doesNotIncludeAnalysisSection() { + IndexTemplates template = storage.versionSpecificTemplates(V1_3); + + assertThat(template.span()).doesNotContain("analysis"); + } + + @Test void strictTraceId_false_includesAnalysisForMixedLengthTraceId() { + storage.close(); + storage = ElasticsearchStorage.newBuilder(() -> mock(WebClient.class)) + .strictTraceId(false) + .build(); + + IndexTemplates template = storage.versionSpecificTemplates(V1_3); + + assertThat(template.span()).contains("analysis"); + } + + @Test void indexTemplatesUrl_1x() { + assertThat(VersionSpecificTemplates.forVersion(V1_3).indexTemplatesUrl("idx", "_doc", null)) + .isEqualTo("/_template/idx_doc_template"); + } + + @Test void indexTemplatesUrl_1x_withPriority() { + assertThat(VersionSpecificTemplates.forVersion(V1_3).indexTemplatesUrl("idx", "_doc", 1)) + .isEqualTo("/_index_template/idx_doc_template"); + } + + @Test void indexTemplatesUrl_2x() { + assertThat(VersionSpecificTemplates.forVersion(V2_0).indexTemplatesUrl("idx", "_doc", null)) + .isEqualTo("/_template/idx_doc_template"); + } + + @Test void indexTemplatesUrl_2x_withPriority() { + assertThat(VersionSpecificTemplates.forVersion(V2_0).indexTemplatesUrl("idx", "_doc", 1)) + .isEqualTo("/_index_template/idx_doc_template"); + } +} diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/OpensearchVersionTest.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/OpensearchVersionTest.java new file mode 100644 index 0000000000..51a71085fa --- /dev/null +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/OpensearchVersionTest.java @@ -0,0 +1,109 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.elasticsearch; + +import com.linecorp.armeria.client.WebClient; +import com.linecorp.armeria.common.AggregatedHttpResponse; +import com.linecorp.armeria.common.HttpData; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.MediaType; +import com.linecorp.armeria.common.ResponseHeaders; +import com.linecorp.armeria.testing.junit5.server.mock.MockWebServerExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static zipkin2.elasticsearch.ElasticsearchStorageTest.RESPONSE_UNAUTHORIZED; + +class OpensearchVersionTest { + static final OpensearchVersion V1_3 = new OpensearchVersion(1, 3); + static final OpensearchVersion V2_11 = new OpensearchVersion(2, 11); + + static final AggregatedHttpResponse VERSION_RESPONSE_1 = AggregatedHttpResponse.of( + HttpStatus.OK, MediaType.JSON_UTF_8, """ + { + "name" : "zipkin-elasticsearch", + "cluster_name" : "docker-cluster", + "cluster_uuid" : "wByRPgSgTryYl0TZXW4MsA", + "version" : { + "distribution" : "opensearch", + "number" : "1.3.14", + "build_type" : "tar", + "build_hash" : "21940d8239b50285ef7f98a1762ef281a5b1c7ee", + "build_date" : "2023-12-08T22:13:08.793451Z", + "build_snapshot" : false, + "lucene_version" : "8.10.1", + "minimum_wire_compatibility_version" : "6.8.0", + "minimum_index_compatibility_version" : "6.0.0-beta1" + }, + "tagline" : "The OpenSearch Project: https://opensearch.org/" + } + """); + static final AggregatedHttpResponse VERSION_RESPONSE_2 = AggregatedHttpResponse.of( + HttpStatus.OK, MediaType.JSON_UTF_8, """ + { + "name" : "PV-NhJd", + "cluster_name" : "CollectorDBCluster", + "cluster_uuid" : "UjZaM0fQRC6tkHINCg9y8w", + "version" : { + "distribution" : "opensearch", + "number" : "2.11.1", + "build_type" : "tar", + "build_hash" : "6b1986e964d440be9137eba1413015c31c5a7752", + "build_date" : "2023-11-29T21:43:10.135035992Z", + "build_snapshot" : false, + "lucene_version" : "9.7.0", + "minimum_wire_compatibility_version" : "7.10.0", + "minimum_index_compatibility_version" : "7.0.0" + }, + "tagline" : "The OpenSearch Project: https://opensearch.org/" + } + """); + + @RegisterExtension static MockWebServerExtension server = new MockWebServerExtension(); + + @BeforeEach void setUp() { + storage = ElasticsearchStorage.newBuilder(() -> WebClient.of(server.httpUri())).build(); + } + + @AfterEach void tearDown() { + storage.close(); + } + + ElasticsearchStorage storage; + + @Test void wrongContent() { + server.enqueue(AggregatedHttpResponse.of( + ResponseHeaders.of(HttpStatus.OK), + HttpData.ofUtf8("you got mail"))); + + assertThatThrownBy(() -> OpensearchVersion.get(storage.http())) + .hasMessage(".version.number not found in response: you got mail"); + } + + @Test void unauthorized() { + server.enqueue(RESPONSE_UNAUTHORIZED); + + assertThatThrownBy(() -> ElasticsearchVersion.get(storage.http())) + .hasMessage("User: anonymous is not authorized to perform: es:ESHttpGet"); + } + + @Test void version1() throws Exception { + server.enqueue(VERSION_RESPONSE_1); + + assertThat(OpensearchVersion.get(storage.http())) + .isEqualTo(V1_3); + } + + /** Prove we compare better than a float. A float of 2.10 is the same as 2.1! */ + @Test void version2_10IsGreaterThan_V2_2() { + assertThat(new OpensearchVersion(2, 10)) + .hasToString("2.10") + .isGreaterThan(new OpensearchVersion(2, 2)); + } +} diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchBaseExtension.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchBaseExtension.java new file mode 100644 index 0000000000..a8bb8fad3d --- /dev/null +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchBaseExtension.java @@ -0,0 +1,128 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.elasticsearch.integration; + +import com.linecorp.armeria.client.ClientFactory; +import com.linecorp.armeria.client.ClientOptions; +import com.linecorp.armeria.client.ClientOptionsBuilder; +import com.linecorp.armeria.client.WebClient; +import com.linecorp.armeria.client.WebClientBuilder; +import com.linecorp.armeria.client.logging.ContentPreviewingClient; +import com.linecorp.armeria.client.logging.LoggingClient; +import com.linecorp.armeria.client.logging.LoggingClientBuilder; +import com.linecorp.armeria.common.HttpResponse; +import com.linecorp.armeria.common.logging.LogLevel; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import zipkin2.elasticsearch.ElasticsearchStorage; +import zipkin2.elasticsearch.ElasticsearchStorage.Builder; + +import static org.testcontainers.utility.DockerImageName.parse; +import static zipkin2.elasticsearch.integration.IgnoredDeprecationWarnings.IGNORE_THESE_WARNINGS; + +class ElasticsearchBaseExtension implements BeforeAllCallback, AfterAllCallback { + static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchBaseExtension.class); + + final GenericContainer container; + + ElasticsearchBaseExtension(GenericContainer container) { + this.container = container; + } + + @Override public void beforeAll(ExtensionContext context) { + if (context.getRequiredTestClass().getEnclosingClass() != null) { + // Only run once in outermost scope. + return; + } + + container.start(); + LOGGER.info("Using baseUrl {}", baseUrl()); + } + + @Override public void afterAll(ExtensionContext context) { + if (context.getRequiredTestClass().getEnclosingClass() != null) { + // Only run once in outermost scope. + return; + } + + container.stop(); + } + + Builder computeStorageBuilder() { + WebClientBuilder builder = WebClient.builder(baseUrl()) + // Elasticsearch 7 never returns a response when receiving an HTTP/2 preface instead of the + // more valid behavior of returning a bad request response, so we can't use the preface. + // + // TODO: find or raise a bug with Elastic + .factory(ClientFactory.builder().useHttp2Preface(false).build()); + builder.decorator((delegate, ctx, req) -> { + final HttpResponse response = delegate.execute(ctx, req); + return HttpResponse.from(response.aggregate().thenApply(r -> { + // ES will return a 'warning' response header when using deprecated api, detect this and + // fail early so we can do something about it. + // Example usage: https://github.com/elastic/elasticsearch/blob/3049e55f093487bb582a7e49ad624961415ba31c/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/IndexPrivilegeIntegTests.java#L559 + final String warningHeader = r.headers().get("warning"); + if (warningHeader != null) { + if (IGNORE_THESE_WARNINGS.stream().noneMatch(p -> p.matcher(warningHeader).find())) { + throw new IllegalArgumentException("Detected usage of deprecated API for request " + + req + ":\n" + warningHeader); + } + } + // Convert AggregatedHttpResponse back to HttpResponse. + return r.toHttpResponse(); + })); + }); + + // When ES_DEBUG=true log full headers, request and response body to the category + // com.linecorp.armeria.client.logging + if (Boolean.parseBoolean(System.getenv("ES_DEBUG"))) { + ClientOptionsBuilder options = ClientOptions.builder(); + LoggingClientBuilder loggingBuilder = LoggingClient.builder() + .requestLogLevel(LogLevel.INFO) + .successfulResponseLogLevel(LogLevel.INFO); + options.decorator(loggingBuilder.newDecorator()); + options.decorator(ContentPreviewingClient.newDecorator(Integer.MAX_VALUE)); + builder.options(options.build()); + } + + WebClient client = builder.build(); + return ElasticsearchStorage.newBuilder(new ElasticsearchStorage.LazyHttpClient() { + @Override public WebClient get() { + return client; + } + + @Override public void close() { + client.endpointGroup().close(); + } + + @Override public String toString() { + return client.uri().toString(); + } + }).index("zipkin-test").flushOnWrites(true); + } + + String baseUrl() { + return "http://" + container.getHost() + ":" + container.getMappedPort(9200); + } + + static String index(TestInfo testInfo) { + String result; + if (testInfo.getTestMethod().isPresent()) { + result = testInfo.getTestMethod().get().getName(); + } else { + assert testInfo.getTestClass().isPresent(); + result = testInfo.getTestClass().get().getSimpleName(); + } + result = result.toLowerCase(); + return result.length() <= 48 ? result : result.substring(result.length() - 48); + } +} diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java index 753209fe97..fed027ea28 100644 --- a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ElasticsearchExtension.java @@ -4,114 +4,19 @@ */ package zipkin2.elasticsearch.integration; -import com.linecorp.armeria.client.ClientFactory; -import com.linecorp.armeria.client.ClientOptions; -import com.linecorp.armeria.client.ClientOptionsBuilder; -import com.linecorp.armeria.client.WebClient; -import com.linecorp.armeria.client.WebClientBuilder; -import com.linecorp.armeria.client.logging.ContentPreviewingClient; -import com.linecorp.armeria.client.logging.LoggingClient; -import com.linecorp.armeria.client.logging.LoggingClientBuilder; -import com.linecorp.armeria.common.HttpResponse; -import com.linecorp.armeria.common.logging.LogLevel; -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.Wait; -import zipkin2.elasticsearch.ElasticsearchStorage; -import zipkin2.elasticsearch.ElasticsearchStorage.Builder; import static org.testcontainers.utility.DockerImageName.parse; -import static zipkin2.elasticsearch.integration.IgnoredDeprecationWarnings.IGNORE_THESE_WARNINGS; -class ElasticsearchExtension implements BeforeAllCallback, AfterAllCallback { +class ElasticsearchExtension extends ElasticsearchBaseExtension { static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchExtension.class); - final ElasticsearchContainer container; - ElasticsearchExtension(int majorVersion) { - container = new ElasticsearchContainer(majorVersion); - } - - @Override public void beforeAll(ExtensionContext context) { - if (context.getRequiredTestClass().getEnclosingClass() != null) { - // Only run once in outermost scope. - return; - } - - container.start(); - LOGGER.info("Using baseUrl {}", baseUrl()); - } - - @Override public void afterAll(ExtensionContext context) { - if (context.getRequiredTestClass().getEnclosingClass() != null) { - // Only run once in outermost scope. - return; - } - - container.stop(); - } - - Builder computeStorageBuilder() { - WebClientBuilder builder = WebClient.builder(baseUrl()) - // Elasticsearch 7 never returns a response when receiving an HTTP/2 preface instead of the - // more valid behavior of returning a bad request response, so we can't use the preface. - // - // TODO: find or raise a bug with Elastic - .factory(ClientFactory.builder().useHttp2Preface(false).build()); - builder.decorator((delegate, ctx, req) -> { - final HttpResponse response = delegate.execute(ctx, req); - return HttpResponse.from(response.aggregate().thenApply(r -> { - // ES will return a 'warning' response header when using deprecated api, detect this and - // fail early so we can do something about it. - // Example usage: https://github.com/elastic/elasticsearch/blob/3049e55f093487bb582a7e49ad624961415ba31c/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/IndexPrivilegeIntegTests.java#L559 - final String warningHeader = r.headers().get("warning"); - if (warningHeader != null) { - if (IGNORE_THESE_WARNINGS.stream().noneMatch(p -> p.matcher(warningHeader).find())) { - throw new IllegalArgumentException("Detected usage of deprecated API for request " - + req + ":\n" + warningHeader); - } - } - // Convert AggregatedHttpResponse back to HttpResponse. - return r.toHttpResponse(); - })); - }); - - // When ES_DEBUG=true log full headers, request and response body to the category - // com.linecorp.armeria.client.logging - if (Boolean.parseBoolean(System.getenv("ES_DEBUG"))) { - ClientOptionsBuilder options = ClientOptions.builder(); - LoggingClientBuilder loggingBuilder = LoggingClient.builder() - .requestLogLevel(LogLevel.INFO) - .successfulResponseLogLevel(LogLevel.INFO); - options.decorator(loggingBuilder.newDecorator()); - options.decorator(ContentPreviewingClient.newDecorator(Integer.MAX_VALUE)); - builder.options(options.build()); - } - - WebClient client = builder.build(); - return ElasticsearchStorage.newBuilder(new ElasticsearchStorage.LazyHttpClient() { - @Override public WebClient get() { - return client; - } - - @Override public void close() { - client.endpointGroup().close(); - } - - @Override public String toString() { - return client.uri().toString(); - } - }).index("zipkin-test").flushOnWrites(true); - } - - String baseUrl() { - return "http://" + container.getHost() + ":" + container.getMappedPort(9200); + super(new ElasticsearchContainer(majorVersion)); } // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 @@ -123,16 +28,4 @@ static final class ElasticsearchContainer extends GenericContainer { diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ITOpenSearchStorageV2.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ITOpenSearchStorageV2.java new file mode 100644 index 0000000000..c4265c9647 --- /dev/null +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/ITOpenSearchStorageV2.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.elasticsearch.integration; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.RegisterExtension; +import zipkin2.elasticsearch.ElasticsearchStorage; + +import static zipkin2.elasticsearch.integration.ElasticsearchExtension.index; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Tag("docker") +class ITOpenSearchStorageV2 extends ITElasticsearchStorage { + + @RegisterExtension static OpenSearchExtension opensearch = new OpenSearchExtension(2); + + @Override OpenSearchExtension elasticsearch() { + return opensearch; + } + + @Nested + class ITEnsureIndexTemplate extends zipkin2.elasticsearch.integration.ITEnsureIndexTemplate { + @Override protected ElasticsearchStorage.Builder newStorageBuilder(TestInfo testInfo) { + return elasticsearch().computeStorageBuilder().index(index(testInfo)); + } + } +} diff --git a/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/OpenSearchExtension.java b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/OpenSearchExtension.java new file mode 100644 index 0000000000..302bc2cca3 --- /dev/null +++ b/zipkin-storage/elasticsearch/src/test/java/zipkin2/elasticsearch/integration/OpenSearchExtension.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenZipkin Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package zipkin2.elasticsearch.integration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; + +import static org.testcontainers.utility.DockerImageName.parse; + +class OpenSearchExtension extends ElasticsearchBaseExtension { + static final Logger LOGGER = LoggerFactory.getLogger(OpenSearchExtension.class); + + OpenSearchExtension(int majorVersion) { + super(new OpenSearchContainer(majorVersion)); + } + + // mostly waiting for https://github.com/testcontainers/testcontainers-java/issues/3537 + static final class OpenSearchContainer extends GenericContainer { + OpenSearchContainer(int majorVersion) { + super(parse("ghcr.io/openzipkin/zipkin-opensearch" + majorVersion + ":3.3.0")); + addExposedPort(9200); + waitStrategy = Wait.forHealthcheck(); + withLogConsumer(new Slf4jLogConsumer(LOGGER)); + } + } +} diff --git a/zipkin-storage/mysql-v1/pom.xml b/zipkin-storage/mysql-v1/pom.xml index 622c1054b6..15b88ec59f 100644 --- a/zipkin-storage/mysql-v1/pom.xml +++ b/zipkin-storage/mysql-v1/pom.xml @@ -11,7 +11,7 @@ io.zipkin.zipkin2 zipkin-storage-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT zipkin-storage-mysql-v1 diff --git a/zipkin-storage/pom.xml b/zipkin-storage/pom.xml index a16358c256..cb1a0c0859 100644 --- a/zipkin-storage/pom.xml +++ b/zipkin-storage/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT io.zipkin.zipkin2 diff --git a/zipkin-tests/pom.xml b/zipkin-tests/pom.xml index 885101a139..85f71a4430 100644 --- a/zipkin-tests/pom.xml +++ b/zipkin-tests/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT io.zipkin.zipkin2 diff --git a/zipkin/pom.xml b/zipkin/pom.xml index 90cf803d1a..f408c30895 100644 --- a/zipkin/pom.xml +++ b/zipkin/pom.xml @@ -11,7 +11,7 @@ io.zipkin zipkin-parent - 3.3.2-SNAPSHOT + 3.4.0-SNAPSHOT io.zipkin.zipkin2