From d7f69262368ea27cdf78eb3a870e98abf589ffaf Mon Sep 17 00:00:00 2001 From: Alexander Wert Date: Wed, 31 May 2023 16:16:41 +0200 Subject: [PATCH 01/10] Added ES client 8.x instrumentation Signed-off-by: Alexander Wert --- ...asticsearchClientAsyncInstrumentation.java | 2 +- ...lasticsearchClientSyncInstrumentation.java | 2 +- ...bstractEs6_4ClientInstrumentationTest.java | 4 +- .../pom.xml | 5 +- .../RestClientTransportInstrumentation.java | 66 +++ .../agent/esapiclient/v8_x/package-info.java | 22 + ...ic.apm.agent.sdk.ElasticApmInstrumentation | 1 + .../AbstractElasticsearchJavaTest.java | 33 +- .../apm/esjavaclient/ElasticsearchJavaIT.java | 0 .../test/resources/commons-logging.properties | 0 .../src/test/resources/log4j.properties | 0 ...searchRestClientInstrumentationHelper.java | 35 +- .../EndpointResolutionHelper.java | 537 ++++++++++++++++++ .../esrestclient/RequestEndpointMap.java | 10 + .../AbstractEsClientInstrumentationTest.java | 33 +- .../apm-es-restclient-plugin/pom.xml | 2 +- 16 files changed, 719 insertions(+), 33 deletions(-) rename apm-agent-plugins/apm-es-restclient-plugin/{apm-es-api-client-test => apm-es-restclient-plugin-8_x}/pom.xml (94%) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/RestClientTransportInstrumentation.java create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/package-info.java create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation rename apm-agent-plugins/apm-es-restclient-plugin/{apm-es-api-client-test => apm-es-restclient-plugin-8_x}/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java (93%) rename apm-agent-plugins/apm-es-restclient-plugin/{apm-es-api-client-test => apm-es-restclient-plugin-8_x}/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java (100%) rename apm-agent-plugins/apm-es-restclient-plugin/{apm-es-api-client-test => apm-es-restclient-plugin-8_x}/src/test/resources/commons-logging.properties (100%) rename apm-agent-plugins/apm-es-restclient-plugin/{apm-es-api-client-test => apm-es-restclient-plugin-8_x}/src/test/resources/log4j.properties (100%) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientAsyncInstrumentation.java index e50edf0dc9..9cacaed07b 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientAsyncInstrumentation.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientAsyncInstrumentation.java @@ -66,7 +66,7 @@ public static class ElasticsearchRestClientAsyncAdvice { @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object[] onBeforeExecute(@Advice.Argument(0) Request request, @Advice.Argument(1) ResponseListener responseListener) { - Span span = helper.createClientSpan(request.getMethod(), request.getEndpoint(), request.getEntity()); + Span span = helper.createClientSpan(request, request.getMethod(), request.getEndpoint(), request.getEntity()); if (span != null) { Object[] ret = new Object[2]; ret[0] = span; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientSyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientSyncInstrumentation.java index 1186200f26..e29e2d9a98 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientSyncInstrumentation.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchClientSyncInstrumentation.java @@ -60,7 +60,7 @@ public static class ElasticsearchRestClientSyncAdvice { @Nullable @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object onBeforeExecute(@Advice.Argument(0) Request request) { - return helper.createClientSpan(request.getMethod(), request.getEndpoint(), request.getEntity()); + return helper.createClientSpan(request, request.getMethod(), request.getEndpoint(), request.getEntity()); } @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java index 28e09e1d00..d7c426d4d1 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java @@ -76,13 +76,13 @@ public void testCreateAndDeleteIndex() throws IOException, ExecutionException, I // Create an Index doCreateIndex(new CreateIndexRequest(SECOND_INDEX)); - validateSpanContentAfterIndexCreateRequest(); + validateSpanContentAfterIndexCreateRequest(false); // Delete the index reporter.reset(); doDeleteIndex(new DeleteIndexRequest(SECOND_INDEX)); - validateSpanContentAfterIndexDeleteRequest(); + validateSpanContentAfterIndexDeleteRequest(false); } @Test diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml similarity index 94% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/pom.xml rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml index c62f5f0790..d5791b6d23 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml @@ -7,11 +7,11 @@ 4.0.0 - apm-es-api-client-test + apm-es-restclient-plugin-8_x ${project.groupId}:${project.artifactId} - 8.7.1 + 8.8.0 ${project.basedir}/../../.. @@ -25,6 +25,7 @@ co.elastic.clients elasticsearch-java ${version.elasticsearch-java} + provided com.fasterxml.jackson.core diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/RestClientTransportInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/RestClientTransportInstrumentation.java new file mode 100644 index 0000000000..687237029c --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/RestClientTransportInstrumentation.java @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.esapiclient.v8_x; + +import co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentation; +import co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentationHelper; +import co.elastic.clients.transport.Endpoint; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.elasticsearch.client.Request; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class RestClientTransportInstrumentation extends ElasticsearchRestClientInstrumentation { + + @Override + public ElementMatcher getTypeMatcher() { + return named("co.elastic.clients.transport.rest_client.RestClientTransport"); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("prepareLowLevelRequest") + .and(takesArguments(3)) + .and(takesArgument(1, named("co.elastic.clients.transport.Endpoint"))) + .and(returns(named("org.elasticsearch.client.Request"))); + } + + @Override + public String getAdviceClassName() { + return getClass().getName() + "$RestClientTransportAdvice"; + } + + public static class RestClientTransportAdvice { + + private static final ElasticsearchRestClientInstrumentationHelper helper = ElasticsearchRestClientInstrumentationHelper.get(); + + + @Advice.OnMethodExit(suppress = Throwable.class, inline = false) + public static void onPrepareLowLevelRequest(@Advice.Argument(1) Endpoint endpoint, @Advice.Return Request request) { + helper.registerEndpointId(request, endpoint.id()); + } + + } +} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/package-info.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/package-info.java new file mode 100644 index 0000000000..5bcff42100 --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +@NonnullApi +package co.elastic.apm.agent.esapiclient.v8_x; + +import co.elastic.apm.agent.sdk.NonnullApi; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation new file mode 100644 index 0000000000..2a87403c81 --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -0,0 +1 @@ +co.elastic.apm.agent.esapiclient.v8_x.RestClientTransportInstrumentation diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java similarity index 93% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java index 4c3042b79a..c42cc173ba 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java @@ -19,11 +19,12 @@ package co.elastic.apm.esjavaclient; import co.elastic.apm.agent.esrestclient.AbstractEsClientInstrumentationTest; -import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.tracer.Outcome; import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient; import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.ElasticsearchException; +import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.elasticsearch._types.Refresh; import co.elastic.clients.elasticsearch._types.SearchType; import co.elastic.clients.elasticsearch._types.StoredScript; @@ -134,7 +135,7 @@ public void testTryToDeleteNonExistingIndex() { Span span = reporter.getFirstSpan(); assertThat(span.getOutcome()).isEqualTo(Outcome.FAILURE); - validateSpanContent(span, String.format("Elasticsearch: DELETE /%s", SECOND_INDEX), 404, "DELETE"); + validateSpanContent(span, "Elasticsearch: DELETE /{index}", 404, "DELETE", SECOND_INDEX); } @Test @@ -144,7 +145,7 @@ public void testDocumentScenario() throws Exception { List spans = reporter.getSpans(); try { assertThat(spans).hasSize(1); - validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 201, "PUT"); + validateSpanContent(spans.get(0), "Elasticsearch: PUT /{index}/_doc/{id}", 201, "PUT", INDEX, DOC_ID); // *** RESET *** reporter.reset(); @@ -158,7 +159,7 @@ public void testDocumentScenario() throws Exception { spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span searchSpan = spans.get(0); - validateSpanContent(searchSpan, String.format("Elasticsearch: POST /%s/_search", INDEX), 200, "POST"); + validateSpanContent(searchSpan, "Elasticsearch: POST /{index}/_search", 200, "POST", INDEX); validateDbContextContent(searchSpan, "{\"from\":0,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}"); // *** RESET *** @@ -178,9 +179,9 @@ public void testDocumentScenario() throws Exception { spans = reporter.getSpans(); assertThat(spans).hasSize(2); Span updateSpan = spans.get(0); - validateSpanContent(updateSpan, String.format("Elasticsearch: POST /%s/_update/%s", INDEX, DOC_ID), 200, "POST"); + validateSpanContent(updateSpan, "Elasticsearch: POST /{index}/_update/{id}", 200, "POST", INDEX, DOC_ID); searchSpan = spans.get(1); - validateSpanContent(searchSpan, String.format("Elasticsearch: POST /%s/_search", INDEX), 200, "POST"); + validateSpanContent(searchSpan, "Elasticsearch: POST /{index}/_search", 200, "POST", INDEX); validateDbContextContent(searchSpan, "{}"); // *** RESET *** @@ -190,7 +191,7 @@ public void testDocumentScenario() throws Exception { // 4. Delete document and validate span content. co.elastic.clients.elasticsearch.core.DeleteResponse dr = deleteDocument(); assertThat(dr.result().jsonValue()).isEqualTo("deleted"); - validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 200, "DELETE"); + validateSpanContent(spans.get(0), "Elasticsearch: DELETE /{index}/_doc/{id}", 200, "DELETE", INDEX, DOC_ID); } } @@ -200,7 +201,7 @@ public void testCountRequest_validateSpanContentAndDbContext() throws Exception reporter.reset(); CountRequest countRequest = new CountRequest.Builder().index(INDEX).query(new Query.Builder() - .term(new TermQuery.Builder().field(FOO).value(BAR).build()) + .term(new TermQuery.Builder().field(FOO).value(FieldValue.of(BAR)).build()) .build()).build(); try { @@ -209,7 +210,7 @@ public void testCountRequest_validateSpanContentAndDbContext() throws Exception List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, String.format("Elasticsearch: POST /%s/_count", INDEX), 200, "POST"); + validateSpanContent(span, "Elasticsearch: POST /{index}/_count", 200, "POST", INDEX); validateDbContextContent(span, "{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}}}"); } finally { deleteDocument(); @@ -227,7 +228,7 @@ public void testMultiSearchRequest_validateSpanContentAndDbContext() throws Inte try { doMultiSearchAndSpanValidate(multiSearchRequest, "Elasticsearch: POST /_msearch"); reporter.reset(); - doMultiSearchAndSpanValidate(multiSearchRequestWithIndex, String.format("Elasticsearch: POST /%s/_msearch", INDEX)); + doMultiSearchAndSpanValidate(multiSearchRequestWithIndex, "Elasticsearch: POST /{index}/_msearch"); } finally { deleteDocument(); } @@ -244,7 +245,7 @@ private MsearchRequest.Builder getMultiSearchRequestBuilder() { .query(new Query.Builder() .match(new MatchQuery.Builder() .field(FOO) - .query(BAR) + .query(FieldValue.of(BAR)) .build()) .build()) .build()) @@ -269,7 +270,7 @@ public void testRollupSearch_validateSpanContentAndDbContext() throws Interrupte RollupSearchRequest searchRequest = new RollupSearchRequest.Builder() .index(INDEX) .query(new Query.Builder() - .term(new TermQuery.Builder().field(FOO).value(BAR).build()) + .term(new TermQuery.Builder().field(FOO).value(FieldValue.of(BAR)).build()) .build()) .size(5) .build(); @@ -280,7 +281,7 @@ public void testRollupSearch_validateSpanContentAndDbContext() throws Interrupte List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, String.format("Elasticsearch: POST /%s/_rollup_search", INDEX), 200, "POST"); + validateSpanContent(span, "Elasticsearch: POST /{index}/_rollup_search", 200, "POST", INDEX); validateDbContextContent(span, "{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}"); } finally { deleteDocument(); @@ -301,7 +302,7 @@ public void testSearchTemplateRequest_validateSpanContentAndDbContext() throws I List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, String.format("Elasticsearch: POST /%s/_search/template", INDEX), 200, "POST"); + validateSpanContent(span, "Elasticsearch: POST /{index}/_search/template", 200, "POST", INDEX); validateDbContextContent(span, "{\"id\":\"elastic-search-template\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"}}"); } finally { deleteMustacheScript(); @@ -340,7 +341,7 @@ public void testMultisearchTemplateRequest_validateSpanContentAndDbContext() thr List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, String.format("Elasticsearch: POST /_msearch/template", INDEX), 200, "POST"); + validateSpanContent(span, "Elasticsearch: POST /_msearch/template", 200, "POST"); verifyMultiSearchTemplateSpanContent(span); } finally { deleteMustacheScript(); @@ -484,7 +485,7 @@ private DeleteResponse deleteDocument() throws IOException { private SearchRequest prepareSearchRequestWithTermQuery() { return new SearchRequest.Builder().index(INDEX) .query(new Query.Builder() - .term(new TermQuery.Builder().field(FOO).value(BAR).build()) + .term(new TermQuery.Builder().field(FOO).value(FieldValue.of(BAR)).build()) .build()) .from(0) .size(5) diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java similarity index 100% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/resources/commons-logging.properties b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/resources/commons-logging.properties similarity index 100% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/resources/commons-logging.properties rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/resources/commons-logging.properties diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/resources/log4j.properties b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/resources/log4j.properties similarity index 100% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-api-client-test/src/test/resources/log4j.properties rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/resources/log4j.properties diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java index 8fc920163c..ba3ffe8af6 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java @@ -19,17 +19,17 @@ package co.elastic.apm.agent.esrestclient; import co.elastic.apm.agent.common.util.WildcardMatcher; -import co.elastic.apm.agent.tracer.GlobalTracer; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.tracer.AbstractSpan; +import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.tracer.Span; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.tracer.Tracer; +import co.elastic.apm.agent.tracer.pooling.Allocator; import co.elastic.apm.agent.tracer.pooling.ObjectPool; import co.elastic.apm.agent.util.IOUtils; import co.elastic.apm.agent.util.LoggerUtils; -import co.elastic.apm.agent.tracer.pooling.Allocator; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.elasticsearch.client.Response; @@ -37,8 +37,6 @@ import org.elasticsearch.client.ResponseListener; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.CancellationException; public class ElasticsearchRestClientInstrumentationHelper { @@ -75,8 +73,23 @@ public ResponseListenerWrapper createInstance() { } } + public void registerEndpointId(Object requestObj, String endpointId) { + RequestEndpointMap.requestEndpointIdMap.put(requestObj, endpointId); + } + + @Nullable + public Span createClientSpan(Object requestObj, String method, String endpoint, @Nullable HttpEntity httpEntity) { + String endpointId = RequestEndpointMap.requestEndpointIdMap.remove(requestObj); + return createClientSpan(method, endpoint, httpEntity, endpointId); + } + @Nullable public Span createClientSpan(String method, String endpoint, @Nullable HttpEntity httpEntity) { + return createClientSpan(method, endpoint, httpEntity, null); + } + + @Nullable + private Span createClientSpan(String method, String endpoint, @Nullable HttpEntity httpEntity, @Nullable String endpointId) { final AbstractSpan activeSpan = tracer.getActive(); if (activeSpan == null) { return null; @@ -91,8 +104,14 @@ public Span createClientSpan(String method, String endpoint, @Nullable HttpEn span.withType(SPAN_TYPE) .withSubtype(ELASTICSEARCH) - .withAction(SPAN_ACTION) - .appendToName("Elasticsearch: ").appendToName(method).appendToName(" ").appendToName(endpoint); + .withAction(SPAN_ACTION); + + if (endpointId != null) { + EndpointResolutionHelper.get().enrichSpanWithRouteInformation(span, method, endpointId, endpoint); + } else { + span.appendToName("Elasticsearch: ").appendToName(method).appendToName(" ").appendToName(endpoint); + } + span.getContext().getDb().withType(ELASTICSEARCH); span.getContext().getServiceTarget().withType(ELASTICSEARCH); span.activate(); diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java new file mode 100644 index 0000000000..482a870c75 --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java @@ -0,0 +1,537 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.esrestclient; + +import co.elastic.apm.agent.tracer.AbstractSpan; +import co.elastic.apm.agent.tracer.Span; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EndpointResolutionHelper { + + @Nullable + private static EndpointResolutionHelper INSTANCE; + + protected static EndpointResolutionHelper get() { + if (INSTANCE == null) { + INSTANCE = new EndpointResolutionHelper(); + } + return INSTANCE; + } + + private final Map routesMap = new HashMap<>(); + private final Map regexPatternMap = new ConcurrentHashMap<>(); + + private EndpointResolutionHelper() { + // This map is generated from the Java client code. + // It's a one-time generation that shouldn't require maintenance effort, + // as in the future the ES client will come with a native instrumentation. + routesMap.put("es/async_search.status", new String[]{"/_async_search/status/{id}"}); + routesMap.put("es/indices.analyze", new String[]{"/_analyze", "/{index}/_analyze"}); + routesMap.put("es/sql.clear_cursor", new String[]{"/_sql/close"}); + routesMap.put("es/ml.delete_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}"}); + routesMap.put("es/explain", new String[]{"/{index}/_explain/{id}"}); + routesMap.put("es/cat.thread_pool", new String[]{"/_cat/thread_pool", "/_cat/thread_pool/{thread_pool_patterns}"}); + routesMap.put("es/ml.delete_calendar", new String[]{"/_ml/calendars/{calendar_id}"}); + routesMap.put("es/indices.create_data_stream", new String[]{"/_data_stream/{name}"}); + routesMap.put("es/cat.fielddata", new String[]{"/_cat/fielddata", "/_cat/fielddata/{fields}"}); + routesMap.put("es/security.enroll_node", new String[]{"/_security/enroll/node"}); + routesMap.put("es/slm.get_status", new String[]{"/_slm/status"}); + routesMap.put("es/ml.put_calendar", new String[]{"/_ml/calendars/{calendar_id}"}); + routesMap.put("es/create", new String[]{"/{index}/_create/{id}"}); + routesMap.put("es/ml.preview_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_preview", "/_ml/datafeeds/_preview"}); + routesMap.put("es/indices.put_template", new String[]{"/_template/{name}"}); + routesMap.put("es/nodes.reload_secure_settings", new String[]{"/_nodes/reload_secure_settings", "/_nodes/{node_id}/reload_secure_settings"}); + routesMap.put("es/indices.delete_data_stream", new String[]{"/_data_stream/{name}"}); + routesMap.put("es/transform.schedule_now_transform", new String[]{"/_transform/{transform_id}/_schedule_now"}); + routesMap.put("es/slm.stop", new String[]{"/_slm/stop"}); + routesMap.put("es/rollup.delete_job", new String[]{"/_rollup/job/{id}"}); + routesMap.put("es/cluster.put_component_template", new String[]{"/_component_template/{name}"}); + routesMap.put("es/delete_script", new String[]{"/_scripts/{id}"}); + routesMap.put("es/ml.delete_trained_model", new String[]{"/_ml/trained_models/{model_id}"}); + routesMap.put("es/indices.simulate_template", new String[]{"/_index_template/_simulate", "/_index_template/_simulate/{name}"}); + routesMap.put("es/slm.get_lifecycle", new String[]{"/_slm/policy/{policy_id}", "/_slm/policy"}); + routesMap.put("es/security.enroll_kibana", new String[]{"/_security/enroll/kibana"}); + routesMap.put("es/fleet.search", new String[]{"/{index}/_fleet/_fleet_search"}); + routesMap.put("es/reindex_rethrottle", new String[]{"/_reindex/{task_id}/_rethrottle"}); + routesMap.put("es/ml.update_filter", new String[]{"/_ml/filters/{filter_id}/_update"}); + routesMap.put("es/rollup.get_rollup_caps", new String[]{"/_rollup/data/{id}", "/_rollup/data"}); + routesMap.put("es/ccr.resume_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}/resume"}); + routesMap.put("es/features.get_features", new String[]{"/_features"}); + routesMap.put("es/slm.get_stats", new String[]{"/_slm/stats"}); + routesMap.put("es/indices.clear_cache", new String[]{"/_cache/clear", "/{index}/_cache/clear"}); + routesMap.put("es/cluster.post_voting_config_exclusions", new String[]{"/_cluster/voting_config_exclusions"}); + routesMap.put("es/index", new String[]{"/{index}/_doc/{id}", "/{index}/_doc"}); + routesMap.put("es/cat.pending_tasks", new String[]{"/_cat/pending_tasks"}); + routesMap.put("es/indices.promote_data_stream", new String[]{"/_data_stream/_promote/{name}"}); + routesMap.put("es/ml.delete_filter", new String[]{"/_ml/filters/{filter_id}"}); + routesMap.put("es/sql.query", new String[]{"/_sql"}); + routesMap.put("es/ccr.follow_stats", new String[]{"/{index}/_ccr/stats"}); + routesMap.put("es/transform.stop_transform", new String[]{"/_transform/{transform_id}/_stop"}); + routesMap.put("es/security.has_privileges_user_profile", new String[]{"/_security/profile/_has_privileges"}); + routesMap.put("es/autoscaling.delete_autoscaling_policy", new String[]{"/_autoscaling/policy/{name}"}); + routesMap.put("es/scripts_painless_execute", new String[]{"/_scripts/painless/_execute"}); + routesMap.put("es/indices.delete", new String[]{"/{index}"}); + routesMap.put("es/security.clear_cached_roles", new String[]{"/_security/role/{name}/_clear_cache"}); + routesMap.put("es/eql.delete", new String[]{"/_eql/search/{id}"}); + routesMap.put("es/update", new String[]{"/{index}/_update/{id}"}); + routesMap.put("es/snapshot.clone", new String[]{"/_snapshot/{repository}/{snapshot}/_clone/{target_snapshot}"}); + routesMap.put("es/license.get_basic_status", new String[]{"/_license/basic_status"}); + routesMap.put("es/indices.close", new String[]{"/{index}/_close"}); + routesMap.put("es/security.saml_authenticate", new String[]{"/_security/saml/authenticate"}); + routesMap.put("es/search_application.put", new String[]{"/_application/search_application/{name}"}); + routesMap.put("es/count", new String[]{"/_count", "/{index}/_count"}); + routesMap.put("es/migration.deprecations", new String[]{"/_migration/deprecations", "/{index}/_migration/deprecations"}); + routesMap.put("es/indices.segments", new String[]{"/_segments", "/{index}/_segments"}); + routesMap.put("es/security.suggest_user_profiles", new String[]{"/_security/profile/_suggest"}); + routesMap.put("es/security.get_user_privileges", new String[]{"/_security/user/_privileges"}); + routesMap.put("es/indices.delete_alias", new String[]{"/{index}/_alias/{name}", "/{index}/_aliases/{name}"}); + routesMap.put("es/indices.get_mapping", new String[]{"/_mapping", "/{index}/_mapping"}); + routesMap.put("es/indices.put_index_template", new String[]{"/_index_template/{name}"}); + routesMap.put("es/searchable_snapshots.stats", new String[]{"/_searchable_snapshots/stats", "/{index}/_searchable_snapshots/stats"}); + routesMap.put("es/security.disable_user", new String[]{"/_security/user/{username}/_disable"}); + routesMap.put("es/ml.upgrade_job_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade"}); + routesMap.put("es/delete", new String[]{"/{index}/_doc/{id}"}); + routesMap.put("es/async_search.delete", new String[]{"/_async_search/{id}"}); + routesMap.put("es/cat.transforms", new String[]{"/_cat/transforms", "/_cat/transforms/{transform_id}"}); + routesMap.put("es/ping", new String[]{"/"}); + routesMap.put("es/ccr.pause_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}/pause"}); + routesMap.put("es/indices.shard_stores", new String[]{"/_shard_stores", "/{index}/_shard_stores"}); + routesMap.put("es/ml.update_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}/_update"}); + routesMap.put("es/logstash.delete_pipeline", new String[]{"/_logstash/pipeline/{id}"}); + routesMap.put("es/sql.translate", new String[]{"/_sql/translate"}); + routesMap.put("es/exists", new String[]{"/{index}/_doc/{id}"}); + routesMap.put("es/snapshot.get_repository", new String[]{"/_snapshot", "/_snapshot/{repository}"}); + routesMap.put("es/snapshot.verify_repository", new String[]{"/_snapshot/{repository}/_verify"}); + routesMap.put("es/indices.put_data_lifecycle", new String[]{"/_data_stream/{name}/_lifecycle"}); + routesMap.put("es/ml.open_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_open"}); + routesMap.put("es/security.update_user_profile_data", new String[]{"/_security/profile/{uid}/_data"}); + routesMap.put("es/enrich.put_policy", new String[]{"/_enrich/policy/{name}"}); + routesMap.put("es/ml.get_datafeed_stats", new String[]{"/_ml/datafeeds/{datafeed_id}/_stats", "/_ml/datafeeds/_stats"}); + routesMap.put("es/open_point_in_time", new String[]{"/{index}/_pit"}); + routesMap.put("es/get_source", new String[]{"/{index}/_source/{id}"}); + routesMap.put("es/delete_by_query", new String[]{"/{index}/_delete_by_query"}); + routesMap.put("es/security.create_api_key", new String[]{"/_security/api_key"}); + routesMap.put("es/cat.tasks", new String[]{"/_cat/tasks"}); + routesMap.put("es/watcher.delete_watch", new String[]{"/_watcher/watch/{id}"}); + routesMap.put("es/ingest.processor_grok", new String[]{"/_ingest/processor/grok"}); + routesMap.put("es/ingest.put_pipeline", new String[]{"/_ingest/pipeline/{id}"}); + routesMap.put("es/ml.get_data_frame_analytics_stats", new String[]{"/_ml/data_frame/analytics/_stats", "/_ml/data_frame/analytics/{id}/_stats"}); + routesMap.put("es/indices.data_streams_stats", new String[]{"/_data_stream/_stats", "/_data_stream/{name}/_stats"}); + routesMap.put("es/security.clear_cached_realms", new String[]{"/_security/realm/{realms}/_clear_cache"}); + routesMap.put("es/field_caps", new String[]{"/_field_caps", "/{index}/_field_caps"}); + routesMap.put("es/ml.evaluate_data_frame", new String[]{"/_ml/data_frame/_evaluate"}); + routesMap.put("es/ml.delete_forecast", new String[]{"/_ml/anomaly_detectors/{job_id}/_forecast", "/_ml/anomaly_detectors/{job_id}/_forecast/{forecast_id}"}); + routesMap.put("es/enrich.get_policy", new String[]{"/_enrich/policy/{name}", "/_enrich/policy"}); + routesMap.put("es/rollup.start_job", new String[]{"/_rollup/job/{id}/_start"}); + routesMap.put("es/tasks.cancel", new String[]{"/_tasks/_cancel", "/_tasks/{task_id}/_cancel"}); + routesMap.put("es/security.saml_logout", new String[]{"/_security/saml/logout"}); + routesMap.put("es/render_search_template", new String[]{"/_render/template", "/_render/template/{id}"}); + routesMap.put("es/ml.get_calendar_events", new String[]{"/_ml/calendars/{calendar_id}/events"}); + routesMap.put("es/security.enable_user_profile", new String[]{"/_security/profile/{uid}/_enable"}); + routesMap.put("es/logstash.get_pipeline", new String[]{"/_logstash/pipeline", "/_logstash/pipeline/{id}"}); + routesMap.put("es/cat.snapshots", new String[]{"/_cat/snapshots", "/_cat/snapshots/{repository}"}); + routesMap.put("es/indices.add_block", new String[]{"/{index}/_block/{block}"}); + routesMap.put("es/terms_enum", new String[]{"/{index}/_terms_enum"}); + routesMap.put("es/ml.forecast", new String[]{"/_ml/anomaly_detectors/{job_id}/_forecast"}); + routesMap.put("es/cluster.stats", new String[]{"/_cluster/stats", "/_cluster/stats/nodes/{node_id}"}); + routesMap.put("es/search_application.list", new String[]{"/_application/search_application"}); + routesMap.put("es/cat.count", new String[]{"/_cat/count", "/_cat/count/{index}"}); + routesMap.put("es/cat.segments", new String[]{"/_cat/segments", "/_cat/segments/{index}"}); + routesMap.put("es/ccr.resume_follow", new String[]{"/{index}/_ccr/resume_follow"}); + routesMap.put("es/search_application.get", new String[]{"/_application/search_application/{name}"}); + routesMap.put("es/security.saml_service_provider_metadata", new String[]{"/_security/saml/metadata/{realm_name}"}); + routesMap.put("es/update_by_query", new String[]{"/{index}/_update_by_query"}); + routesMap.put("es/ml.stop_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_stop"}); + routesMap.put("es/ilm.explain_lifecycle", new String[]{"/{index}/_ilm/explain"}); + routesMap.put("es/ml.put_trained_model_vocabulary", new String[]{"/_ml/trained_models/{model_id}/vocabulary"}); + routesMap.put("es/indices.exists", new String[]{"/{index}"}); + routesMap.put("es/ml.set_upgrade_mode", new String[]{"/_ml/set_upgrade_mode"}); + routesMap.put("es/security.saml_invalidate", new String[]{"/_security/saml/invalidate"}); + routesMap.put("es/ml.get_job_stats", new String[]{"/_ml/anomaly_detectors/_stats", "/_ml/anomaly_detectors/{job_id}/_stats"}); + routesMap.put("es/cluster.allocation_explain", new String[]{"/_cluster/allocation/explain"}); + routesMap.put("es/watcher.activate_watch", new String[]{"/_watcher/watch/{watch_id}/_activate"}); + routesMap.put("es/searchable_snapshots.clear_cache", new String[]{"/_searchable_snapshots/cache/clear", "/{index}/_searchable_snapshots/cache/clear"}); + routesMap.put("es/msearch_template", new String[]{"/_msearch/template", "/{index}/_msearch/template"}); + routesMap.put("es/bulk", new String[]{"/_bulk", "/{index}/_bulk"}); + routesMap.put("es/cat.nodeattrs", new String[]{"/_cat/nodeattrs"}); + routesMap.put("es/indices.get_index_template", new String[]{"/_index_template", "/_index_template/{name}"}); + routesMap.put("es/license.get", new String[]{"/_license"}); + routesMap.put("es/ccr.forget_follower", new String[]{"/{index}/_ccr/forget_follower"}); + routesMap.put("es/security.delete_role", new String[]{"/_security/role/{name}"}); + routesMap.put("es/indices.validate_query", new String[]{"/_validate/query", "/{index}/_validate/query"}); + routesMap.put("es/tasks.get", new String[]{"/_tasks/{task_id}"}); + routesMap.put("es/ml.start_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}/_start"}); + routesMap.put("es/indices.create", new String[]{"/{index}"}); + routesMap.put("es/cluster.delete_voting_config_exclusions", new String[]{"/_cluster/voting_config_exclusions"}); + routesMap.put("es/info", new String[]{"/"}); + routesMap.put("es/watcher.stop", new String[]{"/_watcher/_stop"}); + routesMap.put("es/enrich.delete_policy", new String[]{"/_enrich/policy/{name}"}); + routesMap.put("es/cat.ml_data_frame_analytics", new String[]{"/_cat/ml/data_frame/analytics", "/_cat/ml/data_frame/analytics/{id}"}); + routesMap.put("es/security.change_password", new String[]{"/_security/user/{username}/_password", "/_security/user/_password"}); + routesMap.put("es/put_script", new String[]{"/_scripts/{id}", "/_scripts/{id}/{context}"}); + routesMap.put("es/ml.put_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}"}); + routesMap.put("es/cat.master", new String[]{"/_cat/master"}); + routesMap.put("es/features.reset_features", new String[]{"/_features/_reset"}); + routesMap.put("es/indices.get_data_lifecycle", new String[]{"/_data_stream/{name}/_lifecycle"}); + routesMap.put("es/ml.get_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}", "/_ml/data_frame/analytics"}); + routesMap.put("es/security.delete_service_token", new String[]{"/_security/service/{namespace}/{service}/credential/token/{name}"}); + routesMap.put("es/indices.recovery", new String[]{"/_recovery", "/{index}/_recovery"}); + routesMap.put("es/cat.recovery", new String[]{"/_cat/recovery", "/_cat/recovery/{index}"}); + routesMap.put("es/indices.downsample", new String[]{"/{index}/_downsample/{target_index}"}); + routesMap.put("es/ingest.delete_pipeline", new String[]{"/_ingest/pipeline/{id}"}); + routesMap.put("es/async_search.get", new String[]{"/_async_search/{id}"}); + routesMap.put("es/eql.get", new String[]{"/_eql/search/{id}"}); + routesMap.put("es/cat.aliases", new String[]{"/_cat/aliases", "/_cat/aliases/{name}"}); + routesMap.put("es/security.get_service_credentials", new String[]{"/_security/service/{namespace}/{service}/credential"}); + routesMap.put("es/cat.allocation", new String[]{"/_cat/allocation", "/_cat/allocation/{node_id}"}); + routesMap.put("es/ml.stop_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}/_stop"}); + routesMap.put("es/indices.open", new String[]{"/{index}/_open"}); + routesMap.put("es/ilm.get_lifecycle", new String[]{"/_ilm/policy/{policy}", "/_ilm/policy"}); + routesMap.put("es/ilm.remove_policy", new String[]{"/{index}/_ilm/remove"}); + routesMap.put("es/security.get_role_mapping", new String[]{"/_security/role_mapping/{name}", "/_security/role_mapping"}); + routesMap.put("es/snapshot.create", new String[]{"/_snapshot/{repository}/{snapshot}"}); + routesMap.put("es/watcher.get_watch", new String[]{"/_watcher/watch/{id}"}); + routesMap.put("es/license.post_start_trial", new String[]{"/_license/start_trial"}); + routesMap.put("es/snapshot.restore", new String[]{"/_snapshot/{repository}/{snapshot}/_restore"}); + routesMap.put("es/indices.put_mapping", new String[]{"/{index}/_mapping"}); + routesMap.put("es/ml.delete_calendar_job", new String[]{"/_ml/calendars/{calendar_id}/jobs/{job_id}"}); + routesMap.put("es/security.clear_api_key_cache", new String[]{"/_security/api_key/{ids}/_clear_cache"}); + routesMap.put("es/slm.start", new String[]{"/_slm/start"}); + routesMap.put("es/cat.component_templates", new String[]{"/_cat/component_templates", "/_cat/component_templates/{name}"}); + routesMap.put("es/security.enable_user", new String[]{"/_security/user/{username}/_enable"}); + routesMap.put("es/cluster.delete_component_template", new String[]{"/_component_template/{name}"}); + routesMap.put("es/security.get_role", new String[]{"/_security/role/{name}", "/_security/role"}); + routesMap.put("es/ingest.get_pipeline", new String[]{"/_ingest/pipeline", "/_ingest/pipeline/{id}"}); + routesMap.put("es/ml.delete_expired_data", new String[]{"/_ml/_delete_expired_data/{job_id}", "/_ml/_delete_expired_data"}); + routesMap.put("es/indices.get_settings", new String[]{"/_settings", "/{index}/_settings", "/{index}/_settings/{name}", "/_settings/{name}"}); + routesMap.put("es/ccr.follow", new String[]{"/{index}/_ccr/follow"}); + routesMap.put("es/termvectors", new String[]{"/{index}/_termvectors/{id}", "/{index}/_termvectors"}); + routesMap.put("es/ml.post_data", new String[]{"/_ml/anomaly_detectors/{job_id}/_data"}); + routesMap.put("es/eql.search", new String[]{"/{index}/_eql/search"}); + routesMap.put("es/ml.get_trained_models", new String[]{"/_ml/trained_models/{model_id}", "/_ml/trained_models"}); + routesMap.put("es/security.disable_user_profile", new String[]{"/_security/profile/{uid}/_disable"}); + routesMap.put("es/security.put_privileges", new String[]{"/_security/privilege"}); + routesMap.put("es/cat.nodes", new String[]{"/_cat/nodes"}); + routesMap.put("es/nodes.info", new String[]{"/_nodes", "/_nodes/{node_id}", "/_nodes/{metric}", "/_nodes/{node_id}/{metric}"}); + routesMap.put("es/graph.explore", new String[]{"/{index}/_graph/explore"}); + routesMap.put("es/autoscaling.put_autoscaling_policy", new String[]{"/_autoscaling/policy/{name}"}); + routesMap.put("es/cat.templates", new String[]{"/_cat/templates", "/_cat/templates/{name}"}); + routesMap.put("es/cluster.remote_info", new String[]{"/_remote/info"}); + routesMap.put("es/rank_eval", new String[]{"/_rank_eval", "/{index}/_rank_eval"}); + routesMap.put("es/security.delete_privileges", new String[]{"/_security/privilege/{application}/{name}"}); + routesMap.put("es/security.get_privileges", new String[]{"/_security/privilege", "/_security/privilege/{application}", "/_security/privilege/{application}/{name}"}); + routesMap.put("es/scroll", new String[]{"/_search/scroll"}); + routesMap.put("es/license.delete", new String[]{"/_license"}); + routesMap.put("es/indices.disk_usage", new String[]{"/{index}/_disk_usage"}); + routesMap.put("es/msearch", new String[]{"/_msearch", "/{index}/_msearch"}); + routesMap.put("es/indices.field_usage_stats", new String[]{"/{index}/_field_usage_stats"}); + routesMap.put("es/indices.rollover", new String[]{"/{alias}/_rollover", "/{alias}/_rollover/{new_index}"}); + routesMap.put("es/cat.ml_trained_models", new String[]{"/_cat/ml/trained_models", "/_cat/ml/trained_models/{model_id}"}); + routesMap.put("es/ml.delete_trained_model_alias", new String[]{"/_ml/trained_models/{model_id}/model_aliases/{model_alias}"}); + routesMap.put("es/indices.get", new String[]{"/{index}"}); + routesMap.put("es/sql.get_async_status", new String[]{"/_sql/async/status/{id}"}); + routesMap.put("es/ilm.stop", new String[]{"/_ilm/stop"}); + routesMap.put("es/security.put_user", new String[]{"/_security/user/{username}"}); + routesMap.put("es/cluster.state", new String[]{"/_cluster/state", "/_cluster/state/{metric}", "/_cluster/state/{metric}/{index}"}); + routesMap.put("es/indices.put_settings", new String[]{"/_settings", "/{index}/_settings"}); + routesMap.put("es/knn_search", new String[]{"/{index}/_knn_search"}); + routesMap.put("es/get", new String[]{"/{index}/_doc/{id}"}); + routesMap.put("es/eql.get_status", new String[]{"/_eql/search/status/{id}"}); + routesMap.put("es/ssl.certificates", new String[]{"/_ssl/certificates"}); + routesMap.put("es/ml.get_model_snapshots", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}", "/_ml/anomaly_detectors/{job_id}/model_snapshots"}); + routesMap.put("es/nodes.clear_repositories_metering_archive", new String[]{"/_nodes/{node_id}/_repositories_metering/{max_archive_version}"}); + routesMap.put("es/security.put_role", new String[]{"/_security/role/{name}"}); + routesMap.put("es/ml.get_influencers", new String[]{"/_ml/anomaly_detectors/{job_id}/results/influencers"}); + routesMap.put("es/transform.upgrade_transforms", new String[]{"/_transform/_upgrade"}); + routesMap.put("es/ml.delete_calendar_event", new String[]{"/_ml/calendars/{calendar_id}/events/{event_id}"}); + routesMap.put("es/indices.get_field_mapping", new String[]{"/_mapping/field/{fields}", "/{index}/_mapping/field/{fields}"}); + routesMap.put("es/transform.preview_transform", new String[]{"/_transform/{transform_id}/_preview", "/_transform/_preview"}); + routesMap.put("es/tasks.list", new String[]{"/_tasks"}); + routesMap.put("es/ml.clear_trained_model_deployment_cache", new String[]{"/_ml/trained_models/{model_id}/deployment/cache/_clear"}); + routesMap.put("es/cluster.reroute", new String[]{"/_cluster/reroute"}); + routesMap.put("es/security.saml_complete_logout", new String[]{"/_security/saml/complete_logout"}); + routesMap.put("es/indices.simulate_index_template", new String[]{"/_index_template/_simulate_index/{name}"}); + routesMap.put("es/snapshot.get", new String[]{"/_snapshot/{repository}/{snapshot}"}); + routesMap.put("es/ccr.put_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}"}); + routesMap.put("es/nodes.hot_threads", new String[]{"/_nodes/hot_threads", "/_nodes/{node_id}/hot_threads"}); + routesMap.put("es/ml.preview_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/_preview", "/_ml/data_frame/analytics/{id}/_preview"}); + routesMap.put("es/indices.flush", new String[]{"/_flush", "/{index}/_flush"}); + routesMap.put("es/cluster.exists_component_template", new String[]{"/_component_template/{name}"}); + routesMap.put("es/snapshot.status", new String[]{"/_snapshot/_status", "/_snapshot/{repository}/_status", "/_snapshot/{repository}/{snapshot}/_status"}); + routesMap.put("es/ml.update_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_update"}); + routesMap.put("es/indices.update_aliases", new String[]{"/_aliases"}); + routesMap.put("es/autoscaling.get_autoscaling_capacity", new String[]{"/_autoscaling/capacity"}); + routesMap.put("es/migration.post_feature_upgrade", new String[]{"/_migration/system_features"}); + routesMap.put("es/ml.get_records", new String[]{"/_ml/anomaly_detectors/{job_id}/results/records"}); + routesMap.put("es/indices.get_alias", new String[]{"/_alias", "/_alias/{name}", "/{index}/_alias/{name}", "/{index}/_alias"}); + routesMap.put("es/logstash.put_pipeline", new String[]{"/_logstash/pipeline/{id}"}); + routesMap.put("es/snapshot.delete_repository", new String[]{"/_snapshot/{repository}"}); + routesMap.put("es/security.has_privileges", new String[]{"/_security/user/_has_privileges", "/_security/user/{user}/_has_privileges"}); + routesMap.put("es/cat.indices", new String[]{"/_cat/indices", "/_cat/indices/{index}"}); + routesMap.put("es/ccr.get_auto_follow_pattern", new String[]{"/_ccr/auto_follow", "/_ccr/auto_follow/{name}"}); + routesMap.put("es/ml.start_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_start"}); + routesMap.put("es/indices.clone", new String[]{"/{index}/_clone/{target}"}); + routesMap.put("es/search_application.delete", new String[]{"/_application/search_application/{name}"}); + routesMap.put("es/security.query_api_keys", new String[]{"/_security/_query/api_key"}); + routesMap.put("es/ml.flush_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_flush"}); + routesMap.put("es/security.clear_cached_privileges", new String[]{"/_security/privilege/{application}/_clear_cache"}); + routesMap.put("es/indices.exists_index_template", new String[]{"/_index_template/{name}"}); + routesMap.put("es/indices.explain_data_lifecycle", new String[]{"/{index}/_lifecycle/explain"}); + routesMap.put("es/indices.put_alias", new String[]{"/{index}/_alias/{name}", "/{index}/_aliases/{name}"}); + routesMap.put("es/ml.get_buckets", new String[]{"/_ml/anomaly_detectors/{job_id}/results/buckets/{timestamp}", "/_ml/anomaly_detectors/{job_id}/results/buckets"}); + routesMap.put("es/ml.put_trained_model_definition_part", new String[]{"/_ml/trained_models/{model_id}/definition/{part}"}); + routesMap.put("es/get_script", new String[]{"/_scripts/{id}"}); + routesMap.put("es/ingest.simulate", new String[]{"/_ingest/pipeline/_simulate", "/_ingest/pipeline/{id}/_simulate"}); + routesMap.put("es/indices.migrate_to_data_stream", new String[]{"/_data_stream/_migrate/{name}"}); + routesMap.put("es/enrich.execute_policy", new String[]{"/_enrich/policy/{name}/_execute"}); + routesMap.put("es/indices.split", new String[]{"/{index}/_split/{target}"}); + routesMap.put("es/ml.delete_model_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}"}); + routesMap.put("es/nodes.usage", new String[]{"/_nodes/usage", "/_nodes/{node_id}/usage", "/_nodes/usage/{metric}", "/_nodes/{node_id}/usage/{metric}"}); + routesMap.put("es/cat.help", new String[]{"/_cat"}); + routesMap.put("es/ml.estimate_model_memory", new String[]{"/_ml/anomaly_detectors/_estimate_model_memory"}); + routesMap.put("es/exists_source", new String[]{"/{index}/_source/{id}"}); + routesMap.put("es/ml.put_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}"}); + routesMap.put("es/security.put_role_mapping", new String[]{"/_security/role_mapping/{name}"}); + routesMap.put("es/rollup.get_rollup_index_caps", new String[]{"/{index}/_rollup/data"}); + routesMap.put("es/transform.reset_transform", new String[]{"/_transform/{transform_id}/_reset"}); + routesMap.put("es/ml.infer_trained_model", new String[]{"/_ml/trained_models/{model_id}/_infer", "/_ml/trained_models/{model_id}/deployment/_infer"}); + routesMap.put("es/reindex", new String[]{"/_reindex"}); + routesMap.put("es/ml.put_trained_model", new String[]{"/_ml/trained_models/{model_id}"}); + routesMap.put("es/cat.ml_jobs", new String[]{"/_cat/ml/anomaly_detectors", "/_cat/ml/anomaly_detectors/{job_id}"}); + routesMap.put("es/search_application.search", new String[]{"/_application/search_application/{name}/_search"}); + routesMap.put("es/ilm.put_lifecycle", new String[]{"/_ilm/policy/{policy}"}); + routesMap.put("es/security.get_token", new String[]{"/_security/oauth2/token"}); + routesMap.put("es/ilm.move_to_step", new String[]{"/_ilm/move/{index}"}); + routesMap.put("es/search_template", new String[]{"/_search/template", "/{index}/_search/template"}); + routesMap.put("es/indices.delete_data_lifecycle", new String[]{"/_data_stream/{name}/_lifecycle"}); + routesMap.put("es/indices.get_data_stream", new String[]{"/_data_stream", "/_data_stream/{name}"}); + routesMap.put("es/ml.get_filters", new String[]{"/_ml/filters", "/_ml/filters/{filter_id}"}); + routesMap.put("es/cat.ml_datafeeds", new String[]{"/_cat/ml/datafeeds", "/_cat/ml/datafeeds/{datafeed_id}"}); + routesMap.put("es/rollup.rollup_search", new String[]{"/{index}/_rollup_search"}); + routesMap.put("es/ml.put_job", new String[]{"/_ml/anomaly_detectors/{job_id}"}); + routesMap.put("es/update_by_query_rethrottle", new String[]{"/_update_by_query/{task_id}/_rethrottle"}); + routesMap.put("es/indices.delete_index_template", new String[]{"/_index_template/{name}"}); + routesMap.put("es/indices.reload_search_analyzers", new String[]{"/{index}/_reload_search_analyzers"}); + routesMap.put("es/cluster.get_settings", new String[]{"/_cluster/settings"}); + routesMap.put("es/cluster.put_settings", new String[]{"/_cluster/settings"}); + routesMap.put("es/transform.put_transform", new String[]{"/_transform/{transform_id}"}); + routesMap.put("es/watcher.stats", new String[]{"/_watcher/stats", "/_watcher/stats/{metric}"}); + routesMap.put("es/ccr.delete_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}"}); + routesMap.put("es/mtermvectors", new String[]{"/_mtermvectors", "/{index}/_mtermvectors"}); + routesMap.put("es/license.post", new String[]{"/_license"}); + routesMap.put("es/xpack.info", new String[]{"/_xpack"}); + routesMap.put("es/dangling_indices.import_dangling_index", new String[]{"/_dangling/{index_uuid}"}); + routesMap.put("es/nodes.get_repositories_metering_info", new String[]{"/_nodes/{node_id}/_repositories_metering"}); + routesMap.put("es/transform.get_transform_stats", new String[]{"/_transform/{transform_id}/_stats"}); + routesMap.put("es/mget", new String[]{"/_mget", "/{index}/_mget"}); + routesMap.put("es/security.get_builtin_privileges", new String[]{"/_security/privilege/_builtin"}); + routesMap.put("es/ml.update_model_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_update"}); + routesMap.put("es/ml.info", new String[]{"/_ml/info"}); + routesMap.put("es/indices.exists_template", new String[]{"/_template/{name}"}); + routesMap.put("es/watcher.ack_watch", new String[]{"/_watcher/watch/{watch_id}/_ack", "/_watcher/watch/{watch_id}/_ack/{action_id}"}); + routesMap.put("es/security.get_user", new String[]{"/_security/user/{username}", "/_security/user"}); + routesMap.put("es/shutdown.get_node", new String[]{"/_nodes/shutdown", "/_nodes/{node_id}/shutdown"}); + routesMap.put("es/watcher.start", new String[]{"/_watcher/_start"}); + routesMap.put("es/indices.shrink", new String[]{"/{index}/_shrink/{target}"}); + routesMap.put("es/license.post_start_basic", new String[]{"/_license/start_basic"}); + routesMap.put("es/xpack.usage", new String[]{"/_xpack/usage"}); + routesMap.put("es/ilm.delete_lifecycle", new String[]{"/_ilm/policy/{policy}"}); + routesMap.put("es/ccr.follow_info", new String[]{"/{index}/_ccr/info"}); + routesMap.put("es/ml.put_calendar_job", new String[]{"/_ml/calendars/{calendar_id}/jobs/{job_id}"}); + routesMap.put("es/rollup.put_job", new String[]{"/_rollup/job/{id}"}); + routesMap.put("es/clear_scroll", new String[]{"/_search/scroll"}); + routesMap.put("es/ml.delete_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}"}); + routesMap.put("es/security.get_api_key", new String[]{"/_security/api_key"}); + routesMap.put("es/cat.health", new String[]{"/_cat/health"}); + routesMap.put("es/security.invalidate_token", new String[]{"/_security/oauth2/token"}); + routesMap.put("es/slm.delete_lifecycle", new String[]{"/_slm/policy/{policy_id}"}); + routesMap.put("es/ml.stop_trained_model_deployment", new String[]{"/_ml/trained_models/{model_id}/deployment/_stop"}); + routesMap.put("es/monitoring.bulk", new String[]{"/_monitoring/bulk", "/_monitoring/{type}/bulk"}); + routesMap.put("es/indices.stats", new String[]{"/_stats", "/_stats/{metric}", "/{index}/_stats", "/{index}/_stats/{metric}"}); + routesMap.put("es/searchable_snapshots.cache_stats", new String[]{"/_searchable_snapshots/cache/stats", "/_searchable_snapshots/{node_id}/cache/stats"}); + routesMap.put("es/async_search.submit", new String[]{"/_async_search", "/{index}/_async_search"}); + routesMap.put("es/rollup.get_jobs", new String[]{"/_rollup/job/{id}", "/_rollup/job"}); + routesMap.put("es/ml.revert_model_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_revert"}); + routesMap.put("es/transform.delete_transform", new String[]{"/_transform/{transform_id}"}); + routesMap.put("es/cluster.pending_tasks", new String[]{"/_cluster/pending_tasks"}); + routesMap.put("es/ml.get_model_snapshot_upgrade_stats", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade/_stats"}); + routesMap.put("es/ml.get_categories", new String[]{"/_ml/anomaly_detectors/{job_id}/results/categories/{category_id}", "/_ml/anomaly_detectors/{job_id}/results/categories"}); + routesMap.put("es/ccr.pause_follow", new String[]{"/{index}/_ccr/pause_follow"}); + routesMap.put("es/security.authenticate", new String[]{"/_security/_authenticate"}); + routesMap.put("es/enrich.stats", new String[]{"/_enrich/_stats"}); + routesMap.put("es/ml.put_trained_model_alias", new String[]{"/_ml/trained_models/{model_id}/model_aliases/{model_alias}"}); + routesMap.put("es/ml.get_overall_buckets", new String[]{"/_ml/anomaly_detectors/{job_id}/results/overall_buckets"}); + routesMap.put("es/indices.get_template", new String[]{"/_template", "/_template/{name}"}); + routesMap.put("es/security.delete_role_mapping", new String[]{"/_security/role_mapping/{name}"}); + routesMap.put("es/ml.get_datafeeds", new String[]{"/_ml/datafeeds/{datafeed_id}", "/_ml/datafeeds"}); + routesMap.put("es/slm.execute_lifecycle", new String[]{"/_slm/policy/{policy_id}/_execute"}); + routesMap.put("es/close_point_in_time", new String[]{"/_pit"}); + routesMap.put("es/snapshot.cleanup_repository", new String[]{"/_snapshot/{repository}/_cleanup"}); + routesMap.put("es/autoscaling.get_autoscaling_policy", new String[]{"/_autoscaling/policy/{name}"}); + routesMap.put("es/slm.put_lifecycle", new String[]{"/_slm/policy/{policy_id}"}); + routesMap.put("es/ml.get_jobs", new String[]{"/_ml/anomaly_detectors/{job_id}", "/_ml/anomaly_detectors"}); + routesMap.put("es/ml.get_trained_models_stats", new String[]{"/_ml/trained_models/{model_id}/_stats", "/_ml/trained_models/_stats"}); + routesMap.put("es/ml.validate_detector", new String[]{"/_ml/anomaly_detectors/_validate/detector"}); + routesMap.put("es/watcher.put_watch", new String[]{"/_watcher/watch/{id}"}); + routesMap.put("es/transform.update_transform", new String[]{"/_transform/{transform_id}/_update"}); + routesMap.put("es/ml.post_calendar_events", new String[]{"/_ml/calendars/{calendar_id}/events"}); + routesMap.put("es/migration.get_feature_upgrade_status", new String[]{"/_migration/system_features"}); + routesMap.put("es/get_script_context", new String[]{"/_script_context"}); + routesMap.put("es/ml.put_filter", new String[]{"/_ml/filters/{filter_id}"}); + routesMap.put("es/ml.update_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_update"}); + routesMap.put("es/ingest.geo_ip_stats", new String[]{"/_ingest/geoip/stats"}); + routesMap.put("es/security.delete_user", new String[]{"/_security/user/{username}"}); + routesMap.put("es/indices.unfreeze", new String[]{"/{index}/_unfreeze"}); + routesMap.put("es/snapshot.create_repository", new String[]{"/_snapshot/{repository}"}); + routesMap.put("es/cluster.get_component_template", new String[]{"/_component_template", "/_component_template/{name}"}); + routesMap.put("es/ilm.migrate_to_data_tiers", new String[]{"/_ilm/migrate_to_data_tiers"}); + routesMap.put("es/indices.refresh", new String[]{"/_refresh", "/{index}/_refresh"}); + routesMap.put("es/ml.get_calendars", new String[]{"/_ml/calendars", "/_ml/calendars/{calendar_id}"}); + routesMap.put("es/watcher.deactivate_watch", new String[]{"/_watcher/watch/{watch_id}/_deactivate"}); + routesMap.put("es/cluster.health", new String[]{"/_cluster/health", "/_cluster/health/{index}"}); + routesMap.put("es/dangling_indices.delete_dangling_index", new String[]{"/_dangling/{index_uuid}"}); + routesMap.put("es/health_report", new String[]{"/_health_report", "/_health_report/{feature}"}); + routesMap.put("es/watcher.query_watches", new String[]{"/_watcher/_query/watches"}); + routesMap.put("es/ccr.unfollow", new String[]{"/{index}/_ccr/unfollow"}); + routesMap.put("es/ml.validate", new String[]{"/_ml/anomaly_detectors/_validate"}); + routesMap.put("es/cat.plugins", new String[]{"/_cat/plugins"}); + routesMap.put("es/watcher.execute_watch", new String[]{"/_watcher/watch/{id}/_execute", "/_watcher/watch/_execute"}); + routesMap.put("es/search_shards", new String[]{"/_search_shards", "/{index}/_search_shards"}); + routesMap.put("es/cat.shards", new String[]{"/_cat/shards", "/_cat/shards/{index}"}); + routesMap.put("es/ml.delete_job", new String[]{"/_ml/anomaly_detectors/{job_id}"}); + routesMap.put("es/ilm.start", new String[]{"/_ilm/start"}); + routesMap.put("es/security.get_user_profile", new String[]{"/_security/profile/{uid}"}); + routesMap.put("es/indices.modify_data_stream", new String[]{"/_data_stream/_modify"}); + routesMap.put("es/indices.exists_alias", new String[]{"/_alias/{name}", "/{index}/_alias/{name}"}); + routesMap.put("es/rollup.stop_job", new String[]{"/_rollup/job/{id}/_stop"}); + routesMap.put("es/dangling_indices.list_dangling_indices", new String[]{"/_dangling"}); + routesMap.put("es/snapshot.delete", new String[]{"/_snapshot/{repository}/{snapshot}"}); + routesMap.put("es/security.activate_user_profile", new String[]{"/_security/profile/_activate"}); + routesMap.put("es/ml.start_trained_model_deployment", new String[]{"/_ml/trained_models/{model_id}/deployment/_start"}); + routesMap.put("es/transform.start_transform", new String[]{"/_transform/{transform_id}/_start"}); + routesMap.put("es/cat.repositories", new String[]{"/_cat/repositories"}); + routesMap.put("es/ilm.get_status", new String[]{"/_ilm/status"}); + routesMap.put("es/shutdown.delete_node", new String[]{"/_nodes/{node_id}/shutdown"}); + routesMap.put("es/nodes.stats", new String[]{"/_nodes/stats", "/_nodes/{node_id}/stats", "/_nodes/stats/{metric}", "/_nodes/{node_id}/stats/{metric}", "/_nodes/stats/{metric}/{index_metric}", "/_nodes/{node_id}/stats/{metric}/{index_metric}"}); + routesMap.put("es/get_script_languages", new String[]{"/_script_language"}); + routesMap.put("es/slm.execute_retention", new String[]{"/_slm/_execute_retention"}); + routesMap.put("es/security.get_service_accounts", new String[]{"/_security/service/{namespace}/{service}", "/_security/service/{namespace}", "/_security/service"}); + routesMap.put("es/shutdown.put_node", new String[]{"/_nodes/{node_id}/shutdown"}); + routesMap.put("es/indices.resolve_index", new String[]{"/_resolve/index/{name}"}); + routesMap.put("es/search", new String[]{"/_search", "/{index}/_search"}); + routesMap.put("es/sql.get_async", new String[]{"/_sql/async/{id}"}); + routesMap.put("es/delete_by_query_rethrottle", new String[]{"/_delete_by_query/{task_id}/_rethrottle"}); + routesMap.put("es/transform.get_transform", new String[]{"/_transform/{transform_id}", "/_transform"}); + routesMap.put("es/security.invalidate_api_key", new String[]{"/_security/api_key"}); + routesMap.put("es/security.saml_prepare_authentication", new String[]{"/_security/saml/prepare"}); + routesMap.put("es/ml.get_memory_stats", new String[]{"/_ml/memory/_stats", "/_ml/memory/{node_id}/_stats"}); + routesMap.put("es/ccr.stats", new String[]{"/_ccr/stats"}); + routesMap.put("es/indices.forcemerge", new String[]{"/_forcemerge", "/{index}/_forcemerge"}); + routesMap.put("es/indices.delete_template", new String[]{"/_template/{name}"}); + routesMap.put("es/sql.delete_async", new String[]{"/_sql/async/delete/{id}"}); + routesMap.put("es/security.update_api_key", new String[]{"/_security/api_key/{id}"}); + routesMap.put("es/security.create_service_token", new String[]{"/_security/service/{namespace}/{service}/credential/token/{name}", "/_security/service/{namespace}/{service}/credential/token"}); + routesMap.put("es/license.get_trial_status", new String[]{"/_license/trial_status"}); + routesMap.put("es/searchable_snapshots.mount", new String[]{"/_snapshot/{repository}/{snapshot}/_mount"}); + routesMap.put("es/security.grant_api_key", new String[]{"/_security/api_key/grant"}); + routesMap.put("es/ilm.retry", new String[]{"/{index}/_ilm/retry"}); + routesMap.put("es/ml.reset_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_reset"}); + routesMap.put("es/ml.close_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_close"}); + routesMap.put("es/ml.explain_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/_explain", "/_ml/data_frame/analytics/{id}/_explain"}); + routesMap.put("es/security.clear_cached_service_tokens", new String[]{"/_security/service/{namespace}/{service}/credential/token/{name}/_clear_cache"}); + routesMap.put("es/search_mvt", new String[]{"/{index}/_mvt/{field}/{zoom}/{x}/{y}"}); + } + + protected void enrichSpanWithRouteInformation(Span span, String method, String endpointId, String urlPath) { + String[] availableRoutes = routesMap.get(endpointId); + if (availableRoutes == null || availableRoutes.length == 0) { + return; + } + + if (availableRoutes.length == 1) { + enrichSpan(span, method, availableRoutes[0], urlPath); + } else { + int i = 0; + boolean enriched = false; + while (i < availableRoutes.length && !enriched) { + enriched = enrichSpan(span, method, availableRoutes[i], urlPath); + i++; + } + } + } + + private Matcher matchUrl(String route, String urlPath) { + if (!regexPatternMap.containsKey(route)) { + StringBuilder regexStr = new StringBuilder(); + regexStr.append('^'); + regexStr.append(route.replace("{", "(?<").replace("}", ">[^/]+)")); + regexStr.append('$'); + regexPatternMap.put(route, Pattern.compile(regexStr.toString())); + } + + Pattern pattern = regexPatternMap.get(route); + return pattern.matcher(urlPath); + } + + private boolean enrichSpan(Span span, String method, String route, String urlPath) { + if (route.contains("{")) { + Matcher matcher = matchUrl(route, urlPath); + if (!matcher.find()) { + return false; + } + setTarget(span, matcher, route); + setDocId(span, matcher, route); + } else if (!route.equals(urlPath)) { + return false; + } + StringBuilder name = span.getAndOverrideName(AbstractSpan.PRIORITY_HIGH_LEVEL_FRAMEWORK); + if (name != null) { + name.append("Elasticsearch: ").append(method).append(" ").append(route); + } + return true; + } + + private void setTarget(Span span, Matcher matcher, String route) { + try { + if (route.contains("{index}")) { + String target = matcher.group("index"); + span.withOtelAttribute("db.elasticsearch.target", target); + } + } catch (Exception e) { + // ignore + } + } + + private void setDocId(Span span, Matcher matcher, String route) { + try { + if (route.startsWith("/{index}/_") && route.endsWith("/{id}")) { + String docId = matcher.group("id"); + span.withOtelAttribute("db.elasticsearch.doc_id", docId); + } + } catch (Exception e) { + // ignore + } + } +} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java new file mode 100644 index 0000000000..e59d33740b --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java @@ -0,0 +1,10 @@ +package co.elastic.apm.agent.esrestclient; + +import co.elastic.apm.agent.sdk.state.GlobalState; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; + +@GlobalState +public class RequestEndpointMap { + public static final WeakMap requestEndpointIdMap = WeakConcurrent.buildMap(); +} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java index 9f08f029a7..36e28ed911 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java @@ -138,6 +138,18 @@ protected void validateDbContextContent(Span span, List possibleContents assertThat(db.getStatementBuffer().toString()).isIn(possibleContents); } + protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, String index, String doc_id) { + validateSpanContent(span, expectedName, statusCode, method, index); + assertThat(span.getOtelAttributes()).containsKey("db.elasticsearch.doc_id"); + assertThat(span.getOtelAttributes().get("db.elasticsearch.doc_id")).isEqualTo(doc_id); + } + + protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, String index) { + validateSpanContent(span, expectedName, statusCode, method); + assertThat(span.getOtelAttributes()).containsKey("db.elasticsearch.target"); + assertThat(span.getOtelAttributes().get("db.elasticsearch.target")).isEqualTo(index); + } + protected void validateSpanContent(Span span, String expectedName, int statusCode, String method) { validateSpanContentWithoutContext(span, expectedName); validateHttpContextContent(span.getContext().getHttp(), statusCode, method); @@ -167,15 +179,32 @@ private void validateHttpContextContent(Http http, int statusCode, String method } protected void validateSpanContentAfterIndexCreateRequest() { + validateSpanContentAfterIndexCreateRequest(true); + } + + protected void validateSpanContentAfterIndexCreateRequest(boolean usePathPattern) { List spans = reporter.getSpans(); assertThat(spans).hasSize(1); - validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s", SECOND_INDEX), 200, "PUT"); + if(usePathPattern){ + validateSpanContent(spans.get(0), "Elasticsearch: PUT /{index}", 200, "PUT", SECOND_INDEX); + } else { + validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s", SECOND_INDEX), 200, "PUT" ); + } } protected void validateSpanContentAfterIndexDeleteRequest() { + validateSpanContentAfterIndexDeleteRequest(true); + } + + protected void validateSpanContentAfterIndexDeleteRequest(boolean usePathPattern) { List spans = reporter.getSpans(); assertThat(spans).hasSize(1); - validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s", SECOND_INDEX), 200, "DELETE"); + if(usePathPattern){ + validateSpanContent(spans.get(0), "Elasticsearch: DELETE /{index}", 200, "DELETE", SECOND_INDEX); + } else { + validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s", SECOND_INDEX), 200, "DELETE"); + } + } protected void validateSpanContentAfterBulkRequest() { diff --git a/apm-agent-plugins/apm-es-restclient-plugin/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/pom.xml index e3f2339899..ae0aca6871 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/pom.xml @@ -22,7 +22,7 @@ apm-es-restclient-plugin-5_6 apm-es-restclient-plugin-6_4 apm-es-restclient-plugin-7_x - apm-es-api-client-test + apm-es-restclient-plugin-8_x From e83ba9b82344420d87558a0677831861949876b2 Mon Sep 17 00:00:00 2001 From: Alexander Wert Date: Fri, 2 Jun 2023 14:04:06 +0200 Subject: [PATCH 02/10] added plugin to agent-builds pom Signed-off-by: Alexander Wert --- apm-agent-builds/pom.xml | 5 ++++ ...sticsearchRestClientInstrumentationIT.java | 27 +++++++++---------- .../EndpointResolutionHelper.java | 17 ++++++------ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apm-agent-builds/pom.xml b/apm-agent-builds/pom.xml index 1332244aad..8ed28ca0fa 100644 --- a/apm-agent-builds/pom.xml +++ b/apm-agent-builds/pom.xml @@ -97,6 +97,11 @@ apm-es-restclient-plugin-7_x ${project.version} + + ${project.groupId} + apm-es-restclient-plugin-8_x + ${project.version} + ${project.groupId} apm-grails-plugin diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java index ae7ccfccad..be6f1b272a 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java @@ -19,8 +19,8 @@ package co.elastic.apm.agent.esrestclient.v5_6; import co.elastic.apm.agent.esrestclient.AbstractEsClientInstrumentationTest; -import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.tracer.Outcome; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; @@ -65,14 +65,13 @@ @RunWith(Parameterized.class) public class ElasticsearchRestClientInstrumentationIT extends AbstractEsClientInstrumentationTest { - private static final String ELASTICSEARCH_CONTAINER_VERSION = "docker.elastic.co/elasticsearch/elasticsearch:5.6.0"; protected static final String USER_NAME = "elastic"; protected static final String PASSWORD = "changeme"; - protected static final String DOC_TYPE = "doc"; - private static RestHighLevelClient client; + private static final String ELASTICSEARCH_CONTAINER_VERSION = "docker.elastic.co/elasticsearch/elasticsearch:5.6.0"; @SuppressWarnings("NullableProblems") protected static RestClient lowLevelClient; + private static RestHighLevelClient client; public ElasticsearchRestClientInstrumentationIT(boolean async) { this.async = async; @@ -86,7 +85,7 @@ public static void startElasticsearchContainerAndClient() throws IOException { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(USER_NAME, PASSWORD)); - RestClientBuilder builder = RestClient.builder(HttpHost.create(container.getHttpHostAddress())) + RestClientBuilder builder = RestClient.builder(HttpHost.create(container.getHttpHostAddress())) .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); lowLevelClient = builder.build(); client = new RestHighLevelClient(lowLevelClient); @@ -124,14 +123,14 @@ public void testCreateAndDeleteIndex() throws IOException, ExecutionException { // Create an Index doPerformRequest("PUT", "/" + SECOND_INDEX); - validateSpanContentAfterIndexCreateRequest(); + validateSpanContentAfterIndexCreateRequest(false); // Delete the index reporter.reset(); doPerformRequest("DELETE", "/" + SECOND_INDEX); - validateSpanContentAfterIndexDeleteRequest(); + validateSpanContentAfterIndexDeleteRequest(false); assertThat(reporter.getFirstSpan().getOutcome()).isEqualTo(Outcome.SUCCESS); } @@ -187,8 +186,8 @@ public void testDocumentScenario() throws IOException, ExecutionException, Inter spans = reporter.getSpans(); assertThat(spans).hasSize(2); boolean updateSpanFound = false; - for(Span span: spans) { - if(span.getNameAsString().contains("_update")) { + for (Span span : spans) { + if (span.getNameAsString().contains("_update")) { updateSpanFound = true; break; } @@ -215,10 +214,6 @@ public void testScenarioAsBulkRequest() throws IOException, ExecutionException, validateSpanContentAfterBulkRequest(); } - private interface ClientMethod { - void invoke(Req request, ActionListener listener); - } - private Res invokeAsync(Req request, ClientMethod method) throws InterruptedException, ExecutionException { final CompletableFuture resultFuture = new CompletableFuture<>(); method.invoke(request, new ActionListener<>() { @@ -280,7 +275,6 @@ private BulkResponse doBulk(BulkRequest bulkRequest) throws IOException, Executi return client.bulk(bulkRequest); } - private Response doPerformRequest(String method, String path) throws IOException, ExecutionException { if (async) { final CompletableFuture resultFuture = new CompletableFuture<>(); @@ -304,4 +298,9 @@ public void onFailure(Exception exception) { return lowLevelClient.performRequest(method, path); } + + private interface ClientMethod { + void invoke(Req request, ActionListener listener); + } + } diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java index 482a870c75..aac4614a12 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java @@ -32,17 +32,8 @@ public class EndpointResolutionHelper { @Nullable private static EndpointResolutionHelper INSTANCE; - - protected static EndpointResolutionHelper get() { - if (INSTANCE == null) { - INSTANCE = new EndpointResolutionHelper(); - } - return INSTANCE; - } - private final Map routesMap = new HashMap<>(); private final Map regexPatternMap = new ConcurrentHashMap<>(); - private EndpointResolutionHelper() { // This map is generated from the Java client code. // It's a one-time generation that shouldn't require maintenance effort, @@ -464,12 +455,20 @@ private EndpointResolutionHelper() { routesMap.put("es/search_mvt", new String[]{"/{index}/_mvt/{field}/{zoom}/{x}/{y}"}); } + protected static EndpointResolutionHelper get() { + if (INSTANCE == null) { + INSTANCE = new EndpointResolutionHelper(); + } + return INSTANCE; + } + protected void enrichSpanWithRouteInformation(Span span, String method, String endpointId, String urlPath) { String[] availableRoutes = routesMap.get(endpointId); if (availableRoutes == null || availableRoutes.length == 0) { return; } + span.withOtelAttribute("db.operation", (endpointId.startsWith("es/") && endpointId.length() > 3) ? endpointId.substring(3) : endpointId); if (availableRoutes.length == 1) { enrichSpan(span, method, availableRoutes[0], urlPath); } else { From 1a68fc67657ddb9fb37fefebc0e64d6cdbfd3fe5 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Fri, 7 Jul 2023 11:09:32 +0200 Subject: [PATCH 03/10] Review fixes --- .../apm-es-restclient-plugin-8_x/pom.xml | 5 ---- .../RestClientTransportInstrumentation.java | 2 +- .../v8_x/package-info.java | 2 +- ...ic.apm.agent.sdk.ElasticApmInstrumentation | 2 +- ...searchRestClientInstrumentationHelper.java | 7 ++++-- .../EndpointResolutionHelper.java | 24 +++++++++---------- .../esrestclient/RequestEndpointMap.java | 10 -------- .../AbstractEsClientInstrumentationTest.java | 6 ++--- 8 files changed, 21 insertions(+), 37 deletions(-) rename apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/{esapiclient => esrestclient}/v8_x/RestClientTransportInstrumentation.java (98%) rename apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/{esapiclient => esrestclient}/v8_x/package-info.java (94%) delete mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml index d5791b6d23..a714721ace 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml @@ -27,11 +27,6 @@ ${version.elasticsearch-java} provided - - com.fasterxml.jackson.core - jackson-databind - 2.14.2 - org.testcontainers elasticsearch diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/RestClientTransportInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/RestClientTransportInstrumentation.java similarity index 98% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/RestClientTransportInstrumentation.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/RestClientTransportInstrumentation.java index 687237029c..c5977ce4eb 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/RestClientTransportInstrumentation.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/RestClientTransportInstrumentation.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.esapiclient.v8_x; +package co.elastic.apm.agent.esrestclient.v8_x; import co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentation; import co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentationHelper; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/package-info.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/package-info.java similarity index 94% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/package-info.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/package-info.java index 5bcff42100..04dd867f33 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esapiclient/v8_x/package-info.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/package-info.java @@ -17,6 +17,6 @@ * under the License. */ @NonnullApi -package co.elastic.apm.agent.esapiclient.v8_x; +package co.elastic.apm.agent.esrestclient.v8_x; import co.elastic.apm.agent.sdk.NonnullApi; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 2a87403c81..9f45d5ea0e 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1 +1 @@ -co.elastic.apm.agent.esapiclient.v8_x.RestClientTransportInstrumentation +co.elastic.apm.agent.esrestclient.v8_x.RestClientTransportInstrumentation diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java index ba3ffe8af6..022b5e88c5 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java @@ -21,6 +21,8 @@ import co.elastic.apm.agent.common.util.WildcardMatcher; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; import co.elastic.apm.agent.tracer.AbstractSpan; import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Outcome; @@ -41,6 +43,7 @@ public class ElasticsearchRestClientInstrumentationHelper { + private static final WeakMap requestEndpointIdMap = WeakConcurrent.buildMap(); private static final Logger logger = LoggerFactory.getLogger(ElasticsearchRestClientInstrumentationHelper.class); private static final Logger unsupportedOperationOnceLogger = LoggerUtils.logOnce(logger); @@ -74,12 +77,12 @@ public ResponseListenerWrapper createInstance() { } public void registerEndpointId(Object requestObj, String endpointId) { - RequestEndpointMap.requestEndpointIdMap.put(requestObj, endpointId); + requestEndpointIdMap.put(requestObj, endpointId); } @Nullable public Span createClientSpan(Object requestObj, String method, String endpoint, @Nullable HttpEntity httpEntity) { - String endpointId = RequestEndpointMap.requestEndpointIdMap.remove(requestObj); + String endpointId = requestEndpointIdMap.remove(requestObj); return createClientSpan(method, endpoint, httpEntity, endpointId); } diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java index aac4614a12..6b17835d03 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java @@ -18,6 +18,8 @@ */ package co.elastic.apm.agent.esrestclient; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.tracer.AbstractSpan; import co.elastic.apm.agent.tracer.Span; @@ -30,10 +32,13 @@ public class EndpointResolutionHelper { + private static final Logger logger = LoggerFactory.getLogger(EndpointResolutionHelper.class); + @Nullable private static EndpointResolutionHelper INSTANCE; private final Map routesMap = new HashMap<>(); private final Map regexPatternMap = new ConcurrentHashMap<>(); + private EndpointResolutionHelper() { // This map is generated from the Java client code. // It's a one-time generation that shouldn't require maintenance effort, @@ -469,14 +474,9 @@ protected void enrichSpanWithRouteInformation(Span span, String method, Strin } span.withOtelAttribute("db.operation", (endpointId.startsWith("es/") && endpointId.length() > 3) ? endpointId.substring(3) : endpointId); - if (availableRoutes.length == 1) { - enrichSpan(span, method, availableRoutes[0], urlPath); - } else { - int i = 0; - boolean enriched = false; - while (i < availableRoutes.length && !enriched) { - enriched = enrichSpan(span, method, availableRoutes[i], urlPath); - i++; + for (String route : availableRoutes) { + if (enrichSpan(span, method, route, urlPath)) { + break; } } } @@ -484,9 +484,7 @@ protected void enrichSpanWithRouteInformation(Span span, String method, Strin private Matcher matchUrl(String route, String urlPath) { if (!regexPatternMap.containsKey(route)) { StringBuilder regexStr = new StringBuilder(); - regexStr.append('^'); regexStr.append(route.replace("{", "(?<").replace("}", ">[^/]+)")); - regexStr.append('$'); regexPatternMap.put(route, Pattern.compile(regexStr.toString())); } @@ -497,7 +495,7 @@ private Matcher matchUrl(String route, String urlPath) { private boolean enrichSpan(Span span, String method, String route, String urlPath) { if (route.contains("{")) { Matcher matcher = matchUrl(route, urlPath); - if (!matcher.find()) { + if (!matcher.matches()) { return false; } setTarget(span, matcher, route); @@ -519,7 +517,7 @@ private void setTarget(Span span, Matcher matcher, String route) { span.withOtelAttribute("db.elasticsearch.target", target); } } catch (Exception e) { - // ignore + logger.error("Error setting target", e); } } @@ -530,7 +528,7 @@ private void setDocId(Span span, Matcher matcher, String route) { span.withOtelAttribute("db.elasticsearch.doc_id", docId); } } catch (Exception e) { - // ignore + logger.error("Error setting doc id", e); } } } diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java deleted file mode 100644 index e59d33740b..0000000000 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/RequestEndpointMap.java +++ /dev/null @@ -1,10 +0,0 @@ -package co.elastic.apm.agent.esrestclient; - -import co.elastic.apm.agent.sdk.state.GlobalState; -import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; -import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; - -@GlobalState -public class RequestEndpointMap { - public static final WeakMap requestEndpointIdMap = WeakConcurrent.buildMap(); -} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java index 36e28ed911..4173adad53 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java @@ -140,14 +140,12 @@ protected void validateDbContextContent(Span span, List possibleContents protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, String index, String doc_id) { validateSpanContent(span, expectedName, statusCode, method, index); - assertThat(span.getOtelAttributes()).containsKey("db.elasticsearch.doc_id"); - assertThat(span.getOtelAttributes().get("db.elasticsearch.doc_id")).isEqualTo(doc_id); + assertThat(span.getOtelAttributes()).containsEntry("db.elasticsearch.doc_id", doc_id); } protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, String index) { validateSpanContent(span, expectedName, statusCode, method); - assertThat(span.getOtelAttributes()).containsKey("db.elasticsearch.target"); - assertThat(span.getOtelAttributes().get("db.elasticsearch.target")).isEqualTo(index); + assertThat(span.getOtelAttributes()).containsEntry("db.elasticsearch.target", index); } protected void validateSpanContent(Span span, String expectedName, int statusCode, String method) { From 046e6199d05559b1b8546bebe060d2c213d282b0 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Fri, 7 Jul 2023 12:38:28 +0200 Subject: [PATCH 04/10] Copied otel classes --- .../ElasticsearchEndpointDefinition.java | 177 ++++ .../ElasticsearchEndpointMap.java | 883 ++++++++++++++++++ 2 files changed, 1060 insertions(+) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java new file mode 100644 index 0000000000..4aae0ea16a --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java @@ -0,0 +1,177 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.apm.agent.esrestclient; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ElasticsearchEndpointDefinition { + + private static final String UNDERSCORE_REPLACEMENT = "0"; + + private final String endpointName; + private final List routes; + + private final boolean isSearchEndpoint; + + public ElasticsearchEndpointDefinition( + String endpointName, String[] routes, boolean isSearchEndpoint) { + this.endpointName = endpointName; + this.routes = new ArrayList<>(); + for (String route : routes) { + this.routes.add(new Route(route)); + } + this.isSearchEndpoint = isSearchEndpoint; + } + + @Nullable + public String getEndpointName() { + return endpointName; + } + + public boolean isSearchEndpoint() { + return isSearchEndpoint; + } + + public void processPathParts(String urlPath, BiConsumer consumer) { + for (Route route : routes) { + if (route.hasParameters()) { + Matcher matcher = route.createMatcher(urlPath); + if (matcher.find()) { + for (String key : route.getPathPartNames()) { + String value = matcher.group(key); + if (key.contains(UNDERSCORE_REPLACEMENT)) { + // replace underscore back + key = key.replace(UNDERSCORE_REPLACEMENT, "_"); + } + consumer.accept(key, value); + } + return; + } + } + } + } + + List getRoutes() { + return routes; + } + + static final class Route { + private final String name; + private final boolean hasParameters; + + private volatile EndpointPattern epPattern; + + public Route(String name) { + this.name = name; + this.hasParameters = name.contains("{") && name.contains("}"); + } + + String getName() { + return name; + } + + boolean hasParameters() { + return hasParameters; + } + + List getPathPartNames() { + return getEndpointPattern().getPathPartNames(); + } + + Matcher createMatcher(String urlPath) { + return getEndpointPattern().getPattern().matcher(urlPath); + } + + private EndpointPattern getEndpointPattern() { + // Intentionally NOT synchronizing here to avoid synchronization overhead. + // Main purpose here is to cache the pattern without the need for strict thread-safety. + if (epPattern == null) { + epPattern = new EndpointPattern(this); + } + + return epPattern; + } + } + + static final class EndpointPattern { + private static final Pattern PATH_PART_NAMES_PATTERN = Pattern.compile("\\{([^}]+)}"); + private final Pattern pattern; + private final List pathPartNames; + + /** + * Creates, compiles and caches a regular expression pattern and retrieves a set of + * pathPartNames (names of the URL path parameters) for this route. + * + *

The regex pattern is later being used to match against a URL path to retrieve the URL path + * parameters for that route pattern using named regex capture groups. + */ + private EndpointPattern(Route route) { + pattern = buildRegexPattern(route.getName()); + + if (route.hasParameters()) { + pathPartNames = new ArrayList<>(); + Matcher matcher = PATH_PART_NAMES_PATTERN.matcher(route.getName()); + while (matcher.find()) { + String groupName = matcher.group(1); + + if (groupName != null) { + groupName = groupName.replace("_", UNDERSCORE_REPLACEMENT); + pathPartNames.add(groupName); + } + } + } else { + pathPartNames = Collections.emptyList(); + } + } + + /** + * Builds a regex pattern from the parameterized route pattern. + */ + static Pattern buildRegexPattern(String routeStr) { + StringBuilder regexStr = new StringBuilder(); + regexStr.append('^'); + int startIdx = routeStr.indexOf("{"); + while (startIdx >= 0) { + regexStr.append(routeStr.substring(0, startIdx)); + + int endIndex = routeStr.indexOf("}"); + if (endIndex <= startIdx + 1) { + break; + } + + // Append named capture group. + // If group name contains an underscore `_` it is being replaced with `0`, + // because `_` is not allowed in capture group names. + regexStr.append("(?<"); + regexStr.append( + routeStr.substring(startIdx + 1, endIndex).replace("_", UNDERSCORE_REPLACEMENT)); + regexStr.append(">[^/]+)"); + + routeStr = routeStr.substring(endIndex + 1); + startIdx = routeStr.indexOf("{"); + } + + regexStr.append(routeStr); + regexStr.append('$'); + + return Pattern.compile(regexStr.toString()); + } + + Pattern getPattern() { + return pattern; + } + + List getPathPartNames() { + return pathPartNames; + } + } +} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java new file mode 100644 index 0000000000..11206fb192 --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java @@ -0,0 +1,883 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.apm.agent.esrestclient; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class ElasticsearchEndpointMap { + + private static final Map routesMap; + + static { + Map routes = new HashMap<>(415); + initEndpoint(routes, "async_search.status", false, "/_async_search/status/{id}"); + initEndpoint(routes, "indices.analyze", false, "/_analyze", "/{index}/_analyze"); + initEndpoint(routes, "sql.clear_cursor", false, "/_sql/close"); + initEndpoint(routes, "ml.delete_datafeed", false, "/_ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "explain", false, "/{index}/_explain/{id}"); + initEndpoint( + routes, + "cat.thread_pool", + false, + "/_cat/thread_pool", + "/_cat/thread_pool/{thread_pool_patterns}"); + initEndpoint(routes, "ml.delete_calendar", false, "/_ml/calendars/{calendar_id}"); + initEndpoint(routes, "indices.create_data_stream", false, "/_data_stream/{name}"); + initEndpoint(routes, "cat.fielddata", false, "/_cat/fielddata", "/_cat/fielddata/{fields}"); + initEndpoint(routes, "security.enroll_node", false, "/_security/enroll/node"); + initEndpoint(routes, "slm.get_status", false, "/_slm/status"); + initEndpoint(routes, "ml.put_calendar", false, "/_ml/calendars/{calendar_id}"); + initEndpoint(routes, "create", false, "/{index}/_create/{id}"); + initEndpoint( + routes, + "ml.preview_datafeed", + false, + "/_ml/datafeeds/{datafeed_id}/_preview", + "/_ml/datafeeds/_preview"); + initEndpoint(routes, "indices.put_template", false, "/_template/{name}"); + initEndpoint( + routes, + "nodes.reload_secure_settings", + false, + "/_nodes/reload_secure_settings", + "/_nodes/{node_id}/reload_secure_settings"); + initEndpoint(routes, "indices.delete_data_stream", false, "/_data_stream/{name}"); + initEndpoint( + routes, + "transform.schedule_now_transform", + false, + "/_transform/{transform_id}/_schedule_now"); + initEndpoint(routes, "slm.stop", false, "/_slm/stop"); + initEndpoint(routes, "rollup.delete_job", false, "/_rollup/job/{id}"); + initEndpoint(routes, "cluster.put_component_template", false, "/_component_template/{name}"); + initEndpoint(routes, "delete_script", false, "/_scripts/{id}"); + initEndpoint(routes, "ml.delete_trained_model", false, "/_ml/trained_models/{model_id}"); + initEndpoint( + routes, + "indices.simulate_template", + false, + "/_index_template/_simulate", + "/_index_template/_simulate/{name}"); + initEndpoint(routes, "slm.get_lifecycle", false, "/_slm/policy/{policy_id}", "/_slm/policy"); + initEndpoint(routes, "security.enroll_kibana", false, "/_security/enroll/kibana"); + initEndpoint(routes, "fleet.search", false, "/{index}/_fleet/_fleet_search"); + initEndpoint(routes, "reindex_rethrottle", false, "/_reindex/{task_id}/_rethrottle"); + initEndpoint(routes, "ml.update_filter", false, "/_ml/filters/{filter_id}/_update"); + initEndpoint(routes, "rollup.get_rollup_caps", false, "/_rollup/data/{id}", "/_rollup/data"); + initEndpoint( + routes, "ccr.resume_auto_follow_pattern", false, "/_ccr/auto_follow/{name}/resume"); + initEndpoint(routes, "features.get_features", false, "/_features"); + initEndpoint(routes, "slm.get_stats", false, "/_slm/stats"); + initEndpoint(routes, "indices.clear_cache", false, "/_cache/clear", "/{index}/_cache/clear"); + initEndpoint( + routes, + "cluster.post_voting_config_exclusions", + false, + "/_cluster/voting_config_exclusions"); + initEndpoint(routes, "index", false, "/{index}/_doc/{id}", "/{index}/_doc"); + initEndpoint(routes, "cat.pending_tasks", false, "/_cat/pending_tasks"); + initEndpoint(routes, "indices.promote_data_stream", false, "/_data_stream/_promote/{name}"); + initEndpoint(routes, "ml.delete_filter", false, "/_ml/filters/{filter_id}"); + initEndpoint(routes, "sql.query", false, "/_sql"); + initEndpoint(routes, "ccr.follow_stats", false, "/{index}/_ccr/stats"); + initEndpoint(routes, "transform.stop_transform", false, "/_transform/{transform_id}/_stop"); + initEndpoint( + routes, + "security.has_privileges_user_profile", + false, + "/_security/profile/_has_privileges"); + initEndpoint( + routes, "autoscaling.delete_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "scripts_painless_execute", false, "/_scripts/painless/_execute"); + initEndpoint(routes, "indices.delete", false, "/{index}"); + initEndpoint( + routes, "security.clear_cached_roles", false, "/_security/role/{name}/_clear_cache"); + initEndpoint(routes, "eql.delete", false, "/_eql/search/{id}"); + initEndpoint(routes, "update", false, "/{index}/_update/{id}"); + initEndpoint( + routes, + "snapshot.clone", + false, + "/_snapshot/{repository}/{snapshot}/_clone/{target_snapshot}"); + initEndpoint(routes, "license.get_basic_status", false, "/_license/basic_status"); + initEndpoint(routes, "indices.close", false, "/{index}/_close"); + initEndpoint(routes, "security.saml_authenticate", false, "/_security/saml/authenticate"); + initEndpoint( + routes, "search_application.put", false, "/_application/search_application/{name}"); + initEndpoint(routes, "count", false, "/_count", "/{index}/_count"); + initEndpoint( + routes, + "migration.deprecations", + false, + "/_migration/deprecations", + "/{index}/_migration/deprecations"); + initEndpoint(routes, "indices.segments", false, "/_segments", "/{index}/_segments"); + initEndpoint(routes, "security.suggest_user_profiles", false, "/_security/profile/_suggest"); + initEndpoint(routes, "security.get_user_privileges", false, "/_security/user/_privileges"); + initEndpoint( + routes, + "indices.delete_alias", + false, + "/{index}/_alias/{name}", + "/{index}/_aliases/{name}"); + initEndpoint(routes, "indices.get_mapping", false, "/_mapping", "/{index}/_mapping"); + initEndpoint(routes, "indices.put_index_template", false, "/_index_template/{name}"); + initEndpoint( + routes, + "searchable_snapshots.stats", + false, + "/_searchable_snapshots/stats", + "/{index}/_searchable_snapshots/stats"); + initEndpoint(routes, "security.disable_user", false, "/_security/user/{username}/_disable"); + initEndpoint( + routes, + "ml.upgrade_job_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade"); + initEndpoint(routes, "delete", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "async_search.delete", false, "/_async_search/{id}"); + initEndpoint( + routes, "cat.transforms", false, "/_cat/transforms", "/_cat/transforms/{transform_id}"); + initEndpoint(routes, "ping", false, "/"); + initEndpoint(routes, "ccr.pause_auto_follow_pattern", false, "/_ccr/auto_follow/{name}/pause"); + initEndpoint(routes, "indices.shard_stores", false, "/_shard_stores", "/{index}/_shard_stores"); + initEndpoint( + routes, "ml.update_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_update"); + initEndpoint(routes, "logstash.delete_pipeline", false, "/_logstash/pipeline/{id}"); + initEndpoint(routes, "sql.translate", false, "/_sql/translate"); + initEndpoint(routes, "exists", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "snapshot.get_repository", false, "/_snapshot", "/_snapshot/{repository}"); + initEndpoint(routes, "snapshot.verify_repository", false, "/_snapshot/{repository}/_verify"); + initEndpoint(routes, "indices.put_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint(routes, "ml.open_job", false, "/_ml/anomaly_detectors/{job_id}/_open"); + initEndpoint( + routes, "security.update_user_profile_data", false, "/_security/profile/{uid}/_data"); + initEndpoint(routes, "enrich.put_policy", false, "/_enrich/policy/{name}"); + initEndpoint( + routes, + "ml.get_datafeed_stats", + false, + "/_ml/datafeeds/{datafeed_id}/_stats", + "/_ml/datafeeds/_stats"); + initEndpoint(routes, "open_point_in_time", false, "/{index}/_pit"); + initEndpoint(routes, "get_source", false, "/{index}/_source/{id}"); + initEndpoint(routes, "delete_by_query", false, "/{index}/_delete_by_query"); + initEndpoint(routes, "security.create_api_key", false, "/_security/api_key"); + initEndpoint(routes, "cat.tasks", false, "/_cat/tasks"); + initEndpoint(routes, "watcher.delete_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "ingest.processor_grok", false, "/_ingest/processor/grok"); + initEndpoint(routes, "ingest.put_pipeline", false, "/_ingest/pipeline/{id}"); + initEndpoint( + routes, + "ml.get_data_frame_analytics_stats", + false, + "/_ml/data_frame/analytics/_stats", + "/_ml/data_frame/analytics/{id}/_stats"); + initEndpoint( + routes, + "indices.data_streams_stats", + false, + "/_data_stream/_stats", + "/_data_stream/{name}/_stats"); + initEndpoint( + routes, "security.clear_cached_realms", false, "/_security/realm/{realms}/_clear_cache"); + initEndpoint(routes, "field_caps", false, "/_field_caps", "/{index}/_field_caps"); + initEndpoint(routes, "ml.evaluate_data_frame", false, "/_ml/data_frame/_evaluate"); + initEndpoint( + routes, + "ml.delete_forecast", + false, + "/_ml/anomaly_detectors/{job_id}/_forecast", + "/_ml/anomaly_detectors/{job_id}/_forecast/{forecast_id}"); + initEndpoint(routes, "enrich.get_policy", false, "/_enrich/policy/{name}", "/_enrich/policy"); + initEndpoint(routes, "rollup.start_job", false, "/_rollup/job/{id}/_start"); + initEndpoint(routes, "tasks.cancel", false, "/_tasks/_cancel", "/_tasks/{task_id}/_cancel"); + initEndpoint(routes, "security.saml_logout", false, "/_security/saml/logout"); + initEndpoint( + routes, "render_search_template", true, "/_render/template", "/_render/template/{id}"); + initEndpoint(routes, "ml.get_calendar_events", false, "/_ml/calendars/{calendar_id}/events"); + initEndpoint(routes, "security.enable_user_profile", false, "/_security/profile/{uid}/_enable"); + initEndpoint( + routes, "logstash.get_pipeline", false, "/_logstash/pipeline", "/_logstash/pipeline/{id}"); + initEndpoint(routes, "cat.snapshots", false, "/_cat/snapshots", "/_cat/snapshots/{repository}"); + initEndpoint(routes, "indices.add_block", false, "/{index}/_block/{block}"); + initEndpoint(routes, "terms_enum", true, "/{index}/_terms_enum"); + initEndpoint(routes, "ml.forecast", false, "/_ml/anomaly_detectors/{job_id}/_forecast"); + initEndpoint( + routes, "cluster.stats", false, "/_cluster/stats", "/_cluster/stats/nodes/{node_id}"); + initEndpoint(routes, "search_application.list", false, "/_application/search_application"); + initEndpoint(routes, "cat.count", false, "/_cat/count", "/_cat/count/{index}"); + initEndpoint(routes, "cat.segments", false, "/_cat/segments", "/_cat/segments/{index}"); + initEndpoint(routes, "ccr.resume_follow", false, "/{index}/_ccr/resume_follow"); + initEndpoint( + routes, "search_application.get", false, "/_application/search_application/{name}"); + initEndpoint( + routes, + "security.saml_service_provider_metadata", + false, + "/_security/saml/metadata/{realm_name}"); + initEndpoint(routes, "update_by_query", false, "/{index}/_update_by_query"); + initEndpoint(routes, "ml.stop_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_stop"); + initEndpoint(routes, "ilm.explain_lifecycle", false, "/{index}/_ilm/explain"); + initEndpoint( + routes, + "ml.put_trained_model_vocabulary", + false, + "/_ml/trained_models/{model_id}/vocabulary"); + initEndpoint(routes, "indices.exists", false, "/{index}"); + initEndpoint(routes, "ml.set_upgrade_mode", false, "/_ml/set_upgrade_mode"); + initEndpoint(routes, "security.saml_invalidate", false, "/_security/saml/invalidate"); + initEndpoint( + routes, + "ml.get_job_stats", + false, + "/_ml/anomaly_detectors/_stats", + "/_ml/anomaly_detectors/{job_id}/_stats"); + initEndpoint(routes, "cluster.allocation_explain", false, "/_cluster/allocation/explain"); + initEndpoint(routes, "watcher.activate_watch", false, "/_watcher/watch/{watch_id}/_activate"); + initEndpoint( + routes, + "searchable_snapshots.clear_cache", + false, + "/_searchable_snapshots/cache/clear", + "/{index}/_searchable_snapshots/cache/clear"); + initEndpoint( + routes, "msearch_template", true, "/_msearch/template", "/{index}/_msearch/template"); + initEndpoint(routes, "bulk", false, "/_bulk", "/{index}/_bulk"); + initEndpoint(routes, "cat.nodeattrs", false, "/_cat/nodeattrs"); + initEndpoint( + routes, "indices.get_index_template", false, "/_index_template", "/_index_template/{name}"); + initEndpoint(routes, "license.get", false, "/_license"); + initEndpoint(routes, "ccr.forget_follower", false, "/{index}/_ccr/forget_follower"); + initEndpoint(routes, "security.delete_role", false, "/_security/role/{name}"); + initEndpoint( + routes, "indices.validate_query", false, "/_validate/query", "/{index}/_validate/query"); + initEndpoint(routes, "tasks.get", false, "/_tasks/{task_id}"); + initEndpoint( + routes, "ml.start_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_start"); + initEndpoint(routes, "indices.create", false, "/{index}"); + initEndpoint( + routes, + "cluster.delete_voting_config_exclusions", + false, + "/_cluster/voting_config_exclusions"); + initEndpoint(routes, "info", false, "/"); + initEndpoint(routes, "watcher.stop", false, "/_watcher/_stop"); + initEndpoint(routes, "enrich.delete_policy", false, "/_enrich/policy/{name}"); + initEndpoint( + routes, + "cat.ml_data_frame_analytics", + false, + "/_cat/ml/data_frame/analytics", + "/_cat/ml/data_frame/analytics/{id}"); + initEndpoint( + routes, + "security.change_password", + false, + "/_security/user/{username}/_password", + "/_security/user/_password"); + initEndpoint(routes, "put_script", false, "/_scripts/{id}", "/_scripts/{id}/{context}"); + initEndpoint(routes, "ml.put_datafeed", false, "/_ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "cat.master", false, "/_cat/master"); + initEndpoint(routes, "features.reset_features", false, "/_features/_reset"); + initEndpoint(routes, "indices.get_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint( + routes, + "ml.get_data_frame_analytics", + false, + "/_ml/data_frame/analytics/{id}", + "/_ml/data_frame/analytics"); + initEndpoint( + routes, + "security.delete_service_token", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}"); + initEndpoint(routes, "indices.recovery", false, "/_recovery", "/{index}/_recovery"); + initEndpoint(routes, "cat.recovery", false, "/_cat/recovery", "/_cat/recovery/{index}"); + initEndpoint(routes, "indices.downsample", false, "/{index}/_downsample/{target_index}"); + initEndpoint(routes, "ingest.delete_pipeline", false, "/_ingest/pipeline/{id}"); + initEndpoint(routes, "async_search.get", false, "/_async_search/{id}"); + initEndpoint(routes, "eql.get", false, "/_eql/search/{id}"); + initEndpoint(routes, "cat.aliases", false, "/_cat/aliases", "/_cat/aliases/{name}"); + initEndpoint( + routes, + "security.get_service_credentials", + false, + "/_security/service/{namespace}/{service}/credential"); + initEndpoint(routes, "cat.allocation", false, "/_cat/allocation", "/_cat/allocation/{node_id}"); + initEndpoint( + routes, "ml.stop_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}/_stop"); + initEndpoint(routes, "indices.open", false, "/{index}/_open"); + initEndpoint(routes, "ilm.get_lifecycle", false, "/_ilm/policy/{policy}", "/_ilm/policy"); + initEndpoint(routes, "ilm.remove_policy", false, "/{index}/_ilm/remove"); + initEndpoint( + routes, + "security.get_role_mapping", + false, + "/_security/role_mapping/{name}", + "/_security/role_mapping"); + initEndpoint(routes, "snapshot.create", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "watcher.get_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "license.post_start_trial", false, "/_license/start_trial"); + initEndpoint(routes, "snapshot.restore", false, "/_snapshot/{repository}/{snapshot}/_restore"); + initEndpoint(routes, "indices.put_mapping", false, "/{index}/_mapping"); + initEndpoint( + routes, "ml.delete_calendar_job", false, "/_ml/calendars/{calendar_id}/jobs/{job_id}"); + initEndpoint( + routes, "security.clear_api_key_cache", false, "/_security/api_key/{ids}/_clear_cache"); + initEndpoint(routes, "slm.start", false, "/_slm/start"); + initEndpoint( + routes, + "cat.component_templates", + false, + "/_cat/component_templates", + "/_cat/component_templates/{name}"); + initEndpoint(routes, "security.enable_user", false, "/_security/user/{username}/_enable"); + initEndpoint(routes, "cluster.delete_component_template", false, "/_component_template/{name}"); + initEndpoint(routes, "security.get_role", false, "/_security/role/{name}", "/_security/role"); + initEndpoint( + routes, "ingest.get_pipeline", false, "/_ingest/pipeline", "/_ingest/pipeline/{id}"); + initEndpoint( + routes, + "ml.delete_expired_data", + false, + "/_ml/_delete_expired_data/{job_id}", + "/_ml/_delete_expired_data"); + initEndpoint( + routes, + "indices.get_settings", + false, + "/_settings", + "/{index}/_settings", + "/{index}/_settings/{name}", + "/_settings/{name}"); + initEndpoint(routes, "ccr.follow", false, "/{index}/_ccr/follow"); + initEndpoint( + routes, "termvectors", false, "/{index}/_termvectors/{id}", "/{index}/_termvectors"); + initEndpoint(routes, "ml.post_data", false, "/_ml/anomaly_detectors/{job_id}/_data"); + initEndpoint(routes, "eql.search", true, "/{index}/_eql/search"); + initEndpoint( + routes, + "ml.get_trained_models", + false, + "/_ml/trained_models/{model_id}", + "/_ml/trained_models"); + initEndpoint( + routes, "security.disable_user_profile", false, "/_security/profile/{uid}/_disable"); + initEndpoint(routes, "security.put_privileges", false, "/_security/privilege"); + initEndpoint(routes, "cat.nodes", false, "/_cat/nodes"); + initEndpoint( + routes, "nodes.info", false, "/_nodes", "/_nodes/{node_id}", "/_nodes/{node_id}/{metric}"); + initEndpoint(routes, "graph.explore", false, "/{index}/_graph/explore"); + initEndpoint( + routes, "autoscaling.put_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "cat.templates", false, "/_cat/templates", "/_cat/templates/{name}"); + initEndpoint(routes, "cluster.remote_info", false, "/_remote/info"); + initEndpoint(routes, "rank_eval", false, "/_rank_eval", "/{index}/_rank_eval"); + initEndpoint( + routes, "security.delete_privileges", false, "/_security/privilege/{application}/{name}"); + initEndpoint( + routes, + "security.get_privileges", + false, + "/_security/privilege", + "/_security/privilege/{application}", + "/_security/privilege/{application}/{name}"); + initEndpoint(routes, "scroll", false, "/_search/scroll"); + initEndpoint(routes, "license.delete", false, "/_license"); + initEndpoint(routes, "indices.disk_usage", false, "/{index}/_disk_usage"); + initEndpoint(routes, "msearch", true, "/_msearch", "/{index}/_msearch"); + initEndpoint(routes, "indices.field_usage_stats", false, "/{index}/_field_usage_stats"); + initEndpoint( + routes, "indices.rollover", false, "/{alias}/_rollover", "/{alias}/_rollover/{new_index}"); + initEndpoint( + routes, + "cat.ml_trained_models", + false, + "/_cat/ml/trained_models", + "/_cat/ml/trained_models/{model_id}"); + initEndpoint( + routes, + "ml.delete_trained_model_alias", + false, + "/_ml/trained_models/{model_id}/model_aliases/{model_alias}"); + initEndpoint(routes, "indices.get", false, "/{index}"); + initEndpoint(routes, "sql.get_async_status", false, "/_sql/async/status/{id}"); + initEndpoint(routes, "ilm.stop", false, "/_ilm/stop"); + initEndpoint(routes, "security.put_user", false, "/_security/user/{username}"); + initEndpoint( + routes, + "cluster.state", + false, + "/_cluster/state", + "/_cluster/state/{metric}", + "/_cluster/state/{metric}/{index}"); + initEndpoint(routes, "indices.put_settings", false, "/_settings", "/{index}/_settings"); + initEndpoint(routes, "knn_search", false, "/{index}/_knn_search"); + initEndpoint(routes, "get", false, "/{index}/_doc/{id}"); + initEndpoint(routes, "eql.get_status", false, "/_eql/search/status/{id}"); + initEndpoint(routes, "ssl.certificates", false, "/_ssl/certificates"); + initEndpoint( + routes, + "ml.get_model_snapshots", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}", + "/_ml/anomaly_detectors/{job_id}/model_snapshots"); + initEndpoint( + routes, + "nodes.clear_repositories_metering_archive", + false, + "/_nodes/{node_id}/_repositories_metering/{max_archive_version}"); + initEndpoint(routes, "security.put_role", false, "/_security/role/{name}"); + initEndpoint( + routes, "ml.get_influencers", false, "/_ml/anomaly_detectors/{job_id}/results/influencers"); + initEndpoint(routes, "transform.upgrade_transforms", false, "/_transform/_upgrade"); + initEndpoint( + routes, + "ml.delete_calendar_event", + false, + "/_ml/calendars/{calendar_id}/events/{event_id}"); + initEndpoint( + routes, + "indices.get_field_mapping", + false, + "/_mapping/field/{fields}", + "/{index}/_mapping/field/{fields}"); + initEndpoint( + routes, + "transform.preview_transform", + false, + "/_transform/{transform_id}/_preview", + "/_transform/_preview"); + initEndpoint(routes, "tasks.list", false, "/_tasks"); + initEndpoint( + routes, + "ml.clear_trained_model_deployment_cache", + false, + "/_ml/trained_models/{model_id}/deployment/cache/_clear"); + initEndpoint(routes, "cluster.reroute", false, "/_cluster/reroute"); + initEndpoint(routes, "security.saml_complete_logout", false, "/_security/saml/complete_logout"); + initEndpoint( + routes, + "indices.simulate_index_template", + false, + "/_index_template/_simulate_index/{name}"); + initEndpoint(routes, "snapshot.get", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "ccr.put_auto_follow_pattern", false, "/_ccr/auto_follow/{name}"); + initEndpoint( + routes, "nodes.hot_threads", false, "/_nodes/hot_threads", "/_nodes/{node_id}/hot_threads"); + initEndpoint( + routes, + "ml.preview_data_frame_analytics", + false, + "/_ml/data_frame/analytics/_preview", + "/_ml/data_frame/analytics/{id}/_preview"); + initEndpoint(routes, "indices.flush", false, "/_flush", "/{index}/_flush"); + initEndpoint(routes, "cluster.exists_component_template", false, "/_component_template/{name}"); + initEndpoint( + routes, + "snapshot.status", + false, + "/_snapshot/_status", + "/_snapshot/{repository}/_status", + "/_snapshot/{repository}/{snapshot}/_status"); + initEndpoint(routes, "ml.update_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_update"); + initEndpoint(routes, "indices.update_aliases", false, "/_aliases"); + initEndpoint(routes, "autoscaling.get_autoscaling_capacity", false, "/_autoscaling/capacity"); + initEndpoint(routes, "migration.post_feature_upgrade", false, "/_migration/system_features"); + initEndpoint( + routes, "ml.get_records", false, "/_ml/anomaly_detectors/{job_id}/results/records"); + initEndpoint( + routes, + "indices.get_alias", + false, + "/_alias", + "/_alias/{name}", + "/{index}/_alias/{name}", + "/{index}/_alias"); + initEndpoint(routes, "logstash.put_pipeline", false, "/_logstash/pipeline/{id}"); + initEndpoint(routes, "snapshot.delete_repository", false, "/_snapshot/{repository}"); + initEndpoint( + routes, + "security.has_privileges", + false, + "/_security/user/_has_privileges", + "/_security/user/{user}/_has_privileges"); + initEndpoint(routes, "cat.indices", false, "/_cat/indices", "/_cat/indices/{index}"); + initEndpoint( + routes, + "ccr.get_auto_follow_pattern", + false, + "/_ccr/auto_follow", + "/_ccr/auto_follow/{name}"); + initEndpoint(routes, "ml.start_datafeed", false, "/_ml/datafeeds/{datafeed_id}/_start"); + initEndpoint(routes, "indices.clone", false, "/{index}/_clone/{target}"); + initEndpoint( + routes, "search_application.delete", false, "/_application/search_application/{name}"); + initEndpoint(routes, "security.query_api_keys", false, "/_security/_query/api_key"); + initEndpoint(routes, "ml.flush_job", false, "/_ml/anomaly_detectors/{job_id}/_flush"); + initEndpoint( + routes, + "security.clear_cached_privileges", + false, + "/_security/privilege/{application}/_clear_cache"); + initEndpoint(routes, "indices.exists_index_template", false, "/_index_template/{name}"); + initEndpoint(routes, "indices.explain_data_lifecycle", false, "/{index}/_lifecycle/explain"); + initEndpoint( + routes, "indices.put_alias", false, "/{index}/_alias/{name}", "/{index}/_aliases/{name}"); + initEndpoint( + routes, + "ml.get_buckets", + false, + "/_ml/anomaly_detectors/{job_id}/results/buckets/{timestamp}", + "/_ml/anomaly_detectors/{job_id}/results/buckets"); + initEndpoint( + routes, + "ml.put_trained_model_definition_part", + false, + "/_ml/trained_models/{model_id}/definition/{part}"); + initEndpoint(routes, "get_script", false, "/_scripts/{id}"); + initEndpoint( + routes, + "ingest.simulate", + false, + "/_ingest/pipeline/_simulate", + "/_ingest/pipeline/{id}/_simulate"); + initEndpoint(routes, "indices.migrate_to_data_stream", false, "/_data_stream/_migrate/{name}"); + initEndpoint(routes, "enrich.execute_policy", false, "/_enrich/policy/{name}/_execute"); + initEndpoint(routes, "indices.split", false, "/{index}/_split/{target}"); + initEndpoint( + routes, + "ml.delete_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}"); + initEndpoint( + routes, + "nodes.usage", + false, + "/_nodes/usage", + "/_nodes/{node_id}/usage", + "/_nodes/usage/{metric}", + "/_nodes/{node_id}/usage/{metric}"); + initEndpoint(routes, "cat.help", false, "/_cat"); + initEndpoint( + routes, "ml.estimate_model_memory", false, "/_ml/anomaly_detectors/_estimate_model_memory"); + initEndpoint(routes, "exists_source", false, "/{index}/_source/{id}"); + initEndpoint(routes, "ml.put_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}"); + initEndpoint(routes, "security.put_role_mapping", false, "/_security/role_mapping/{name}"); + initEndpoint(routes, "rollup.get_rollup_index_caps", false, "/{index}/_rollup/data"); + initEndpoint(routes, "transform.reset_transform", false, "/_transform/{transform_id}/_reset"); + initEndpoint( + routes, + "ml.infer_trained_model", + false, + "/_ml/trained_models/{model_id}/_infer", + "/_ml/trained_models/{model_id}/deployment/_infer"); + initEndpoint(routes, "reindex", false, "/_reindex"); + initEndpoint(routes, "ml.put_trained_model", false, "/_ml/trained_models/{model_id}"); + initEndpoint( + routes, + "cat.ml_jobs", + false, + "/_cat/ml/anomaly_detectors", + "/_cat/ml/anomaly_detectors/{job_id}"); + initEndpoint( + routes, + "search_application.search", + false, + "/_application/search_application/{name}/_search"); + initEndpoint(routes, "ilm.put_lifecycle", false, "/_ilm/policy/{policy}"); + initEndpoint(routes, "security.get_token", false, "/_security/oauth2/token"); + initEndpoint(routes, "ilm.move_to_step", false, "/_ilm/move/{index}"); + initEndpoint(routes, "search_template", true, "/_search/template", "/{index}/_search/template"); + initEndpoint(routes, "indices.delete_data_lifecycle", false, "/_data_stream/{name}/_lifecycle"); + initEndpoint(routes, "indices.get_data_stream", false, "/_data_stream", "/_data_stream/{name}"); + initEndpoint(routes, "ml.get_filters", false, "/_ml/filters", "/_ml/filters/{filter_id}"); + initEndpoint( + routes, + "cat.ml_datafeeds", + false, + "/_cat/ml/datafeeds", + "/_cat/ml/datafeeds/{datafeed_id}"); + initEndpoint(routes, "rollup.rollup_search", false, "/{index}/_rollup_search"); + initEndpoint(routes, "ml.put_job", false, "/_ml/anomaly_detectors/{job_id}"); + initEndpoint( + routes, "update_by_query_rethrottle", false, "/_update_by_query/{task_id}/_rethrottle"); + initEndpoint(routes, "indices.delete_index_template", false, "/_index_template/{name}"); + initEndpoint( + routes, "indices.reload_search_analyzers", false, "/{index}/_reload_search_analyzers"); + initEndpoint(routes, "cluster.get_settings", false, "/_cluster/settings"); + initEndpoint(routes, "cluster.put_settings", false, "/_cluster/settings"); + initEndpoint(routes, "transform.put_transform", false, "/_transform/{transform_id}"); + initEndpoint(routes, "watcher.stats", false, "/_watcher/stats", "/_watcher/stats/{metric}"); + initEndpoint(routes, "ccr.delete_auto_follow_pattern", false, "/_ccr/auto_follow/{name}"); + initEndpoint(routes, "mtermvectors", false, "/_mtermvectors", "/{index}/_mtermvectors"); + initEndpoint(routes, "license.post", false, "/_license"); + initEndpoint(routes, "xpack.info", false, "/_xpack"); + initEndpoint( + routes, "dangling_indices.import_dangling_index", false, "/_dangling/{index_uuid}"); + initEndpoint( + routes, + "nodes.get_repositories_metering_info", + false, + "/_nodes/{node_id}/_repositories_metering"); + initEndpoint( + routes, "transform.get_transform_stats", false, "/_transform/{transform_id}/_stats"); + initEndpoint(routes, "mget", false, "/_mget", "/{index}/_mget"); + initEndpoint(routes, "security.get_builtin_privileges", false, "/_security/privilege/_builtin"); + initEndpoint( + routes, + "ml.update_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_update"); + initEndpoint(routes, "ml.info", false, "/_ml/info"); + initEndpoint(routes, "indices.exists_template", false, "/_template/{name}"); + initEndpoint( + routes, + "watcher.ack_watch", + false, + "/_watcher/watch/{watch_id}/_ack", + "/_watcher/watch/{watch_id}/_ack/{action_id}"); + initEndpoint( + routes, "security.get_user", false, "/_security/user/{username}", "/_security/user"); + initEndpoint( + routes, "shutdown.get_node", false, "/_nodes/shutdown", "/_nodes/{node_id}/shutdown"); + initEndpoint(routes, "watcher.start", false, "/_watcher/_start"); + initEndpoint(routes, "indices.shrink", false, "/{index}/_shrink/{target}"); + initEndpoint(routes, "license.post_start_basic", false, "/_license/start_basic"); + initEndpoint(routes, "xpack.usage", false, "/_xpack/usage"); + initEndpoint(routes, "ilm.delete_lifecycle", false, "/_ilm/policy/{policy}"); + initEndpoint(routes, "ccr.follow_info", false, "/{index}/_ccr/info"); + initEndpoint( + routes, "ml.put_calendar_job", false, "/_ml/calendars/{calendar_id}/jobs/{job_id}"); + initEndpoint(routes, "rollup.put_job", false, "/_rollup/job/{id}"); + initEndpoint(routes, "clear_scroll", false, "/_search/scroll"); + initEndpoint(routes, "ml.delete_data_frame_analytics", false, "/_ml/data_frame/analytics/{id}"); + initEndpoint(routes, "security.get_api_key", false, "/_security/api_key"); + initEndpoint(routes, "cat.health", false, "/_cat/health"); + initEndpoint(routes, "security.invalidate_token", false, "/_security/oauth2/token"); + initEndpoint(routes, "slm.delete_lifecycle", false, "/_slm/policy/{policy_id}"); + initEndpoint( + routes, + "ml.stop_trained_model_deployment", + false, + "/_ml/trained_models/{model_id}/deployment/_stop"); + initEndpoint(routes, "monitoring.bulk", false, "/_monitoring/bulk", "/_monitoring/{type}/bulk"); + initEndpoint( + routes, + "indices.stats", + false, + "/_stats", + "/_stats/{metric}", + "/{index}/_stats", + "/{index}/_stats/{metric}"); + initEndpoint( + routes, + "searchable_snapshots.cache_stats", + false, + "/_searchable_snapshots/cache/stats", + "/_searchable_snapshots/{node_id}/cache/stats"); + initEndpoint(routes, "async_search.submit", true, "/_async_search", "/{index}/_async_search"); + initEndpoint(routes, "rollup.get_jobs", false, "/_rollup/job/{id}", "/_rollup/job"); + initEndpoint( + routes, + "ml.revert_model_snapshot", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_revert"); + initEndpoint(routes, "transform.delete_transform", false, "/_transform/{transform_id}"); + initEndpoint(routes, "cluster.pending_tasks", false, "/_cluster/pending_tasks"); + initEndpoint( + routes, + "ml.get_model_snapshot_upgrade_stats", + false, + "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade/_stats"); + initEndpoint( + routes, + "ml.get_categories", + false, + "/_ml/anomaly_detectors/{job_id}/results/categories/{category_id}", + "/_ml/anomaly_detectors/{job_id}/results/categories"); + initEndpoint(routes, "ccr.pause_follow", false, "/{index}/_ccr/pause_follow"); + initEndpoint(routes, "security.authenticate", false, "/_security/_authenticate"); + initEndpoint(routes, "enrich.stats", false, "/_enrich/_stats"); + initEndpoint( + routes, + "ml.put_trained_model_alias", + false, + "/_ml/trained_models/{model_id}/model_aliases/{model_alias}"); + initEndpoint( + routes, + "ml.get_overall_buckets", + false, + "/_ml/anomaly_detectors/{job_id}/results/overall_buckets"); + initEndpoint(routes, "indices.get_template", false, "/_template", "/_template/{name}"); + initEndpoint(routes, "security.delete_role_mapping", false, "/_security/role_mapping/{name}"); + initEndpoint( + routes, "ml.get_datafeeds", false, "/_ml/datafeeds/{datafeed_id}", "/_ml/datafeeds"); + initEndpoint(routes, "slm.execute_lifecycle", false, "/_slm/policy/{policy_id}/_execute"); + initEndpoint(routes, "close_point_in_time", false, "/_pit"); + initEndpoint(routes, "snapshot.cleanup_repository", false, "/_snapshot/{repository}/_cleanup"); + initEndpoint( + routes, "autoscaling.get_autoscaling_policy", false, "/_autoscaling/policy/{name}"); + initEndpoint(routes, "slm.put_lifecycle", false, "/_slm/policy/{policy_id}"); + initEndpoint( + routes, "ml.get_jobs", false, "/_ml/anomaly_detectors/{job_id}", "/_ml/anomaly_detectors"); + initEndpoint( + routes, + "ml.get_trained_models_stats", + false, + "/_ml/trained_models/{model_id}/_stats", + "/_ml/trained_models/_stats"); + initEndpoint( + routes, "ml.validate_detector", false, "/_ml/anomaly_detectors/_validate/detector"); + initEndpoint(routes, "watcher.put_watch", false, "/_watcher/watch/{id}"); + initEndpoint(routes, "transform.update_transform", false, "/_transform/{transform_id}/_update"); + initEndpoint(routes, "ml.post_calendar_events", false, "/_ml/calendars/{calendar_id}/events"); + initEndpoint( + routes, "migration.get_feature_upgrade_status", false, "/_migration/system_features"); + initEndpoint(routes, "get_script_context", false, "/_script_context"); + initEndpoint(routes, "ml.put_filter", false, "/_ml/filters/{filter_id}"); + initEndpoint(routes, "ml.update_job", false, "/_ml/anomaly_detectors/{job_id}/_update"); + initEndpoint(routes, "ingest.geo_ip_stats", false, "/_ingest/geoip/stats"); + initEndpoint(routes, "security.delete_user", false, "/_security/user/{username}"); + initEndpoint(routes, "indices.unfreeze", false, "/{index}/_unfreeze"); + initEndpoint(routes, "snapshot.create_repository", false, "/_snapshot/{repository}"); + initEndpoint( + routes, + "cluster.get_component_template", + false, + "/_component_template", + "/_component_template/{name}"); + initEndpoint(routes, "ilm.migrate_to_data_tiers", false, "/_ilm/migrate_to_data_tiers"); + initEndpoint(routes, "indices.refresh", false, "/_refresh", "/{index}/_refresh"); + initEndpoint( + routes, "ml.get_calendars", false, "/_ml/calendars", "/_ml/calendars/{calendar_id}"); + initEndpoint( + routes, "watcher.deactivate_watch", false, "/_watcher/watch/{watch_id}/_deactivate"); + initEndpoint(routes, "cluster.health", false, "/_cluster/health", "/_cluster/health/{index}"); + initEndpoint( + routes, "dangling_indices.delete_dangling_index", false, "/_dangling/{index_uuid}"); + initEndpoint(routes, "health_report", false, "/_health_report", "/_health_report/{feature}"); + initEndpoint(routes, "watcher.query_watches", false, "/_watcher/_query/watches"); + initEndpoint(routes, "ccr.unfollow", false, "/{index}/_ccr/unfollow"); + initEndpoint(routes, "ml.validate", false, "/_ml/anomaly_detectors/_validate"); + initEndpoint(routes, "cat.plugins", false, "/_cat/plugins"); + initEndpoint( + routes, + "watcher.execute_watch", + false, + "/_watcher/watch/{id}/_execute", + "/_watcher/watch/_execute"); + initEndpoint(routes, "search_shards", false, "/_search_shards", "/{index}/_search_shards"); + initEndpoint(routes, "cat.shards", false, "/_cat/shards", "/_cat/shards/{index}"); + initEndpoint(routes, "ml.delete_job", false, "/_ml/anomaly_detectors/{job_id}"); + initEndpoint(routes, "ilm.start", false, "/_ilm/start"); + initEndpoint(routes, "security.get_user_profile", false, "/_security/profile/{uid}"); + initEndpoint(routes, "indices.modify_data_stream", false, "/_data_stream/_modify"); + initEndpoint(routes, "indices.exists_alias", false, "/_alias/{name}", "/{index}/_alias/{name}"); + initEndpoint(routes, "rollup.stop_job", false, "/_rollup/job/{id}/_stop"); + initEndpoint(routes, "dangling_indices.list_dangling_indices", false, "/_dangling"); + initEndpoint(routes, "snapshot.delete", false, "/_snapshot/{repository}/{snapshot}"); + initEndpoint(routes, "security.activate_user_profile", false, "/_security/profile/_activate"); + initEndpoint( + routes, + "ml.start_trained_model_deployment", + false, + "/_ml/trained_models/{model_id}/deployment/_start"); + initEndpoint(routes, "transform.start_transform", false, "/_transform/{transform_id}/_start"); + initEndpoint(routes, "cat.repositories", false, "/_cat/repositories"); + initEndpoint(routes, "ilm.get_status", false, "/_ilm/status"); + initEndpoint(routes, "shutdown.delete_node", false, "/_nodes/{node_id}/shutdown"); + initEndpoint( + routes, + "nodes.stats", + false, + "/_nodes/stats", + "/_nodes/{node_id}/stats", + "/_nodes/stats/{metric}", + "/_nodes/{node_id}/stats/{metric}", + "/_nodes/stats/{metric}/{index_metric}", + "/_nodes/{node_id}/stats/{metric}/{index_metric}"); + initEndpoint(routes, "get_script_languages", false, "/_script_language"); + initEndpoint(routes, "slm.execute_retention", false, "/_slm/_execute_retention"); + initEndpoint( + routes, + "security.get_service_accounts", + false, + "/_security/service/{namespace}/{service}", + "/_security/service/{namespace}", + "/_security/service"); + initEndpoint(routes, "shutdown.put_node", false, "/_nodes/{node_id}/shutdown"); + initEndpoint(routes, "indices.resolve_index", false, "/_resolve/index/{name}"); + initEndpoint(routes, "search", true, "/_search", "/{index}/_search"); + initEndpoint(routes, "sql.get_async", false, "/_sql/async/{id}"); + initEndpoint( + routes, "delete_by_query_rethrottle", false, "/_delete_by_query/{task_id}/_rethrottle"); + initEndpoint( + routes, "transform.get_transform", false, "/_transform/{transform_id}", "/_transform"); + initEndpoint(routes, "security.invalidate_api_key", false, "/_security/api_key"); + initEndpoint(routes, "security.saml_prepare_authentication", false, "/_security/saml/prepare"); + initEndpoint( + routes, "ml.get_memory_stats", false, "/_ml/memory/_stats", "/_ml/memory/{node_id}/_stats"); + initEndpoint(routes, "ccr.stats", false, "/_ccr/stats"); + initEndpoint(routes, "indices.forcemerge", false, "/_forcemerge", "/{index}/_forcemerge"); + initEndpoint(routes, "indices.delete_template", false, "/_template/{name}"); + initEndpoint(routes, "sql.delete_async", false, "/_sql/async/delete/{id}"); + initEndpoint(routes, "security.update_api_key", false, "/_security/api_key/{id}"); + initEndpoint( + routes, + "security.create_service_token", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}", + "/_security/service/{namespace}/{service}/credential/token"); + initEndpoint(routes, "license.get_trial_status", false, "/_license/trial_status"); + initEndpoint( + routes, "searchable_snapshots.mount", false, "/_snapshot/{repository}/{snapshot}/_mount"); + initEndpoint(routes, "security.grant_api_key", false, "/_security/api_key/grant"); + initEndpoint(routes, "ilm.retry", false, "/{index}/_ilm/retry"); + initEndpoint(routes, "ml.reset_job", false, "/_ml/anomaly_detectors/{job_id}/_reset"); + initEndpoint(routes, "ml.close_job", false, "/_ml/anomaly_detectors/{job_id}/_close"); + initEndpoint( + routes, + "ml.explain_data_frame_analytics", + false, + "/_ml/data_frame/analytics/_explain", + "/_ml/data_frame/analytics/{id}/_explain"); + initEndpoint( + routes, + "security.clear_cached_service_tokens", + false, + "/_security/service/{namespace}/{service}/credential/token/{name}/_clear_cache"); + initEndpoint(routes, "search_mvt", false, "/{index}/_mvt/{field}/{zoom}/{x}/{y}"); + routesMap = Collections.unmodifiableMap(routes); + } + + private ElasticsearchEndpointMap() { + } + + private static void initEndpoint( + Map map, + String endpointId, + boolean isSearchEndpoint, + String... routes) { + ElasticsearchEndpointDefinition endpointDef = + new ElasticsearchEndpointDefinition(endpointId, routes, isSearchEndpoint); + map.put(endpointId, endpointDef); + } + + @Nullable + public static ElasticsearchEndpointDefinition get(String endpointId) { + return routesMap.get(endpointId); + } + + public static Collection getAllEndpoints() { + return routesMap.values(); + } +} From 103fcb094f8b3ec8c642fce1d6e83265c54727fa Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 12 Jul 2023 09:45:10 +0200 Subject: [PATCH 05/10] Updated to match latest OTel spec --- .../AbstractElasticsearchJavaTest.java | 20 +- .../ElasticsearchEndpointDefinition.java | 61 +- .../ElasticsearchEndpointMap.java | 5 +- ...searchRestClientInstrumentationHelper.java | 37 +- .../EndpointResolutionHelper.java | 534 ------------------ .../AbstractEsClientInstrumentationTest.java | 21 +- 6 files changed, 84 insertions(+), 594 deletions(-) delete mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java index c42cc173ba..b1feb13552 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java @@ -135,7 +135,7 @@ public void testTryToDeleteNonExistingIndex() { Span span = reporter.getFirstSpan(); assertThat(span.getOutcome()).isEqualTo(Outcome.FAILURE); - validateSpanContent(span, "Elasticsearch: DELETE /{index}", 404, "DELETE", SECOND_INDEX); + validateSpanContent(span, "Elasticsearch: indices.delete", 404, "DELETE", Map.of("index", SECOND_INDEX)); } @Test @@ -145,7 +145,7 @@ public void testDocumentScenario() throws Exception { List spans = reporter.getSpans(); try { assertThat(spans).hasSize(1); - validateSpanContent(spans.get(0), "Elasticsearch: PUT /{index}/_doc/{id}", 201, "PUT", INDEX, DOC_ID); + validateSpanContent(spans.get(0), "Elasticsearch: index", 201, "PUT", Map.of("index", INDEX, "id", DOC_ID)); // *** RESET *** reporter.reset(); @@ -159,7 +159,7 @@ public void testDocumentScenario() throws Exception { spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span searchSpan = spans.get(0); - validateSpanContent(searchSpan, "Elasticsearch: POST /{index}/_search", 200, "POST", INDEX); + validateSpanContent(searchSpan, "Elasticsearch: search", 200, "POST", Map.of("index", INDEX)); validateDbContextContent(searchSpan, "{\"from\":0,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}"); // *** RESET *** @@ -179,9 +179,9 @@ public void testDocumentScenario() throws Exception { spans = reporter.getSpans(); assertThat(spans).hasSize(2); Span updateSpan = spans.get(0); - validateSpanContent(updateSpan, "Elasticsearch: POST /{index}/_update/{id}", 200, "POST", INDEX, DOC_ID); + validateSpanContent(updateSpan, "Elasticsearch: update", 200, "POST", Map.of("index", INDEX, "id", DOC_ID)); searchSpan = spans.get(1); - validateSpanContent(searchSpan, "Elasticsearch: POST /{index}/_search", 200, "POST", INDEX); + validateSpanContent(searchSpan, "Elasticsearch: search", 200, "POST", Map.of("index", INDEX)); validateDbContextContent(searchSpan, "{}"); // *** RESET *** @@ -191,7 +191,7 @@ public void testDocumentScenario() throws Exception { // 4. Delete document and validate span content. co.elastic.clients.elasticsearch.core.DeleteResponse dr = deleteDocument(); assertThat(dr.result().jsonValue()).isEqualTo("deleted"); - validateSpanContent(spans.get(0), "Elasticsearch: DELETE /{index}/_doc/{id}", 200, "DELETE", INDEX, DOC_ID); + validateSpanContent(spans.get(0), "Elasticsearch: delete", 200, "DELETE", Map.of("index", INDEX, "id", DOC_ID)); } } @@ -210,7 +210,7 @@ public void testCountRequest_validateSpanContentAndDbContext() throws Exception List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: POST /{index}/_count", 200, "POST", INDEX); + validateSpanContent(span, "Elasticsearch: count", 200, "POST", Map.of("index", INDEX)); validateDbContextContent(span, "{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}}}"); } finally { deleteDocument(); @@ -281,7 +281,7 @@ public void testRollupSearch_validateSpanContentAndDbContext() throws Interrupte List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: POST /{index}/_rollup_search", 200, "POST", INDEX); + validateSpanContent(span, "Elasticsearch: rollup.rollup_search", 200, "POST", Map.of("index", INDEX)); validateDbContextContent(span, "{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}"); } finally { deleteDocument(); @@ -302,7 +302,7 @@ public void testSearchTemplateRequest_validateSpanContentAndDbContext() throws I List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: POST /{index}/_search/template", 200, "POST", INDEX); + validateSpanContent(span, "Elasticsearch: search_template", 200, "POST", Map.of("index", INDEX)); validateDbContextContent(span, "{\"id\":\"elastic-search-template\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"}}"); } finally { deleteMustacheScript(); @@ -341,7 +341,7 @@ public void testMultisearchTemplateRequest_validateSpanContentAndDbContext() thr List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: POST /_msearch/template", 200, "POST"); + validateSpanContent(span, "Elasticsearch: msearch_template", 200, "POST", Map.of()); verifyMultiSearchTemplateSpanContent(span); } finally { deleteMustacheScript(); diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java index 4aae0ea16a..69b81cba42 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java @@ -5,16 +5,21 @@ package co.elastic.apm.agent.esrestclient; -import javax.annotation.Nullable; +import co.elastic.apm.agent.tracer.Span; + import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; -import java.util.function.BiConsumer; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class ElasticsearchEndpointDefinition { + private static final String OTEL_PATH_PARTS_ATTRIBUTE_PREFIX = "db.elasticsearch.path_parts."; + private static final String UNDERSCORE_REPLACEMENT = "0"; private final String endpointName; @@ -32,7 +37,7 @@ public ElasticsearchEndpointDefinition( this.isSearchEndpoint = isSearchEndpoint; } - @Nullable + public String getEndpointName() { return endpointName; } @@ -41,18 +46,16 @@ public boolean isSearchEndpoint() { return isSearchEndpoint; } - public void processPathParts(String urlPath, BiConsumer consumer) { + public void addPathPartAttributes(String urlPath, Span spanToEnrich) { for (Route route : routes) { if (route.hasParameters()) { - Matcher matcher = route.createMatcher(urlPath); + EndpointPattern pattern = route.getEndpointPattern(); + Matcher matcher = pattern.createMatcher(urlPath); if (matcher.find()) { - for (String key : route.getPathPartNames()) { - String value = matcher.group(key); - if (key.contains(UNDERSCORE_REPLACEMENT)) { - // replace underscore back - key = key.replace(UNDERSCORE_REPLACEMENT, "_"); - } - consumer.accept(key, value); + for (String groupName : pattern.getPatternGroupNames()) { + String value = matcher.group(groupName); + String attributeKey = pattern.getOtelPathPartAttributeName(groupName); + spanToEnrich.withOtelAttribute(attributeKey, value); } return; } @@ -83,14 +86,6 @@ boolean hasParameters() { return hasParameters; } - List getPathPartNames() { - return getEndpointPattern().getPathPartNames(); - } - - Matcher createMatcher(String urlPath) { - return getEndpointPattern().getPattern().matcher(urlPath); - } - private EndpointPattern getEndpointPattern() { // Intentionally NOT synchronizing here to avoid synchronization overhead. // Main purpose here is to cache the pattern without the need for strict thread-safety. @@ -105,7 +100,7 @@ private EndpointPattern getEndpointPattern() { static final class EndpointPattern { private static final Pattern PATH_PART_NAMES_PATTERN = Pattern.compile("\\{([^}]+)}"); private final Pattern pattern; - private final List pathPartNames; + private final Map pathPartNamesToOtelAttributes; /** * Creates, compiles and caches a regular expression pattern and retrieves a set of @@ -118,18 +113,18 @@ private EndpointPattern(Route route) { pattern = buildRegexPattern(route.getName()); if (route.hasParameters()) { - pathPartNames = new ArrayList<>(); + pathPartNamesToOtelAttributes = new HashMap<>(); Matcher matcher = PATH_PART_NAMES_PATTERN.matcher(route.getName()); while (matcher.find()) { String groupName = matcher.group(1); if (groupName != null) { - groupName = groupName.replace("_", UNDERSCORE_REPLACEMENT); - pathPartNames.add(groupName); + String actualPatternGroupName = groupName.replace("_", UNDERSCORE_REPLACEMENT); + pathPartNamesToOtelAttributes.put(actualPatternGroupName, OTEL_PATH_PARTS_ATTRIBUTE_PREFIX + groupName); } } } else { - pathPartNames = Collections.emptyList(); + pathPartNamesToOtelAttributes = Collections.emptyMap(); } } @@ -166,12 +161,20 @@ static Pattern buildRegexPattern(String routeStr) { return Pattern.compile(regexStr.toString()); } - Pattern getPattern() { - return pattern; + Matcher createMatcher(String urlPath) { + return pattern.matcher(urlPath); + } + + String getOtelPathPartAttributeName(String patternGroupName) { + String attributeName = pathPartNamesToOtelAttributes.get(patternGroupName); + if (attributeName == null) { + throw new IllegalArgumentException(patternGroupName + " is not a group of this pattern!"); + } + return attributeName; } - List getPathPartNames() { - return pathPartNames; + Collection getPatternGroupNames() { + return pathPartNamesToOtelAttributes.keySet(); } } } diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java index 11206fb192..4247a531a0 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java @@ -873,7 +873,10 @@ private static void initEndpoint( } @Nullable - public static ElasticsearchEndpointDefinition get(String endpointId) { + public static ElasticsearchEndpointDefinition get(@Nullable String endpointId) { + if (endpointId == null) { + return null; + } return routesMap.get(endpointId); } diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java index 022b5e88c5..2d59197611 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java @@ -43,7 +43,7 @@ public class ElasticsearchRestClientInstrumentationHelper { - private static final WeakMap requestEndpointIdMap = WeakConcurrent.buildMap(); + private static final WeakMap requestEndpointMap = WeakConcurrent.buildMap(); private static final Logger logger = LoggerFactory.getLogger(ElasticsearchRestClientInstrumentationHelper.class); private static final Logger unsupportedOperationOnceLogger = LoggerUtils.logOnce(logger); @@ -77,22 +77,28 @@ public ResponseListenerWrapper createInstance() { } public void registerEndpointId(Object requestObj, String endpointId) { - requestEndpointIdMap.put(requestObj, endpointId); + if (endpointId.startsWith("es/") && endpointId.length() > 3) { + endpointId = endpointId.substring(3); + } + ElasticsearchEndpointDefinition endpoint = ElasticsearchEndpointMap.get(endpointId); + if (endpoint != null) { + requestEndpointMap.put(requestObj, endpoint); + } } @Nullable - public Span createClientSpan(Object requestObj, String method, String endpoint, @Nullable HttpEntity httpEntity) { - String endpointId = requestEndpointIdMap.remove(requestObj); - return createClientSpan(method, endpoint, httpEntity, endpointId); + public Span createClientSpan(Object requestObj, String method, String httpPath, @Nullable HttpEntity httpEntity) { + ElasticsearchEndpointDefinition endpoint = requestEndpointMap.remove(requestObj); + return createClientSpan(method, httpPath, httpEntity, endpoint); } @Nullable - public Span createClientSpan(String method, String endpoint, @Nullable HttpEntity httpEntity) { - return createClientSpan(method, endpoint, httpEntity, null); + public Span createClientSpan(String method, String httpPath, @Nullable HttpEntity httpEntity) { + return createClientSpan(method, httpPath, httpEntity, null); } @Nullable - private Span createClientSpan(String method, String endpoint, @Nullable HttpEntity httpEntity, @Nullable String endpointId) { + private Span createClientSpan(String method, String httpPath, @Nullable HttpEntity httpEntity, @Nullable ElasticsearchEndpointDefinition endpoint) { final AbstractSpan activeSpan = tracer.getActive(); if (activeSpan == null) { return null; @@ -109,10 +115,17 @@ private Span createClientSpan(String method, String endpoint, @Nullable HttpE .withSubtype(ELASTICSEARCH) .withAction(SPAN_ACTION); - if (endpointId != null) { - EndpointResolutionHelper.get().enrichSpanWithRouteInformation(span, method, endpointId, endpoint); + StringBuilder name = span.getAndOverrideName(AbstractSpan.PRIORITY_HIGH_LEVEL_FRAMEWORK); + if (endpoint != null) { + if (name != null) { + name.append("Elasticsearch: ").append(endpoint.getEndpointName()); + } + span.withOtelAttribute("db.operation", endpoint.getEndpointName()); + endpoint.addPathPartAttributes(httpPath, span); } else { - span.appendToName("Elasticsearch: ").appendToName(method).appendToName(" ").appendToName(endpoint); + if (name != null) { + name.append("Elasticsearch: ").append(method).append(" ").append(httpPath); + } } span.getContext().getDb().withType(ELASTICSEARCH); @@ -120,7 +133,7 @@ private Span createClientSpan(String method, String endpoint, @Nullable HttpE span.activate(); if (span.isSampled()) { span.getContext().getHttp().withMethod(method); - if (WildcardMatcher.isAnyMatch(config.getCaptureBodyUrls(), endpoint)) { + if (WildcardMatcher.isAnyMatch(config.getCaptureBodyUrls(), httpPath)) { if (httpEntity != null && httpEntity.isRepeatable()) { try { IOUtils.readUtf8Stream(httpEntity.getContent(), span.getContext().getDb().withStatementBuffer()); diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java deleted file mode 100644 index 6b17835d03..0000000000 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/EndpointResolutionHelper.java +++ /dev/null @@ -1,534 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package co.elastic.apm.agent.esrestclient; - -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.tracer.AbstractSpan; -import co.elastic.apm.agent.tracer.Span; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class EndpointResolutionHelper { - - private static final Logger logger = LoggerFactory.getLogger(EndpointResolutionHelper.class); - - @Nullable - private static EndpointResolutionHelper INSTANCE; - private final Map routesMap = new HashMap<>(); - private final Map regexPatternMap = new ConcurrentHashMap<>(); - - private EndpointResolutionHelper() { - // This map is generated from the Java client code. - // It's a one-time generation that shouldn't require maintenance effort, - // as in the future the ES client will come with a native instrumentation. - routesMap.put("es/async_search.status", new String[]{"/_async_search/status/{id}"}); - routesMap.put("es/indices.analyze", new String[]{"/_analyze", "/{index}/_analyze"}); - routesMap.put("es/sql.clear_cursor", new String[]{"/_sql/close"}); - routesMap.put("es/ml.delete_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}"}); - routesMap.put("es/explain", new String[]{"/{index}/_explain/{id}"}); - routesMap.put("es/cat.thread_pool", new String[]{"/_cat/thread_pool", "/_cat/thread_pool/{thread_pool_patterns}"}); - routesMap.put("es/ml.delete_calendar", new String[]{"/_ml/calendars/{calendar_id}"}); - routesMap.put("es/indices.create_data_stream", new String[]{"/_data_stream/{name}"}); - routesMap.put("es/cat.fielddata", new String[]{"/_cat/fielddata", "/_cat/fielddata/{fields}"}); - routesMap.put("es/security.enroll_node", new String[]{"/_security/enroll/node"}); - routesMap.put("es/slm.get_status", new String[]{"/_slm/status"}); - routesMap.put("es/ml.put_calendar", new String[]{"/_ml/calendars/{calendar_id}"}); - routesMap.put("es/create", new String[]{"/{index}/_create/{id}"}); - routesMap.put("es/ml.preview_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_preview", "/_ml/datafeeds/_preview"}); - routesMap.put("es/indices.put_template", new String[]{"/_template/{name}"}); - routesMap.put("es/nodes.reload_secure_settings", new String[]{"/_nodes/reload_secure_settings", "/_nodes/{node_id}/reload_secure_settings"}); - routesMap.put("es/indices.delete_data_stream", new String[]{"/_data_stream/{name}"}); - routesMap.put("es/transform.schedule_now_transform", new String[]{"/_transform/{transform_id}/_schedule_now"}); - routesMap.put("es/slm.stop", new String[]{"/_slm/stop"}); - routesMap.put("es/rollup.delete_job", new String[]{"/_rollup/job/{id}"}); - routesMap.put("es/cluster.put_component_template", new String[]{"/_component_template/{name}"}); - routesMap.put("es/delete_script", new String[]{"/_scripts/{id}"}); - routesMap.put("es/ml.delete_trained_model", new String[]{"/_ml/trained_models/{model_id}"}); - routesMap.put("es/indices.simulate_template", new String[]{"/_index_template/_simulate", "/_index_template/_simulate/{name}"}); - routesMap.put("es/slm.get_lifecycle", new String[]{"/_slm/policy/{policy_id}", "/_slm/policy"}); - routesMap.put("es/security.enroll_kibana", new String[]{"/_security/enroll/kibana"}); - routesMap.put("es/fleet.search", new String[]{"/{index}/_fleet/_fleet_search"}); - routesMap.put("es/reindex_rethrottle", new String[]{"/_reindex/{task_id}/_rethrottle"}); - routesMap.put("es/ml.update_filter", new String[]{"/_ml/filters/{filter_id}/_update"}); - routesMap.put("es/rollup.get_rollup_caps", new String[]{"/_rollup/data/{id}", "/_rollup/data"}); - routesMap.put("es/ccr.resume_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}/resume"}); - routesMap.put("es/features.get_features", new String[]{"/_features"}); - routesMap.put("es/slm.get_stats", new String[]{"/_slm/stats"}); - routesMap.put("es/indices.clear_cache", new String[]{"/_cache/clear", "/{index}/_cache/clear"}); - routesMap.put("es/cluster.post_voting_config_exclusions", new String[]{"/_cluster/voting_config_exclusions"}); - routesMap.put("es/index", new String[]{"/{index}/_doc/{id}", "/{index}/_doc"}); - routesMap.put("es/cat.pending_tasks", new String[]{"/_cat/pending_tasks"}); - routesMap.put("es/indices.promote_data_stream", new String[]{"/_data_stream/_promote/{name}"}); - routesMap.put("es/ml.delete_filter", new String[]{"/_ml/filters/{filter_id}"}); - routesMap.put("es/sql.query", new String[]{"/_sql"}); - routesMap.put("es/ccr.follow_stats", new String[]{"/{index}/_ccr/stats"}); - routesMap.put("es/transform.stop_transform", new String[]{"/_transform/{transform_id}/_stop"}); - routesMap.put("es/security.has_privileges_user_profile", new String[]{"/_security/profile/_has_privileges"}); - routesMap.put("es/autoscaling.delete_autoscaling_policy", new String[]{"/_autoscaling/policy/{name}"}); - routesMap.put("es/scripts_painless_execute", new String[]{"/_scripts/painless/_execute"}); - routesMap.put("es/indices.delete", new String[]{"/{index}"}); - routesMap.put("es/security.clear_cached_roles", new String[]{"/_security/role/{name}/_clear_cache"}); - routesMap.put("es/eql.delete", new String[]{"/_eql/search/{id}"}); - routesMap.put("es/update", new String[]{"/{index}/_update/{id}"}); - routesMap.put("es/snapshot.clone", new String[]{"/_snapshot/{repository}/{snapshot}/_clone/{target_snapshot}"}); - routesMap.put("es/license.get_basic_status", new String[]{"/_license/basic_status"}); - routesMap.put("es/indices.close", new String[]{"/{index}/_close"}); - routesMap.put("es/security.saml_authenticate", new String[]{"/_security/saml/authenticate"}); - routesMap.put("es/search_application.put", new String[]{"/_application/search_application/{name}"}); - routesMap.put("es/count", new String[]{"/_count", "/{index}/_count"}); - routesMap.put("es/migration.deprecations", new String[]{"/_migration/deprecations", "/{index}/_migration/deprecations"}); - routesMap.put("es/indices.segments", new String[]{"/_segments", "/{index}/_segments"}); - routesMap.put("es/security.suggest_user_profiles", new String[]{"/_security/profile/_suggest"}); - routesMap.put("es/security.get_user_privileges", new String[]{"/_security/user/_privileges"}); - routesMap.put("es/indices.delete_alias", new String[]{"/{index}/_alias/{name}", "/{index}/_aliases/{name}"}); - routesMap.put("es/indices.get_mapping", new String[]{"/_mapping", "/{index}/_mapping"}); - routesMap.put("es/indices.put_index_template", new String[]{"/_index_template/{name}"}); - routesMap.put("es/searchable_snapshots.stats", new String[]{"/_searchable_snapshots/stats", "/{index}/_searchable_snapshots/stats"}); - routesMap.put("es/security.disable_user", new String[]{"/_security/user/{username}/_disable"}); - routesMap.put("es/ml.upgrade_job_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade"}); - routesMap.put("es/delete", new String[]{"/{index}/_doc/{id}"}); - routesMap.put("es/async_search.delete", new String[]{"/_async_search/{id}"}); - routesMap.put("es/cat.transforms", new String[]{"/_cat/transforms", "/_cat/transforms/{transform_id}"}); - routesMap.put("es/ping", new String[]{"/"}); - routesMap.put("es/ccr.pause_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}/pause"}); - routesMap.put("es/indices.shard_stores", new String[]{"/_shard_stores", "/{index}/_shard_stores"}); - routesMap.put("es/ml.update_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}/_update"}); - routesMap.put("es/logstash.delete_pipeline", new String[]{"/_logstash/pipeline/{id}"}); - routesMap.put("es/sql.translate", new String[]{"/_sql/translate"}); - routesMap.put("es/exists", new String[]{"/{index}/_doc/{id}"}); - routesMap.put("es/snapshot.get_repository", new String[]{"/_snapshot", "/_snapshot/{repository}"}); - routesMap.put("es/snapshot.verify_repository", new String[]{"/_snapshot/{repository}/_verify"}); - routesMap.put("es/indices.put_data_lifecycle", new String[]{"/_data_stream/{name}/_lifecycle"}); - routesMap.put("es/ml.open_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_open"}); - routesMap.put("es/security.update_user_profile_data", new String[]{"/_security/profile/{uid}/_data"}); - routesMap.put("es/enrich.put_policy", new String[]{"/_enrich/policy/{name}"}); - routesMap.put("es/ml.get_datafeed_stats", new String[]{"/_ml/datafeeds/{datafeed_id}/_stats", "/_ml/datafeeds/_stats"}); - routesMap.put("es/open_point_in_time", new String[]{"/{index}/_pit"}); - routesMap.put("es/get_source", new String[]{"/{index}/_source/{id}"}); - routesMap.put("es/delete_by_query", new String[]{"/{index}/_delete_by_query"}); - routesMap.put("es/security.create_api_key", new String[]{"/_security/api_key"}); - routesMap.put("es/cat.tasks", new String[]{"/_cat/tasks"}); - routesMap.put("es/watcher.delete_watch", new String[]{"/_watcher/watch/{id}"}); - routesMap.put("es/ingest.processor_grok", new String[]{"/_ingest/processor/grok"}); - routesMap.put("es/ingest.put_pipeline", new String[]{"/_ingest/pipeline/{id}"}); - routesMap.put("es/ml.get_data_frame_analytics_stats", new String[]{"/_ml/data_frame/analytics/_stats", "/_ml/data_frame/analytics/{id}/_stats"}); - routesMap.put("es/indices.data_streams_stats", new String[]{"/_data_stream/_stats", "/_data_stream/{name}/_stats"}); - routesMap.put("es/security.clear_cached_realms", new String[]{"/_security/realm/{realms}/_clear_cache"}); - routesMap.put("es/field_caps", new String[]{"/_field_caps", "/{index}/_field_caps"}); - routesMap.put("es/ml.evaluate_data_frame", new String[]{"/_ml/data_frame/_evaluate"}); - routesMap.put("es/ml.delete_forecast", new String[]{"/_ml/anomaly_detectors/{job_id}/_forecast", "/_ml/anomaly_detectors/{job_id}/_forecast/{forecast_id}"}); - routesMap.put("es/enrich.get_policy", new String[]{"/_enrich/policy/{name}", "/_enrich/policy"}); - routesMap.put("es/rollup.start_job", new String[]{"/_rollup/job/{id}/_start"}); - routesMap.put("es/tasks.cancel", new String[]{"/_tasks/_cancel", "/_tasks/{task_id}/_cancel"}); - routesMap.put("es/security.saml_logout", new String[]{"/_security/saml/logout"}); - routesMap.put("es/render_search_template", new String[]{"/_render/template", "/_render/template/{id}"}); - routesMap.put("es/ml.get_calendar_events", new String[]{"/_ml/calendars/{calendar_id}/events"}); - routesMap.put("es/security.enable_user_profile", new String[]{"/_security/profile/{uid}/_enable"}); - routesMap.put("es/logstash.get_pipeline", new String[]{"/_logstash/pipeline", "/_logstash/pipeline/{id}"}); - routesMap.put("es/cat.snapshots", new String[]{"/_cat/snapshots", "/_cat/snapshots/{repository}"}); - routesMap.put("es/indices.add_block", new String[]{"/{index}/_block/{block}"}); - routesMap.put("es/terms_enum", new String[]{"/{index}/_terms_enum"}); - routesMap.put("es/ml.forecast", new String[]{"/_ml/anomaly_detectors/{job_id}/_forecast"}); - routesMap.put("es/cluster.stats", new String[]{"/_cluster/stats", "/_cluster/stats/nodes/{node_id}"}); - routesMap.put("es/search_application.list", new String[]{"/_application/search_application"}); - routesMap.put("es/cat.count", new String[]{"/_cat/count", "/_cat/count/{index}"}); - routesMap.put("es/cat.segments", new String[]{"/_cat/segments", "/_cat/segments/{index}"}); - routesMap.put("es/ccr.resume_follow", new String[]{"/{index}/_ccr/resume_follow"}); - routesMap.put("es/search_application.get", new String[]{"/_application/search_application/{name}"}); - routesMap.put("es/security.saml_service_provider_metadata", new String[]{"/_security/saml/metadata/{realm_name}"}); - routesMap.put("es/update_by_query", new String[]{"/{index}/_update_by_query"}); - routesMap.put("es/ml.stop_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_stop"}); - routesMap.put("es/ilm.explain_lifecycle", new String[]{"/{index}/_ilm/explain"}); - routesMap.put("es/ml.put_trained_model_vocabulary", new String[]{"/_ml/trained_models/{model_id}/vocabulary"}); - routesMap.put("es/indices.exists", new String[]{"/{index}"}); - routesMap.put("es/ml.set_upgrade_mode", new String[]{"/_ml/set_upgrade_mode"}); - routesMap.put("es/security.saml_invalidate", new String[]{"/_security/saml/invalidate"}); - routesMap.put("es/ml.get_job_stats", new String[]{"/_ml/anomaly_detectors/_stats", "/_ml/anomaly_detectors/{job_id}/_stats"}); - routesMap.put("es/cluster.allocation_explain", new String[]{"/_cluster/allocation/explain"}); - routesMap.put("es/watcher.activate_watch", new String[]{"/_watcher/watch/{watch_id}/_activate"}); - routesMap.put("es/searchable_snapshots.clear_cache", new String[]{"/_searchable_snapshots/cache/clear", "/{index}/_searchable_snapshots/cache/clear"}); - routesMap.put("es/msearch_template", new String[]{"/_msearch/template", "/{index}/_msearch/template"}); - routesMap.put("es/bulk", new String[]{"/_bulk", "/{index}/_bulk"}); - routesMap.put("es/cat.nodeattrs", new String[]{"/_cat/nodeattrs"}); - routesMap.put("es/indices.get_index_template", new String[]{"/_index_template", "/_index_template/{name}"}); - routesMap.put("es/license.get", new String[]{"/_license"}); - routesMap.put("es/ccr.forget_follower", new String[]{"/{index}/_ccr/forget_follower"}); - routesMap.put("es/security.delete_role", new String[]{"/_security/role/{name}"}); - routesMap.put("es/indices.validate_query", new String[]{"/_validate/query", "/{index}/_validate/query"}); - routesMap.put("es/tasks.get", new String[]{"/_tasks/{task_id}"}); - routesMap.put("es/ml.start_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}/_start"}); - routesMap.put("es/indices.create", new String[]{"/{index}"}); - routesMap.put("es/cluster.delete_voting_config_exclusions", new String[]{"/_cluster/voting_config_exclusions"}); - routesMap.put("es/info", new String[]{"/"}); - routesMap.put("es/watcher.stop", new String[]{"/_watcher/_stop"}); - routesMap.put("es/enrich.delete_policy", new String[]{"/_enrich/policy/{name}"}); - routesMap.put("es/cat.ml_data_frame_analytics", new String[]{"/_cat/ml/data_frame/analytics", "/_cat/ml/data_frame/analytics/{id}"}); - routesMap.put("es/security.change_password", new String[]{"/_security/user/{username}/_password", "/_security/user/_password"}); - routesMap.put("es/put_script", new String[]{"/_scripts/{id}", "/_scripts/{id}/{context}"}); - routesMap.put("es/ml.put_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}"}); - routesMap.put("es/cat.master", new String[]{"/_cat/master"}); - routesMap.put("es/features.reset_features", new String[]{"/_features/_reset"}); - routesMap.put("es/indices.get_data_lifecycle", new String[]{"/_data_stream/{name}/_lifecycle"}); - routesMap.put("es/ml.get_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}", "/_ml/data_frame/analytics"}); - routesMap.put("es/security.delete_service_token", new String[]{"/_security/service/{namespace}/{service}/credential/token/{name}"}); - routesMap.put("es/indices.recovery", new String[]{"/_recovery", "/{index}/_recovery"}); - routesMap.put("es/cat.recovery", new String[]{"/_cat/recovery", "/_cat/recovery/{index}"}); - routesMap.put("es/indices.downsample", new String[]{"/{index}/_downsample/{target_index}"}); - routesMap.put("es/ingest.delete_pipeline", new String[]{"/_ingest/pipeline/{id}"}); - routesMap.put("es/async_search.get", new String[]{"/_async_search/{id}"}); - routesMap.put("es/eql.get", new String[]{"/_eql/search/{id}"}); - routesMap.put("es/cat.aliases", new String[]{"/_cat/aliases", "/_cat/aliases/{name}"}); - routesMap.put("es/security.get_service_credentials", new String[]{"/_security/service/{namespace}/{service}/credential"}); - routesMap.put("es/cat.allocation", new String[]{"/_cat/allocation", "/_cat/allocation/{node_id}"}); - routesMap.put("es/ml.stop_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}/_stop"}); - routesMap.put("es/indices.open", new String[]{"/{index}/_open"}); - routesMap.put("es/ilm.get_lifecycle", new String[]{"/_ilm/policy/{policy}", "/_ilm/policy"}); - routesMap.put("es/ilm.remove_policy", new String[]{"/{index}/_ilm/remove"}); - routesMap.put("es/security.get_role_mapping", new String[]{"/_security/role_mapping/{name}", "/_security/role_mapping"}); - routesMap.put("es/snapshot.create", new String[]{"/_snapshot/{repository}/{snapshot}"}); - routesMap.put("es/watcher.get_watch", new String[]{"/_watcher/watch/{id}"}); - routesMap.put("es/license.post_start_trial", new String[]{"/_license/start_trial"}); - routesMap.put("es/snapshot.restore", new String[]{"/_snapshot/{repository}/{snapshot}/_restore"}); - routesMap.put("es/indices.put_mapping", new String[]{"/{index}/_mapping"}); - routesMap.put("es/ml.delete_calendar_job", new String[]{"/_ml/calendars/{calendar_id}/jobs/{job_id}"}); - routesMap.put("es/security.clear_api_key_cache", new String[]{"/_security/api_key/{ids}/_clear_cache"}); - routesMap.put("es/slm.start", new String[]{"/_slm/start"}); - routesMap.put("es/cat.component_templates", new String[]{"/_cat/component_templates", "/_cat/component_templates/{name}"}); - routesMap.put("es/security.enable_user", new String[]{"/_security/user/{username}/_enable"}); - routesMap.put("es/cluster.delete_component_template", new String[]{"/_component_template/{name}"}); - routesMap.put("es/security.get_role", new String[]{"/_security/role/{name}", "/_security/role"}); - routesMap.put("es/ingest.get_pipeline", new String[]{"/_ingest/pipeline", "/_ingest/pipeline/{id}"}); - routesMap.put("es/ml.delete_expired_data", new String[]{"/_ml/_delete_expired_data/{job_id}", "/_ml/_delete_expired_data"}); - routesMap.put("es/indices.get_settings", new String[]{"/_settings", "/{index}/_settings", "/{index}/_settings/{name}", "/_settings/{name}"}); - routesMap.put("es/ccr.follow", new String[]{"/{index}/_ccr/follow"}); - routesMap.put("es/termvectors", new String[]{"/{index}/_termvectors/{id}", "/{index}/_termvectors"}); - routesMap.put("es/ml.post_data", new String[]{"/_ml/anomaly_detectors/{job_id}/_data"}); - routesMap.put("es/eql.search", new String[]{"/{index}/_eql/search"}); - routesMap.put("es/ml.get_trained_models", new String[]{"/_ml/trained_models/{model_id}", "/_ml/trained_models"}); - routesMap.put("es/security.disable_user_profile", new String[]{"/_security/profile/{uid}/_disable"}); - routesMap.put("es/security.put_privileges", new String[]{"/_security/privilege"}); - routesMap.put("es/cat.nodes", new String[]{"/_cat/nodes"}); - routesMap.put("es/nodes.info", new String[]{"/_nodes", "/_nodes/{node_id}", "/_nodes/{metric}", "/_nodes/{node_id}/{metric}"}); - routesMap.put("es/graph.explore", new String[]{"/{index}/_graph/explore"}); - routesMap.put("es/autoscaling.put_autoscaling_policy", new String[]{"/_autoscaling/policy/{name}"}); - routesMap.put("es/cat.templates", new String[]{"/_cat/templates", "/_cat/templates/{name}"}); - routesMap.put("es/cluster.remote_info", new String[]{"/_remote/info"}); - routesMap.put("es/rank_eval", new String[]{"/_rank_eval", "/{index}/_rank_eval"}); - routesMap.put("es/security.delete_privileges", new String[]{"/_security/privilege/{application}/{name}"}); - routesMap.put("es/security.get_privileges", new String[]{"/_security/privilege", "/_security/privilege/{application}", "/_security/privilege/{application}/{name}"}); - routesMap.put("es/scroll", new String[]{"/_search/scroll"}); - routesMap.put("es/license.delete", new String[]{"/_license"}); - routesMap.put("es/indices.disk_usage", new String[]{"/{index}/_disk_usage"}); - routesMap.put("es/msearch", new String[]{"/_msearch", "/{index}/_msearch"}); - routesMap.put("es/indices.field_usage_stats", new String[]{"/{index}/_field_usage_stats"}); - routesMap.put("es/indices.rollover", new String[]{"/{alias}/_rollover", "/{alias}/_rollover/{new_index}"}); - routesMap.put("es/cat.ml_trained_models", new String[]{"/_cat/ml/trained_models", "/_cat/ml/trained_models/{model_id}"}); - routesMap.put("es/ml.delete_trained_model_alias", new String[]{"/_ml/trained_models/{model_id}/model_aliases/{model_alias}"}); - routesMap.put("es/indices.get", new String[]{"/{index}"}); - routesMap.put("es/sql.get_async_status", new String[]{"/_sql/async/status/{id}"}); - routesMap.put("es/ilm.stop", new String[]{"/_ilm/stop"}); - routesMap.put("es/security.put_user", new String[]{"/_security/user/{username}"}); - routesMap.put("es/cluster.state", new String[]{"/_cluster/state", "/_cluster/state/{metric}", "/_cluster/state/{metric}/{index}"}); - routesMap.put("es/indices.put_settings", new String[]{"/_settings", "/{index}/_settings"}); - routesMap.put("es/knn_search", new String[]{"/{index}/_knn_search"}); - routesMap.put("es/get", new String[]{"/{index}/_doc/{id}"}); - routesMap.put("es/eql.get_status", new String[]{"/_eql/search/status/{id}"}); - routesMap.put("es/ssl.certificates", new String[]{"/_ssl/certificates"}); - routesMap.put("es/ml.get_model_snapshots", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}", "/_ml/anomaly_detectors/{job_id}/model_snapshots"}); - routesMap.put("es/nodes.clear_repositories_metering_archive", new String[]{"/_nodes/{node_id}/_repositories_metering/{max_archive_version}"}); - routesMap.put("es/security.put_role", new String[]{"/_security/role/{name}"}); - routesMap.put("es/ml.get_influencers", new String[]{"/_ml/anomaly_detectors/{job_id}/results/influencers"}); - routesMap.put("es/transform.upgrade_transforms", new String[]{"/_transform/_upgrade"}); - routesMap.put("es/ml.delete_calendar_event", new String[]{"/_ml/calendars/{calendar_id}/events/{event_id}"}); - routesMap.put("es/indices.get_field_mapping", new String[]{"/_mapping/field/{fields}", "/{index}/_mapping/field/{fields}"}); - routesMap.put("es/transform.preview_transform", new String[]{"/_transform/{transform_id}/_preview", "/_transform/_preview"}); - routesMap.put("es/tasks.list", new String[]{"/_tasks"}); - routesMap.put("es/ml.clear_trained_model_deployment_cache", new String[]{"/_ml/trained_models/{model_id}/deployment/cache/_clear"}); - routesMap.put("es/cluster.reroute", new String[]{"/_cluster/reroute"}); - routesMap.put("es/security.saml_complete_logout", new String[]{"/_security/saml/complete_logout"}); - routesMap.put("es/indices.simulate_index_template", new String[]{"/_index_template/_simulate_index/{name}"}); - routesMap.put("es/snapshot.get", new String[]{"/_snapshot/{repository}/{snapshot}"}); - routesMap.put("es/ccr.put_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}"}); - routesMap.put("es/nodes.hot_threads", new String[]{"/_nodes/hot_threads", "/_nodes/{node_id}/hot_threads"}); - routesMap.put("es/ml.preview_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/_preview", "/_ml/data_frame/analytics/{id}/_preview"}); - routesMap.put("es/indices.flush", new String[]{"/_flush", "/{index}/_flush"}); - routesMap.put("es/cluster.exists_component_template", new String[]{"/_component_template/{name}"}); - routesMap.put("es/snapshot.status", new String[]{"/_snapshot/_status", "/_snapshot/{repository}/_status", "/_snapshot/{repository}/{snapshot}/_status"}); - routesMap.put("es/ml.update_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_update"}); - routesMap.put("es/indices.update_aliases", new String[]{"/_aliases"}); - routesMap.put("es/autoscaling.get_autoscaling_capacity", new String[]{"/_autoscaling/capacity"}); - routesMap.put("es/migration.post_feature_upgrade", new String[]{"/_migration/system_features"}); - routesMap.put("es/ml.get_records", new String[]{"/_ml/anomaly_detectors/{job_id}/results/records"}); - routesMap.put("es/indices.get_alias", new String[]{"/_alias", "/_alias/{name}", "/{index}/_alias/{name}", "/{index}/_alias"}); - routesMap.put("es/logstash.put_pipeline", new String[]{"/_logstash/pipeline/{id}"}); - routesMap.put("es/snapshot.delete_repository", new String[]{"/_snapshot/{repository}"}); - routesMap.put("es/security.has_privileges", new String[]{"/_security/user/_has_privileges", "/_security/user/{user}/_has_privileges"}); - routesMap.put("es/cat.indices", new String[]{"/_cat/indices", "/_cat/indices/{index}"}); - routesMap.put("es/ccr.get_auto_follow_pattern", new String[]{"/_ccr/auto_follow", "/_ccr/auto_follow/{name}"}); - routesMap.put("es/ml.start_datafeed", new String[]{"/_ml/datafeeds/{datafeed_id}/_start"}); - routesMap.put("es/indices.clone", new String[]{"/{index}/_clone/{target}"}); - routesMap.put("es/search_application.delete", new String[]{"/_application/search_application/{name}"}); - routesMap.put("es/security.query_api_keys", new String[]{"/_security/_query/api_key"}); - routesMap.put("es/ml.flush_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_flush"}); - routesMap.put("es/security.clear_cached_privileges", new String[]{"/_security/privilege/{application}/_clear_cache"}); - routesMap.put("es/indices.exists_index_template", new String[]{"/_index_template/{name}"}); - routesMap.put("es/indices.explain_data_lifecycle", new String[]{"/{index}/_lifecycle/explain"}); - routesMap.put("es/indices.put_alias", new String[]{"/{index}/_alias/{name}", "/{index}/_aliases/{name}"}); - routesMap.put("es/ml.get_buckets", new String[]{"/_ml/anomaly_detectors/{job_id}/results/buckets/{timestamp}", "/_ml/anomaly_detectors/{job_id}/results/buckets"}); - routesMap.put("es/ml.put_trained_model_definition_part", new String[]{"/_ml/trained_models/{model_id}/definition/{part}"}); - routesMap.put("es/get_script", new String[]{"/_scripts/{id}"}); - routesMap.put("es/ingest.simulate", new String[]{"/_ingest/pipeline/_simulate", "/_ingest/pipeline/{id}/_simulate"}); - routesMap.put("es/indices.migrate_to_data_stream", new String[]{"/_data_stream/_migrate/{name}"}); - routesMap.put("es/enrich.execute_policy", new String[]{"/_enrich/policy/{name}/_execute"}); - routesMap.put("es/indices.split", new String[]{"/{index}/_split/{target}"}); - routesMap.put("es/ml.delete_model_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}"}); - routesMap.put("es/nodes.usage", new String[]{"/_nodes/usage", "/_nodes/{node_id}/usage", "/_nodes/usage/{metric}", "/_nodes/{node_id}/usage/{metric}"}); - routesMap.put("es/cat.help", new String[]{"/_cat"}); - routesMap.put("es/ml.estimate_model_memory", new String[]{"/_ml/anomaly_detectors/_estimate_model_memory"}); - routesMap.put("es/exists_source", new String[]{"/{index}/_source/{id}"}); - routesMap.put("es/ml.put_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}"}); - routesMap.put("es/security.put_role_mapping", new String[]{"/_security/role_mapping/{name}"}); - routesMap.put("es/rollup.get_rollup_index_caps", new String[]{"/{index}/_rollup/data"}); - routesMap.put("es/transform.reset_transform", new String[]{"/_transform/{transform_id}/_reset"}); - routesMap.put("es/ml.infer_trained_model", new String[]{"/_ml/trained_models/{model_id}/_infer", "/_ml/trained_models/{model_id}/deployment/_infer"}); - routesMap.put("es/reindex", new String[]{"/_reindex"}); - routesMap.put("es/ml.put_trained_model", new String[]{"/_ml/trained_models/{model_id}"}); - routesMap.put("es/cat.ml_jobs", new String[]{"/_cat/ml/anomaly_detectors", "/_cat/ml/anomaly_detectors/{job_id}"}); - routesMap.put("es/search_application.search", new String[]{"/_application/search_application/{name}/_search"}); - routesMap.put("es/ilm.put_lifecycle", new String[]{"/_ilm/policy/{policy}"}); - routesMap.put("es/security.get_token", new String[]{"/_security/oauth2/token"}); - routesMap.put("es/ilm.move_to_step", new String[]{"/_ilm/move/{index}"}); - routesMap.put("es/search_template", new String[]{"/_search/template", "/{index}/_search/template"}); - routesMap.put("es/indices.delete_data_lifecycle", new String[]{"/_data_stream/{name}/_lifecycle"}); - routesMap.put("es/indices.get_data_stream", new String[]{"/_data_stream", "/_data_stream/{name}"}); - routesMap.put("es/ml.get_filters", new String[]{"/_ml/filters", "/_ml/filters/{filter_id}"}); - routesMap.put("es/cat.ml_datafeeds", new String[]{"/_cat/ml/datafeeds", "/_cat/ml/datafeeds/{datafeed_id}"}); - routesMap.put("es/rollup.rollup_search", new String[]{"/{index}/_rollup_search"}); - routesMap.put("es/ml.put_job", new String[]{"/_ml/anomaly_detectors/{job_id}"}); - routesMap.put("es/update_by_query_rethrottle", new String[]{"/_update_by_query/{task_id}/_rethrottle"}); - routesMap.put("es/indices.delete_index_template", new String[]{"/_index_template/{name}"}); - routesMap.put("es/indices.reload_search_analyzers", new String[]{"/{index}/_reload_search_analyzers"}); - routesMap.put("es/cluster.get_settings", new String[]{"/_cluster/settings"}); - routesMap.put("es/cluster.put_settings", new String[]{"/_cluster/settings"}); - routesMap.put("es/transform.put_transform", new String[]{"/_transform/{transform_id}"}); - routesMap.put("es/watcher.stats", new String[]{"/_watcher/stats", "/_watcher/stats/{metric}"}); - routesMap.put("es/ccr.delete_auto_follow_pattern", new String[]{"/_ccr/auto_follow/{name}"}); - routesMap.put("es/mtermvectors", new String[]{"/_mtermvectors", "/{index}/_mtermvectors"}); - routesMap.put("es/license.post", new String[]{"/_license"}); - routesMap.put("es/xpack.info", new String[]{"/_xpack"}); - routesMap.put("es/dangling_indices.import_dangling_index", new String[]{"/_dangling/{index_uuid}"}); - routesMap.put("es/nodes.get_repositories_metering_info", new String[]{"/_nodes/{node_id}/_repositories_metering"}); - routesMap.put("es/transform.get_transform_stats", new String[]{"/_transform/{transform_id}/_stats"}); - routesMap.put("es/mget", new String[]{"/_mget", "/{index}/_mget"}); - routesMap.put("es/security.get_builtin_privileges", new String[]{"/_security/privilege/_builtin"}); - routesMap.put("es/ml.update_model_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_update"}); - routesMap.put("es/ml.info", new String[]{"/_ml/info"}); - routesMap.put("es/indices.exists_template", new String[]{"/_template/{name}"}); - routesMap.put("es/watcher.ack_watch", new String[]{"/_watcher/watch/{watch_id}/_ack", "/_watcher/watch/{watch_id}/_ack/{action_id}"}); - routesMap.put("es/security.get_user", new String[]{"/_security/user/{username}", "/_security/user"}); - routesMap.put("es/shutdown.get_node", new String[]{"/_nodes/shutdown", "/_nodes/{node_id}/shutdown"}); - routesMap.put("es/watcher.start", new String[]{"/_watcher/_start"}); - routesMap.put("es/indices.shrink", new String[]{"/{index}/_shrink/{target}"}); - routesMap.put("es/license.post_start_basic", new String[]{"/_license/start_basic"}); - routesMap.put("es/xpack.usage", new String[]{"/_xpack/usage"}); - routesMap.put("es/ilm.delete_lifecycle", new String[]{"/_ilm/policy/{policy}"}); - routesMap.put("es/ccr.follow_info", new String[]{"/{index}/_ccr/info"}); - routesMap.put("es/ml.put_calendar_job", new String[]{"/_ml/calendars/{calendar_id}/jobs/{job_id}"}); - routesMap.put("es/rollup.put_job", new String[]{"/_rollup/job/{id}"}); - routesMap.put("es/clear_scroll", new String[]{"/_search/scroll"}); - routesMap.put("es/ml.delete_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/{id}"}); - routesMap.put("es/security.get_api_key", new String[]{"/_security/api_key"}); - routesMap.put("es/cat.health", new String[]{"/_cat/health"}); - routesMap.put("es/security.invalidate_token", new String[]{"/_security/oauth2/token"}); - routesMap.put("es/slm.delete_lifecycle", new String[]{"/_slm/policy/{policy_id}"}); - routesMap.put("es/ml.stop_trained_model_deployment", new String[]{"/_ml/trained_models/{model_id}/deployment/_stop"}); - routesMap.put("es/monitoring.bulk", new String[]{"/_monitoring/bulk", "/_monitoring/{type}/bulk"}); - routesMap.put("es/indices.stats", new String[]{"/_stats", "/_stats/{metric}", "/{index}/_stats", "/{index}/_stats/{metric}"}); - routesMap.put("es/searchable_snapshots.cache_stats", new String[]{"/_searchable_snapshots/cache/stats", "/_searchable_snapshots/{node_id}/cache/stats"}); - routesMap.put("es/async_search.submit", new String[]{"/_async_search", "/{index}/_async_search"}); - routesMap.put("es/rollup.get_jobs", new String[]{"/_rollup/job/{id}", "/_rollup/job"}); - routesMap.put("es/ml.revert_model_snapshot", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_revert"}); - routesMap.put("es/transform.delete_transform", new String[]{"/_transform/{transform_id}"}); - routesMap.put("es/cluster.pending_tasks", new String[]{"/_cluster/pending_tasks"}); - routesMap.put("es/ml.get_model_snapshot_upgrade_stats", new String[]{"/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade/_stats"}); - routesMap.put("es/ml.get_categories", new String[]{"/_ml/anomaly_detectors/{job_id}/results/categories/{category_id}", "/_ml/anomaly_detectors/{job_id}/results/categories"}); - routesMap.put("es/ccr.pause_follow", new String[]{"/{index}/_ccr/pause_follow"}); - routesMap.put("es/security.authenticate", new String[]{"/_security/_authenticate"}); - routesMap.put("es/enrich.stats", new String[]{"/_enrich/_stats"}); - routesMap.put("es/ml.put_trained_model_alias", new String[]{"/_ml/trained_models/{model_id}/model_aliases/{model_alias}"}); - routesMap.put("es/ml.get_overall_buckets", new String[]{"/_ml/anomaly_detectors/{job_id}/results/overall_buckets"}); - routesMap.put("es/indices.get_template", new String[]{"/_template", "/_template/{name}"}); - routesMap.put("es/security.delete_role_mapping", new String[]{"/_security/role_mapping/{name}"}); - routesMap.put("es/ml.get_datafeeds", new String[]{"/_ml/datafeeds/{datafeed_id}", "/_ml/datafeeds"}); - routesMap.put("es/slm.execute_lifecycle", new String[]{"/_slm/policy/{policy_id}/_execute"}); - routesMap.put("es/close_point_in_time", new String[]{"/_pit"}); - routesMap.put("es/snapshot.cleanup_repository", new String[]{"/_snapshot/{repository}/_cleanup"}); - routesMap.put("es/autoscaling.get_autoscaling_policy", new String[]{"/_autoscaling/policy/{name}"}); - routesMap.put("es/slm.put_lifecycle", new String[]{"/_slm/policy/{policy_id}"}); - routesMap.put("es/ml.get_jobs", new String[]{"/_ml/anomaly_detectors/{job_id}", "/_ml/anomaly_detectors"}); - routesMap.put("es/ml.get_trained_models_stats", new String[]{"/_ml/trained_models/{model_id}/_stats", "/_ml/trained_models/_stats"}); - routesMap.put("es/ml.validate_detector", new String[]{"/_ml/anomaly_detectors/_validate/detector"}); - routesMap.put("es/watcher.put_watch", new String[]{"/_watcher/watch/{id}"}); - routesMap.put("es/transform.update_transform", new String[]{"/_transform/{transform_id}/_update"}); - routesMap.put("es/ml.post_calendar_events", new String[]{"/_ml/calendars/{calendar_id}/events"}); - routesMap.put("es/migration.get_feature_upgrade_status", new String[]{"/_migration/system_features"}); - routesMap.put("es/get_script_context", new String[]{"/_script_context"}); - routesMap.put("es/ml.put_filter", new String[]{"/_ml/filters/{filter_id}"}); - routesMap.put("es/ml.update_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_update"}); - routesMap.put("es/ingest.geo_ip_stats", new String[]{"/_ingest/geoip/stats"}); - routesMap.put("es/security.delete_user", new String[]{"/_security/user/{username}"}); - routesMap.put("es/indices.unfreeze", new String[]{"/{index}/_unfreeze"}); - routesMap.put("es/snapshot.create_repository", new String[]{"/_snapshot/{repository}"}); - routesMap.put("es/cluster.get_component_template", new String[]{"/_component_template", "/_component_template/{name}"}); - routesMap.put("es/ilm.migrate_to_data_tiers", new String[]{"/_ilm/migrate_to_data_tiers"}); - routesMap.put("es/indices.refresh", new String[]{"/_refresh", "/{index}/_refresh"}); - routesMap.put("es/ml.get_calendars", new String[]{"/_ml/calendars", "/_ml/calendars/{calendar_id}"}); - routesMap.put("es/watcher.deactivate_watch", new String[]{"/_watcher/watch/{watch_id}/_deactivate"}); - routesMap.put("es/cluster.health", new String[]{"/_cluster/health", "/_cluster/health/{index}"}); - routesMap.put("es/dangling_indices.delete_dangling_index", new String[]{"/_dangling/{index_uuid}"}); - routesMap.put("es/health_report", new String[]{"/_health_report", "/_health_report/{feature}"}); - routesMap.put("es/watcher.query_watches", new String[]{"/_watcher/_query/watches"}); - routesMap.put("es/ccr.unfollow", new String[]{"/{index}/_ccr/unfollow"}); - routesMap.put("es/ml.validate", new String[]{"/_ml/anomaly_detectors/_validate"}); - routesMap.put("es/cat.plugins", new String[]{"/_cat/plugins"}); - routesMap.put("es/watcher.execute_watch", new String[]{"/_watcher/watch/{id}/_execute", "/_watcher/watch/_execute"}); - routesMap.put("es/search_shards", new String[]{"/_search_shards", "/{index}/_search_shards"}); - routesMap.put("es/cat.shards", new String[]{"/_cat/shards", "/_cat/shards/{index}"}); - routesMap.put("es/ml.delete_job", new String[]{"/_ml/anomaly_detectors/{job_id}"}); - routesMap.put("es/ilm.start", new String[]{"/_ilm/start"}); - routesMap.put("es/security.get_user_profile", new String[]{"/_security/profile/{uid}"}); - routesMap.put("es/indices.modify_data_stream", new String[]{"/_data_stream/_modify"}); - routesMap.put("es/indices.exists_alias", new String[]{"/_alias/{name}", "/{index}/_alias/{name}"}); - routesMap.put("es/rollup.stop_job", new String[]{"/_rollup/job/{id}/_stop"}); - routesMap.put("es/dangling_indices.list_dangling_indices", new String[]{"/_dangling"}); - routesMap.put("es/snapshot.delete", new String[]{"/_snapshot/{repository}/{snapshot}"}); - routesMap.put("es/security.activate_user_profile", new String[]{"/_security/profile/_activate"}); - routesMap.put("es/ml.start_trained_model_deployment", new String[]{"/_ml/trained_models/{model_id}/deployment/_start"}); - routesMap.put("es/transform.start_transform", new String[]{"/_transform/{transform_id}/_start"}); - routesMap.put("es/cat.repositories", new String[]{"/_cat/repositories"}); - routesMap.put("es/ilm.get_status", new String[]{"/_ilm/status"}); - routesMap.put("es/shutdown.delete_node", new String[]{"/_nodes/{node_id}/shutdown"}); - routesMap.put("es/nodes.stats", new String[]{"/_nodes/stats", "/_nodes/{node_id}/stats", "/_nodes/stats/{metric}", "/_nodes/{node_id}/stats/{metric}", "/_nodes/stats/{metric}/{index_metric}", "/_nodes/{node_id}/stats/{metric}/{index_metric}"}); - routesMap.put("es/get_script_languages", new String[]{"/_script_language"}); - routesMap.put("es/slm.execute_retention", new String[]{"/_slm/_execute_retention"}); - routesMap.put("es/security.get_service_accounts", new String[]{"/_security/service/{namespace}/{service}", "/_security/service/{namespace}", "/_security/service"}); - routesMap.put("es/shutdown.put_node", new String[]{"/_nodes/{node_id}/shutdown"}); - routesMap.put("es/indices.resolve_index", new String[]{"/_resolve/index/{name}"}); - routesMap.put("es/search", new String[]{"/_search", "/{index}/_search"}); - routesMap.put("es/sql.get_async", new String[]{"/_sql/async/{id}"}); - routesMap.put("es/delete_by_query_rethrottle", new String[]{"/_delete_by_query/{task_id}/_rethrottle"}); - routesMap.put("es/transform.get_transform", new String[]{"/_transform/{transform_id}", "/_transform"}); - routesMap.put("es/security.invalidate_api_key", new String[]{"/_security/api_key"}); - routesMap.put("es/security.saml_prepare_authentication", new String[]{"/_security/saml/prepare"}); - routesMap.put("es/ml.get_memory_stats", new String[]{"/_ml/memory/_stats", "/_ml/memory/{node_id}/_stats"}); - routesMap.put("es/ccr.stats", new String[]{"/_ccr/stats"}); - routesMap.put("es/indices.forcemerge", new String[]{"/_forcemerge", "/{index}/_forcemerge"}); - routesMap.put("es/indices.delete_template", new String[]{"/_template/{name}"}); - routesMap.put("es/sql.delete_async", new String[]{"/_sql/async/delete/{id}"}); - routesMap.put("es/security.update_api_key", new String[]{"/_security/api_key/{id}"}); - routesMap.put("es/security.create_service_token", new String[]{"/_security/service/{namespace}/{service}/credential/token/{name}", "/_security/service/{namespace}/{service}/credential/token"}); - routesMap.put("es/license.get_trial_status", new String[]{"/_license/trial_status"}); - routesMap.put("es/searchable_snapshots.mount", new String[]{"/_snapshot/{repository}/{snapshot}/_mount"}); - routesMap.put("es/security.grant_api_key", new String[]{"/_security/api_key/grant"}); - routesMap.put("es/ilm.retry", new String[]{"/{index}/_ilm/retry"}); - routesMap.put("es/ml.reset_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_reset"}); - routesMap.put("es/ml.close_job", new String[]{"/_ml/anomaly_detectors/{job_id}/_close"}); - routesMap.put("es/ml.explain_data_frame_analytics", new String[]{"/_ml/data_frame/analytics/_explain", "/_ml/data_frame/analytics/{id}/_explain"}); - routesMap.put("es/security.clear_cached_service_tokens", new String[]{"/_security/service/{namespace}/{service}/credential/token/{name}/_clear_cache"}); - routesMap.put("es/search_mvt", new String[]{"/{index}/_mvt/{field}/{zoom}/{x}/{y}"}); - } - - protected static EndpointResolutionHelper get() { - if (INSTANCE == null) { - INSTANCE = new EndpointResolutionHelper(); - } - return INSTANCE; - } - - protected void enrichSpanWithRouteInformation(Span span, String method, String endpointId, String urlPath) { - String[] availableRoutes = routesMap.get(endpointId); - if (availableRoutes == null || availableRoutes.length == 0) { - return; - } - - span.withOtelAttribute("db.operation", (endpointId.startsWith("es/") && endpointId.length() > 3) ? endpointId.substring(3) : endpointId); - for (String route : availableRoutes) { - if (enrichSpan(span, method, route, urlPath)) { - break; - } - } - } - - private Matcher matchUrl(String route, String urlPath) { - if (!regexPatternMap.containsKey(route)) { - StringBuilder regexStr = new StringBuilder(); - regexStr.append(route.replace("{", "(?<").replace("}", ">[^/]+)")); - regexPatternMap.put(route, Pattern.compile(regexStr.toString())); - } - - Pattern pattern = regexPatternMap.get(route); - return pattern.matcher(urlPath); - } - - private boolean enrichSpan(Span span, String method, String route, String urlPath) { - if (route.contains("{")) { - Matcher matcher = matchUrl(route, urlPath); - if (!matcher.matches()) { - return false; - } - setTarget(span, matcher, route); - setDocId(span, matcher, route); - } else if (!route.equals(urlPath)) { - return false; - } - StringBuilder name = span.getAndOverrideName(AbstractSpan.PRIORITY_HIGH_LEVEL_FRAMEWORK); - if (name != null) { - name.append("Elasticsearch: ").append(method).append(" ").append(route); - } - return true; - } - - private void setTarget(Span span, Matcher matcher, String route) { - try { - if (route.contains("{index}")) { - String target = matcher.group("index"); - span.withOtelAttribute("db.elasticsearch.target", target); - } - } catch (Exception e) { - logger.error("Error setting target", e); - } - } - - private void setDocId(Span span, Matcher matcher, String route) { - try { - if (route.startsWith("/{index}/_") && route.endsWith("/{id}")) { - String docId = matcher.group("id"); - span.withOtelAttribute("db.elasticsearch.doc_id", docId); - } - } catch (Exception e) { - logger.error("Error setting doc id", e); - } - } -} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java index 4173adad53..809e42ef15 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java @@ -36,6 +36,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import static co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentationHelper.ELASTICSEARCH; import static co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentationHelper.SPAN_ACTION; @@ -138,14 +140,17 @@ protected void validateDbContextContent(Span span, List possibleContents assertThat(db.getStatementBuffer().toString()).isIn(possibleContents); } - protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, String index, String doc_id) { - validateSpanContent(span, expectedName, statusCode, method, index); - assertThat(span.getOtelAttributes()).containsEntry("db.elasticsearch.doc_id", doc_id); - } - protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, String index) { + protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, Map expectedPathParts) { validateSpanContent(span, expectedName, statusCode, method); - assertThat(span.getOtelAttributes()).containsEntry("db.elasticsearch.target", index); + expectedPathParts.forEach((partName, value) -> { + assertThat(span.getOtelAttributes()).containsEntry("db.elasticsearch.path_parts." + partName, value); + }); + List spanPartAttributes = span.getOtelAttributes().keySet().stream() + .filter(name -> name.startsWith("db.elasticsearch.path_parts.")) + .map(name -> name.substring("db.elasticsearch.path_parts.".length())) + .collect(Collectors.toList()); + assertThat(spanPartAttributes).containsExactlyElementsOf(expectedPathParts.keySet()); } protected void validateSpanContent(Span span, String expectedName, int statusCode, String method) { @@ -184,7 +189,7 @@ protected void validateSpanContentAfterIndexCreateRequest(boolean usePathPattern List spans = reporter.getSpans(); assertThat(spans).hasSize(1); if(usePathPattern){ - validateSpanContent(spans.get(0), "Elasticsearch: PUT /{index}", 200, "PUT", SECOND_INDEX); + validateSpanContent(spans.get(0), "Elasticsearch: indices.create", 200, "PUT", Map.of("index", SECOND_INDEX)); } else { validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s", SECOND_INDEX), 200, "PUT" ); } @@ -198,7 +203,7 @@ protected void validateSpanContentAfterIndexDeleteRequest(boolean usePathPattern List spans = reporter.getSpans(); assertThat(spans).hasSize(1); if(usePathPattern){ - validateSpanContent(spans.get(0), "Elasticsearch: DELETE /{index}", 200, "DELETE", SECOND_INDEX); + validateSpanContent(spans.get(0), "Elasticsearch: indices.delete", 200, "DELETE", Map.of("index", SECOND_INDEX)); } else { validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s", SECOND_INDEX), 200, "DELETE"); } From befe1271be58a045eb8a7b0f1c5a7a4f90ddd668 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 12 Jul 2023 12:21:55 +0200 Subject: [PATCH 06/10] Fixed and refactored test validations --- ...sticsearchRestClientInstrumentationIT.java | 38 ++- ...bstractEs6_4ClientInstrumentationTest.java | 94 ++++--- ...sticsearchRestClientInstrumentationIT.java | 13 +- ...sticsearchRestClientInstrumentationIT.java | 22 +- .../v8_x/Elasticsearch8JavaIT.java} | 220 ++++++++++----- .../apm/esjavaclient/ElasticsearchJavaIT.java | 76 ----- .../ElasticsearchEndpointDefinition.java | 19 +- .../ElasticsearchEndpointMap.java | 19 +- .../AbstractEsClientInstrumentationTest.java | 259 +++++++++++------- 9 files changed, 438 insertions(+), 322 deletions(-) rename apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/{esjavaclient/AbstractElasticsearchJavaTest.java => agent/esrestclient/v8_x/Elasticsearch8JavaIT.java} (76%) delete mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java index be6f1b272a..76916bf9c6 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/agent/esrestclient/v5_6/ElasticsearchRestClientInstrumentationIT.java @@ -123,14 +123,18 @@ public void testCreateAndDeleteIndex() throws IOException, ExecutionException { // Create an Index doPerformRequest("PUT", "/" + SECOND_INDEX); - validateSpanContentAfterIndexCreateRequest(false); + validateSpan() + .method("PUT").pathName("/%s", SECOND_INDEX) + .check(); // Delete the index reporter.reset(); doPerformRequest("DELETE", "/" + SECOND_INDEX); - validateSpanContentAfterIndexDeleteRequest(false); + validateSpan() + .method("DELETE").pathName("/%s", SECOND_INDEX) + .check(); assertThat(reporter.getFirstSpan().getOutcome()).isEqualTo(Outcome.SUCCESS); } @@ -146,10 +150,10 @@ public void testDocumentScenario() throws IOException, ExecutionException, Inter ).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)); assertThat(ir.status().getStatus()).isEqualTo(201); - - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 201, "PUT"); + validateSpan() + .method("PUT").pathName("/%s/%s/%s", INDEX, DOC_TYPE, DOC_ID) + .statusCode(201) + .check(); // Search the index reporter.reset(); @@ -164,12 +168,10 @@ public void testDocumentScenario() throws IOException, ExecutionException, Inter assertThat(sr.getHits().totalHits).isEqualTo(1L); assertThat(sr.getHits().getAt(0).getSourceAsMap().get(FOO)).isEqualTo(BAR); - - spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span searchSpan = spans.get(0); - validateSpanContent(searchSpan, String.format("Elasticsearch: GET /%s/_search", INDEX), 200, "GET"); - validateDbContextContent(searchSpan, "{\"from\":0,\"size\":5,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}"); + validateSpan() + .method("GET").pathName("/%s/_search", INDEX) + .expectStatement("{\"from\":0,\"size\":5,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}") + .check(); // Now update and re-search reporter.reset(); @@ -183,7 +185,7 @@ public void testDocumentScenario() throws IOException, ExecutionException, Inter assertThat(sr.getHits().getAt(0).getSourceAsMap().get(FOO)).isEqualTo(BAZ); - spans = reporter.getSpans(); + List spans = reporter.getSpans(); assertThat(spans).hasSize(2); boolean updateSpanFound = false; for (Span span : spans) { @@ -197,7 +199,10 @@ public void testDocumentScenario() throws IOException, ExecutionException, Inter // Finally - delete the document reporter.reset(); DeleteResponse dr = doDelete(new DeleteRequest(INDEX, DOC_TYPE, DOC_ID)); - validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 200, "DELETE"); + + validateSpan() + .method("DELETE").pathName("/%s/%s/%s", INDEX, DOC_TYPE, DOC_ID) + .check(); } @Test @@ -211,7 +216,10 @@ public void testScenarioAsBulkRequest() throws IOException, ExecutionException, )) .add(new DeleteRequest(INDEX, DOC_TYPE, "2"))); - validateSpanContentAfterBulkRequest(); + validateSpan() + .method("POST") + .pathName("/_bulk") + .check(); } private Res invokeAsync(Req request, ClientMethod method) throws InterruptedException, ExecutionException { diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java index d7c426d4d1..e3e0aa498d 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/AbstractEs6_4ClientInstrumentationTest.java @@ -76,13 +76,17 @@ public void testCreateAndDeleteIndex() throws IOException, ExecutionException, I // Create an Index doCreateIndex(new CreateIndexRequest(SECOND_INDEX)); - validateSpanContentAfterIndexCreateRequest(false); + validateSpan() + .method("PUT").pathName("/%s", SECOND_INDEX) + .check(); // Delete the index reporter.reset(); doDeleteIndex(new DeleteIndexRequest(SECOND_INDEX)); - validateSpanContentAfterIndexDeleteRequest(false); + validateSpan() + .method("DELETE").pathName("/%s", SECOND_INDEX) + .check(); } @Test @@ -108,9 +112,10 @@ public void testDocumentScenario() throws Exception { // Index a document createDocument(); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 201, "PUT"); + validateSpan() + .method("PUT").pathName("/%s/%s/%s", INDEX, DOC_TYPE, DOC_ID) + .statusCode(201) + .check(); reporter.reset(); // do search request @@ -119,11 +124,11 @@ public void testDocumentScenario() throws Exception { SearchResponse response = doSearch(searchRequest); verifyTotalHits(response.getHits()); - spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span searchSpan = spans.get(0); - validateSpanContent(searchSpan, String.format("Elasticsearch: POST /%s/_search", INDEX), 200, "POST"); - validateDbContextContent(searchSpan, "{\"from\":0,\"size\":5,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}"); + + validateSpan() + .method("POST").pathName("/%s/_search", INDEX) + .expectStatement("{\"from\":0,\"size\":5,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}") + .check(); reporter.reset(); Map jsonMap = new HashMap<>(); @@ -134,7 +139,7 @@ public void testDocumentScenario() throws Exception { SearchResponse sr = doSearch(new SearchRequest(INDEX)); assertThat(sr.getHits().getAt(0).getSourceAsMap().get(FOO)).isEqualTo(BAZ); - spans = reporter.getSpans(); + List spans = reporter.getSpans(); assertThat(spans).hasSize(2); boolean updateSpanFound = false; for (Span span : spans) { @@ -149,7 +154,10 @@ public void testDocumentScenario() throws Exception { reporter.reset(); DeleteResponse dr = deleteDocument(); assertThat(dr.status().getStatus()).isEqualTo(200); - validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 200, "DELETE"); + + validateSpan() + .method("DELETE").pathName("/%s/%s/%s", INDEX, DOC_TYPE, DOC_ID) + .check(); } @Test @@ -165,11 +173,11 @@ public void testCountRequest_validateSpanContentAndDbContext() throws Exception CountResponse responses = doCount(countRequest); assertThat(responses.getCount()).isEqualTo(1); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, String.format("Elasticsearch: POST /%s/_count", INDEX), 200, "POST"); - validateDbContextContent(span, "{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}"); + + validateSpan() + .method("POST").pathName("/%s/_count", INDEX) + .expectStatement("{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}") + .check(); deleteDocument(); } @@ -188,11 +196,10 @@ public void testMultiSearchRequest_validateSpanContentAndDbContext() throws Inte MultiSearchResponse response = doMultiSearch(multiSearchRequest); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: POST /_msearch", 200, "POST"); - verifyMultiSearchSpanContent(span); + validateSpan() + .method("POST").pathName("/_msearch") + .expectStatement(getExpectedMultisearchStatement()) + .check(); deleteDocument(); } @@ -212,11 +219,11 @@ public void testRollupSearch_validateSpanContentAndDbContext() throws Interrupte SearchResponse response = doRollupSearch(rollupSearchRequest); verifyTotalHits(response.getHits()); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, String.format("Elasticsearch: POST /%s/_rollup_search", INDEX), 200, "POST"); - validateDbContextContent(span, "{\"from\":0,\"size\":5,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}"); + + validateSpan() + .method("POST").pathName("/%s/_rollup_search", INDEX) + .expectStatement("{\"from\":0,\"size\":5,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}") + .check(); deleteDocument(); } @@ -231,12 +238,12 @@ public void testSearchTemplateRequest_validateSpanContentAndDbContext() throws I SearchTemplateResponse response = doSearchTemplate(searchTemplateRequest); verifyTotalHits(response.getResponse().getHits()); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); String httpMethod = getSearchTemplateHttpMethod(); - validateSpanContent(span, String.format("Elasticsearch: %s /%s/_search/template", httpMethod, INDEX), 200, httpMethod); - validateDbContextContent(span, "{\"source\":\"{ \\\"query\\\": { \\\"term\\\" : { \\\"{{field}}\\\" : \\\"{{value}}\\\" } }, \\\"size\\\" : \\\"{{size}}\\\"}\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"},\"explain\":false,\"profile\":false}"); + + validateSpan() + .method(httpMethod).pathName("/%s/_search/template", INDEX) + .expectStatement("{\"source\":\"{ \\\"query\\\": { \\\"term\\\" : { \\\"{{field}}\\\" : \\\"{{value}}\\\" } }, \\\"size\\\" : \\\"{{size}}\\\"}\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"},\"explain\":false,\"profile\":false}") + .check(); deleteDocument(); } @@ -259,11 +266,11 @@ public void testMultisearchTemplateRequest_validateSpanContentAndDbContext() thr MultiSearchTemplateResponse.Item[] items = response.getResponses(); assertThat(items.length).isEqualTo(1); verifyTotalHits(items[0].getResponse().getResponse().getHits()); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, String.format("Elasticsearch: POST /_msearch/template", INDEX), 200, "POST"); - verifyMultiSearchTemplateSpanContent(span); + + validateSpan() + .method("POST").pathName("/_msearch/template") + .expectStatement(getMultiSearchTemplateStatement()) + .check(); deleteDocument(); } @@ -304,13 +311,9 @@ private SearchTemplateRequest prepareSearchTemplateRequest() { return searchTemplateRequest; } - protected void verifyMultiSearchTemplateSpanContent(Span span) { + protected abstract String getMultiSearchTemplateStatement(); - } - - protected void verifyMultiSearchSpanContent(Span span) { - - } + protected abstract String getExpectedMultisearchStatement(); protected void verifyTotalHits(SearchHits searchHits) { @@ -322,7 +325,10 @@ public void testScenarioAsBulkRequest() throws IOException, ExecutionException, .add(createIndexRequest("2")) .add(new DeleteRequest(INDEX, DOC_TYPE, "2"))); - validateSpanContentAfterBulkRequest(); + validateSpan() + .method("POST") + .pathName("/_bulk") + .check(); } diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchRestClientInstrumentationIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchRestClientInstrumentationIT.java index 23d72eca1b..b16ef2298f 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchRestClientInstrumentationIT.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/esrestclient/v6_4/ElasticsearchRestClientInstrumentationIT.java @@ -18,7 +18,6 @@ */ package co.elastic.apm.agent.esrestclient.v6_4; -import co.elastic.apm.agent.impl.transaction.Span; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; @@ -74,15 +73,15 @@ public static void stopElasticsearchContainerAndClient() throws IOException { } @Override - protected void verifyMultiSearchTemplateSpanContent(Span span) { - validateDbContextContent(span, "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\"}\n" + - "{\"source\":\"{ \\\"query\\\": { \\\"term\\\" : { \\\"{{field}}\\\" : \\\"{{value}}\\\" } }, \\\"size\\\" : \\\"{{size}}\\\"}\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"},\"explain\":false,\"profile\":false}\n"); + protected String getMultiSearchTemplateStatement() { + return "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\"}\n" + + "{\"source\":\"{ \\\"query\\\": { \\\"term\\\" : { \\\"{{field}}\\\" : \\\"{{value}}\\\" } }, \\\"size\\\" : \\\"{{size}}\\\"}\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"},\"explain\":false,\"profile\":false}\n"; } @Override - protected void verifyMultiSearchSpanContent(Span span) { - validateDbContextContent(span, "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\"}\n" + - "{\"query\":{\"match\":{\"foo\":{\"query\":\"bar\",\"operator\":\"OR\",\"prefix_length\":0,\"max_expansions\":50,\"fuzzy_transpositions\":true,\"lenient\":false,\"zero_terms_query\":\"NONE\",\"auto_generate_synonyms_phrase_query\":true,\"boost\":1.0}}}}\n"); + protected String getExpectedMultisearchStatement() { + return "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\"}\n" + + "{\"query\":{\"match\":{\"foo\":{\"query\":\"bar\",\"operator\":\"OR\",\"prefix_length\":0,\"max_expansions\":50,\"fuzzy_transpositions\":true,\"lenient\":false,\"zero_terms_query\":\"NONE\",\"auto_generate_synonyms_phrase_query\":true,\"boost\":1.0}}}}\n"; } @Override diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/src/test/java/co/elastic/apm/agent/esrestclient/v7_x/ElasticsearchRestClientInstrumentationIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/src/test/java/co/elastic/apm/agent/esrestclient/v7_x/ElasticsearchRestClientInstrumentationIT.java index 6cfde2f329..cceba3d000 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/src/test/java/co/elastic/apm/agent/esrestclient/v7_x/ElasticsearchRestClientInstrumentationIT.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/src/test/java/co/elastic/apm/agent/esrestclient/v7_x/ElasticsearchRestClientInstrumentationIT.java @@ -20,9 +20,9 @@ import co.elastic.apm.agent.esrestclient.v6_4.AbstractEs6_4ClientInstrumentationTest; import co.elastic.apm.agent.impl.transaction.AbstractSpan; -import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.tracer.Outcome; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; @@ -117,7 +117,6 @@ public void testCancelScenario() throws InterruptedException, ExecutionException // When spans are cancelled, we can't know the actual address, because there is no response, and we set the outcome as UNKNOWN reporter.disableCheckDestinationAddress(); reporter.disableCheckUnknownOutcome(); - disableHttpUrlCheck(); createDocument(); reporter.reset(); @@ -139,7 +138,12 @@ public void onFailure(Exception e) { cancellable.cancel(); Span searchSpan = reporter.getFirstSpan(500); - validateSpanContent(searchSpan, String.format("Elasticsearch: POST /%s/_search", INDEX), -1, "POST"); + validateSpan(searchSpan) + .method("POST").pathName("/%s/_search", INDEX) + .statusCode(-1) + .disableHttpUrlCheck() + .expectAnyStatement() + .check(); assertThat(searchSpan.getOutcome()) .describedAs("span outcome should be unknown when cancelled") @@ -188,15 +192,15 @@ public void onFailure(Exception exception) { } @Override - protected void verifyMultiSearchTemplateSpanContent(Span span) { - validateDbContextContent(span, "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\",\"ccs_minimize_roundtrips\":true}\n" + - "{\"source\":\"{ \\\"query\\\": { \\\"term\\\" : { \\\"{{field}}\\\" : \\\"{{value}}\\\" } }, \\\"size\\\" : \\\"{{size}}\\\"}\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"},\"explain\":false,\"profile\":false}\n"); + protected String getMultiSearchTemplateStatement() { + return "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\",\"ccs_minimize_roundtrips\":true}\n" + + "{\"source\":\"{ \\\"query\\\": { \\\"term\\\" : { \\\"{{field}}\\\" : \\\"{{value}}\\\" } }, \\\"size\\\" : \\\"{{size}}\\\"}\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"},\"explain\":false,\"profile\":false}\n"; } @Override - protected void verifyMultiSearchSpanContent(Span span) { - validateDbContextContent(span, "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\",\"ccs_minimize_roundtrips\":true}\n" + - "{\"query\":{\"match\":{\"foo\":{\"query\":\"bar\",\"operator\":\"OR\",\"prefix_length\":0,\"max_expansions\":50,\"fuzzy_transpositions\":true,\"lenient\":false,\"zero_terms_query\":\"NONE\",\"auto_generate_synonyms_phrase_query\":true,\"boost\":1.0}}}}\n"); + protected String getExpectedMultisearchStatement() { + return "{\"index\":[\"my-index\"],\"types\":[],\"search_type\":\"query_then_fetch\",\"ccs_minimize_roundtrips\":true}\n" + + "{\"query\":{\"match\":{\"foo\":{\"query\":\"bar\",\"operator\":\"OR\",\"prefix_length\":0,\"max_expansions\":50,\"fuzzy_transpositions\":true,\"lenient\":false,\"zero_terms_query\":\"NONE\",\"auto_generate_synonyms_phrase_query\":true,\"boost\":1.0}}}}\n"; } @Override diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/agent/esrestclient/v8_x/Elasticsearch8JavaIT.java similarity index 76% rename from apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/agent/esrestclient/v8_x/Elasticsearch8JavaIT.java index b1feb13552..fbc1a7e392 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/AbstractElasticsearchJavaTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/agent/esrestclient/v8_x/Elasticsearch8JavaIT.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.esjavaclient; +package co.elastic.apm.agent.esrestclient.v8_x; import co.elastic.apm.agent.esrestclient.AbstractEsClientInstrumentationTest; import co.elastic.apm.agent.impl.transaction.Span; @@ -69,25 +69,39 @@ import co.elastic.clients.elasticsearch.rollup.RollupSearchRequest; import co.elastic.clients.elasticsearch.rollup.RollupSearchResponse; import co.elastic.clients.json.JsonData; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; import jakarta.json.Json; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.client.RestClient; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; -public abstract class AbstractElasticsearchJavaTest extends AbstractEsClientInstrumentationTest { +@RunWith(Parameterized.class) +public class Elasticsearch8JavaIT extends AbstractEsClientInstrumentationTest { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchJavaTest.class); + private static final String ELASTICSEARCH_CONTAINER_VERSION = "docker.elastic.co/elasticsearch/elasticsearch:7.17.2"; + + private static final Logger LOGGER = LoggerFactory.getLogger(Elasticsearch8JavaIT.class); protected static final String USER_NAME = "elastic-user"; protected static final String PASSWORD = "elastic-pass"; @@ -95,17 +109,57 @@ public abstract class AbstractElasticsearchJavaTest extends AbstractEsClientInst protected static ElasticsearchClient client; protected static ElasticsearchAsyncClient asyncClient; + public Elasticsearch8JavaIT(boolean async) { + this.async = async; + } + + @BeforeClass + public static void startElasticsearchContainerAndClient() throws IOException { + startContainer(ELASTICSEARCH_CONTAINER_VERSION); + + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(USER_NAME, PASSWORD)); + + RestClient restClient = RestClient.builder(HttpHost.create(container.getHttpHostAddress())) + .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)) + .build(); + + ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + client = new ElasticsearchClient(transport); + asyncClient = new ElasticsearchAsyncClient(transport); + + client.indices().create(new CreateIndexRequest.Builder().index(INDEX).build()); + reporter.reset(); + } + + @AfterClass + public static void stopElasticsearchContainerAndClient() throws IOException { + if (client != null) { + // prevent misleading NPE when failed to start container + client.indices().delete(new DeleteIndexRequest.Builder().index(INDEX).build()); + } + } + + @Test public void testCreateAndDeleteIndex() throws IOException, ExecutionException, InterruptedException { // Create an Index doCreateIndex(new CreateIndexRequest.Builder().index(SECOND_INDEX).build()); - validateSpanContentAfterIndexCreateRequest(); + validateSpan() + .method("PUT") + .endpointName("indices.create") + .expectPathPart("index", SECOND_INDEX) + .check(); reporter.reset(); // Delete the index doDeleteIndex(new DeleteIndexRequest.Builder().index(SECOND_INDEX).build()); - validateSpanContentAfterIndexDeleteRequest(); + validateSpan() + .method("DELETE") + .endpointName("indices.delete") + .expectPathPart("index", SECOND_INDEX) + .check(); } @Test @@ -135,7 +189,12 @@ public void testTryToDeleteNonExistingIndex() { Span span = reporter.getFirstSpan(); assertThat(span.getOutcome()).isEqualTo(Outcome.FAILURE); - validateSpanContent(span, "Elasticsearch: indices.delete", 404, "DELETE", Map.of("index", SECOND_INDEX)); + validateSpan(span) + .method("DELETE") + .endpointName("indices.delete") + .expectPathPart("index", SECOND_INDEX) + .statusCode(404) + .check(); } @Test @@ -145,7 +204,13 @@ public void testDocumentScenario() throws Exception { List spans = reporter.getSpans(); try { assertThat(spans).hasSize(1); - validateSpanContent(spans.get(0), "Elasticsearch: index", 201, "PUT", Map.of("index", INDEX, "id", DOC_ID)); + validateSpan(spans.get(0)) + .method("PUT") + .endpointName("index") + .expectPathPart("index", INDEX) + .expectPathPart("id", DOC_ID) + .statusCode(201) + .check(); // *** RESET *** reporter.reset(); @@ -159,8 +224,13 @@ public void testDocumentScenario() throws Exception { spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span searchSpan = spans.get(0); - validateSpanContent(searchSpan, "Elasticsearch: search", 200, "POST", Map.of("index", INDEX)); - validateDbContextContent(searchSpan, "{\"from\":0,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}"); + + validateSpan(searchSpan) + .method("POST") + .endpointName("search") + .expectPathPart("index", INDEX) + .expectStatement("{\"from\":0,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}") + .check(); // *** RESET *** reporter.reset(); @@ -179,10 +249,20 @@ public void testDocumentScenario() throws Exception { spans = reporter.getSpans(); assertThat(spans).hasSize(2); Span updateSpan = spans.get(0); - validateSpanContent(updateSpan, "Elasticsearch: update", 200, "POST", Map.of("index", INDEX, "id", DOC_ID)); + validateSpan(updateSpan) + .method("POST") + .endpointName("update") + .expectPathPart("index", INDEX) + .expectPathPart("id", DOC_ID) + .check(); + searchSpan = spans.get(1); - validateSpanContent(searchSpan, "Elasticsearch: search", 200, "POST", Map.of("index", INDEX)); - validateDbContextContent(searchSpan, "{}"); + validateSpan(searchSpan) + .method("POST") + .endpointName("search") + .expectPathPart("index", INDEX) + .expectStatement("{}") + .check(); // *** RESET *** reporter.reset(); @@ -191,7 +271,12 @@ public void testDocumentScenario() throws Exception { // 4. Delete document and validate span content. co.elastic.clients.elasticsearch.core.DeleteResponse dr = deleteDocument(); assertThat(dr.result().jsonValue()).isEqualTo("deleted"); - validateSpanContent(spans.get(0), "Elasticsearch: delete", 200, "DELETE", Map.of("index", INDEX, "id", DOC_ID)); + validateSpan(spans.get(0)) + .method("DELETE") + .endpointName("delete") + .expectPathPart("index", INDEX) + .expectPathPart("id", DOC_ID) + .check(); } } @@ -207,11 +292,12 @@ public void testCountRequest_validateSpanContentAndDbContext() throws Exception try { CountResponse responses = doCount(countRequest); assertThat(responses.count()).isEqualTo(1L); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: count", 200, "POST", Map.of("index", INDEX)); - validateDbContextContent(span, "{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}}}"); + validateSpan() + .method("POST") + .endpointName("count") + .expectPathPart("index", INDEX) + .expectStatement("{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}}}") + .check(); } finally { deleteDocument(); } @@ -226,9 +312,26 @@ public void testMultiSearchRequest_validateSpanContentAndDbContext() throws Inte MsearchRequest multiSearchRequestWithIndex = getMultiSearchRequestBuilder().index(INDEX).build(); try { - doMultiSearchAndSpanValidate(multiSearchRequest, "Elasticsearch: POST /_msearch"); + doMultiSearch(multiSearchRequest, Map.class); + + String statement = "{\"index\":[\"my-index\"],\"search_type\":\"query_then_fetch\"}\n" + + "{\"query\":{\"match\":{\"foo\":{\"query\":\"bar\"}}}}\n"; + + validateSpan() + .method("POST") + .endpointName("msearch") + .expectNoPathParts() + .expectStatement(statement) + .check(); reporter.reset(); - doMultiSearchAndSpanValidate(multiSearchRequestWithIndex, "Elasticsearch: POST /{index}/_msearch"); + doMultiSearch(multiSearchRequestWithIndex, Map.class); + + validateSpan() + .method("POST") + .endpointName("msearch") + .expectPathPart("index", INDEX) + .expectStatement(statement) + .check(); } finally { deleteDocument(); } @@ -252,16 +355,6 @@ private MsearchRequest.Builder getMultiSearchRequestBuilder() { .build()); } - private void doMultiSearchAndSpanValidate(MsearchRequest multiSearchRequest, String expectedSpanName) throws IOException, ExecutionException, InterruptedException { - doMultiSearch(multiSearchRequest, Map.class); - - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, expectedSpanName, 200, "POST"); - verifyMultiSearchSpanContent(span); - } - @Test public void testRollupSearch_validateSpanContentAndDbContext() throws InterruptedException, ExecutionException, IOException { prepareDefaultDocumentAndIndex(); @@ -278,11 +371,13 @@ public void testRollupSearch_validateSpanContentAndDbContext() throws Interrupte RollupSearchResponse response = doRollupSearch(searchRequest, Map.class); verifyTotalHits(response.hits()); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: rollup.rollup_search", 200, "POST", Map.of("index", INDEX)); - validateDbContextContent(span, "{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}"); + + validateSpan() + .method("POST") + .endpointName("rollup.rollup_search") + .expectPathPart("index", INDEX) + .expectStatement("{\"query\":{\"term\":{\"foo\":{\"value\":\"bar\"}}},\"size\":5}") + .check(); } finally { deleteDocument(); } @@ -299,11 +394,13 @@ public void testSearchTemplateRequest_validateSpanContentAndDbContext() throws I SearchTemplateResponse response = doSearchTemplate(searchTemplateRequest, Map.class); verifyTotalHits(response.hits()); - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: search_template", 200, "POST", Map.of("index", INDEX)); - validateDbContextContent(span, "{\"id\":\"elastic-search-template\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"}}"); + + validateSpan() + .method("POST") + .endpointName("search_template") + .expectPathPart("index", INDEX) + .expectStatement("{\"id\":\"elastic-search-template\",\"params\":{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"}}") + .check(); } finally { deleteMustacheScript(); deleteDocument(); @@ -341,8 +438,14 @@ public void testMultisearchTemplateRequest_validateSpanContentAndDbContext() thr List spans = reporter.getSpans(); assertThat(spans).hasSize(1); Span span = spans.get(0); - validateSpanContent(span, "Elasticsearch: msearch_template", 200, "POST", Map.of()); - verifyMultiSearchTemplateSpanContent(span); + + validateSpan(span) + .method("POST") + .endpointName("msearch_template") + .expectNoPathParts() + .expectStatement("{\"index\":[\"my-index\"],\"search_type\":\"query_then_fetch\"}\n" + + "{\"id\":\"elastic-search-template\",\"params\":{\"value\":\"bar\",\"field\":\"foo\",\"size\":5}}") + .check(); } finally { deleteMustacheScript(); deleteDocument(); @@ -370,31 +473,13 @@ public void testScenarioAsBulkRequest() throws IOException, ExecutionException, doBulk(bulkRequest); - validateSpanContentAfterBulkRequest(); + validateSpan() + .method("POST") + .endpointName("bulk") + .expectNoPathParts() + .check(); } - private void verifyMultiSearchTemplateSpanContent(Span span) { - String immutablePart = "{\"index\":[\"my-index\"],\"search_type\":\"query_then_fetch\"}\n" + - "{\"id\":\"elastic-search-template\",\"params\":"; - List params = Arrays.asList( - "{\"size\":5,\"field\":\"foo\",\"value\":\"bar\"}", - "{\"size\":5,\"value\":\"bar\",\"field\":\"foo\"}", - "{\"field\":\"foo\",\"size\":5,\"value\":\"bar\"}", - "{\"value\":\"bar\",\"size\":5,\"field\":\"foo\"}", - "{\"field\":\"foo\",\"value\":\"bar\",\"size\":5}", - "{\"value\":\"bar\",\"field\":\"foo\",\"size\":5}"); - String end = "}\n"; - - List possibleSpanContent = params.stream() - .map(k -> immutablePart + k + end).collect(Collectors.toList()); - - validateDbContextContent(span, possibleSpanContent); - } - - private void verifyMultiSearchSpanContent(Span span) { - validateDbContextContent(span, "{\"index\":[\"my-index\"],\"search_type\":\"query_then_fetch\"}\n" + - "{\"query\":{\"match\":{\"foo\":{\"query\":\"bar\"}}}}\n"); - } private void verifyTotalHits(HitsMetadata hitsMetadata) { assertThat(hitsMetadata.total().value()).isEqualTo(1L); @@ -535,5 +620,4 @@ private SearchTemplateRequest prepareSearchTemplateRequest(String templateId) { .build(); return searchTemplateRequest; } - } diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java deleted file mode 100644 index d64116b0a5..0000000000 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/test/java/co/elastic/apm/esjavaclient/ElasticsearchJavaIT.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package co.elastic.apm.esjavaclient; - -import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient; -import co.elastic.clients.elasticsearch.ElasticsearchClient; -import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; -import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest; -import co.elastic.clients.json.jackson.JacksonJsonpMapper; -import co.elastic.clients.transport.ElasticsearchTransport; -import co.elastic.clients.transport.rest_client.RestClientTransport; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.elasticsearch.client.RestClient; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.io.IOException; - -@RunWith(Parameterized.class) -public class ElasticsearchJavaIT extends AbstractElasticsearchJavaTest { - - private static final String ELASTICSEARCH_CONTAINER_VERSION = "docker.elastic.co/elasticsearch/elasticsearch:7.17.2"; - - public ElasticsearchJavaIT(boolean async) { - this.async = async; - } - - @BeforeClass - public static void startElasticsearchContainerAndClient() throws IOException { - startContainer(ELASTICSEARCH_CONTAINER_VERSION); - - CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(USER_NAME, PASSWORD)); - - RestClient restClient = RestClient.builder(HttpHost.create(container.getHttpHostAddress())) - .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)) - .build(); - - ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); - client = new ElasticsearchClient(transport); - asyncClient = new ElasticsearchAsyncClient(transport); - - client.indices().create(new CreateIndexRequest.Builder().index(INDEX).build()); - reporter.reset(); - } - - @AfterClass - public static void stopElasticsearchContainerAndClient() throws IOException { - if (client != null) { - // prevent misleading NPE when failed to start container - client.indices().delete(new DeleteIndexRequest.Builder().index(INDEX).build()); - } - } -} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java index 69b81cba42..9da1d712d6 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointDefinition.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ - package co.elastic.apm.agent.esrestclient; import co.elastic.apm.agent.tracer.Span; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java index 4247a531a0..d32aecd0b7 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMap.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ - package co.elastic.apm.agent.esrestclient; import javax.annotation.Nullable; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java index 809e42ef15..ae641992d6 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/AbstractEsClientInstrumentationTest.java @@ -26,6 +26,9 @@ import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.testutils.TestContainersUtils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -34,7 +37,7 @@ import javax.annotation.Nullable; import java.util.Arrays; -import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -56,21 +59,9 @@ public abstract class AbstractEsClientInstrumentationTest extends AbstractInstru protected static final String FOO = "foo"; protected static final String BAR = "bar"; protected static final String BAZ = "baz"; - protected static final String SEARCH_QUERY_PATH_SUFFIX = "_search"; - protected static final String MSEARCH_QUERY_PATH_SUFFIX = "_msearch"; - protected static final String COUNT_QUERY_PATH_SUFFIX = "_count"; protected boolean async; - private boolean checkHttpUrl = true; - - /** - * Disables HTTP URL check for the current test method - */ - public void disableHttpUrlCheck() { - checkHttpUrl = false; - } - @Parameterized.Parameters(name = "Async={0}") public static Iterable data() { return Arrays.asList(new Object[][]{{Boolean.FALSE}, {Boolean.TRUE}}); @@ -91,12 +82,6 @@ public static void stopContainer() { @Before public void startTransaction() { - // While JUnit does not recycle test class instances between method invocations by default - // this test should not be required, but it allows to ensure proper correctness even if that changes - assertThat(checkHttpUrl) - .describedAs("checking HTTP URLs should be enabled by default") - .isTrue(); - startTestRootTransaction("ES Transaction"); } @@ -115,105 +100,185 @@ public void assertThatErrorsExistWhenDeleteNonExistingIndex() { assertThat(errorCapture.getException()).isNotNull(); } - protected void validateSpanContentWithoutContext(Span span, String expectedName) { - assertThat(span) - .hasType(SPAN_TYPE) - .hasSubType(ELASTICSEARCH) - .hasAction(SPAN_ACTION) - .hasName(expectedName); - - assertThat(span.getContext().getDb().getType()).isEqualTo(ELASTICSEARCH); - if (!expectedName.contains(SEARCH_QUERY_PATH_SUFFIX) && !expectedName.contains(MSEARCH_QUERY_PATH_SUFFIX) && !expectedName.contains(COUNT_QUERY_PATH_SUFFIX)) { - assertThat((CharSequence) (span.getContext().getDb().getStatementBuffer())).isNull(); - } + protected EsSpanValidationBuilder validateSpan(Span spanToValidate) { + return new EsSpanValidationBuilder(spanToValidate); } - protected void validateDbContextContent(Span span, String statement) { - validateDbContextContent(span, Collections.singletonList(statement)); + protected EsSpanValidationBuilder validateSpan() { + List spans = reporter.getSpans(); + assertThat(spans).hasSize(1); + return validateSpan(spans.get(0)); } - protected void validateDbContextContent(Span span, List possibleContents) { - Db db = span.getContext().getDb(); - assertThat(db.getType()).isEqualTo(ELASTICSEARCH); - assertThat((CharSequence) db.getStatementBuffer()).isNotNull(); + protected static class EsSpanValidationBuilder { - assertThat(db.getStatementBuffer().toString()).isIn(possibleContents); - } + private static final ObjectMapper jackson = new ObjectMapper(); + private final Span span; - protected void validateSpanContent(Span span, String expectedName, int statusCode, String method, Map expectedPathParts) { - validateSpanContent(span, expectedName, statusCode, method); - expectedPathParts.forEach((partName, value) -> { - assertThat(span.getOtelAttributes()).containsEntry("db.elasticsearch.path_parts." + partName, value); - }); - List spanPartAttributes = span.getOtelAttributes().keySet().stream() - .filter(name -> name.startsWith("db.elasticsearch.path_parts.")) - .map(name -> name.substring("db.elasticsearch.path_parts.".length())) - .collect(Collectors.toList()); - assertThat(spanPartAttributes).containsExactlyElementsOf(expectedPathParts.keySet()); - } + private boolean statementExpectedNonNull = false; - protected void validateSpanContent(Span span, String expectedName, int statusCode, String method) { - validateSpanContentWithoutContext(span, expectedName); - validateHttpContextContent(span.getContext().getHttp(), statusCode, method); - validateDestinationContextContent(span.getContext().getDestination()); + @Nullable + private JsonNode expectedStatement; - assertThat(span.getContext().getServiceTarget()) - .hasType(ELASTICSEARCH) - .hasNoName() // we can't validate cluster name here as there is no simple way to inject that without reverse-proxy - .hasDestinationResource(ELASTICSEARCH); - } + @Nullable + private Map expectedPathParts; + + private int expectedStatusCode = 200; + + @Nullable + private String expectedHttpMethod; - private void validateDestinationContextContent(Destination destination) { - assertThat(destination).isNotNull(); - if (reporter.checkDestinationAddress()) { - assertThat(destination.getAddress().toString()).isEqualTo(container.getContainerIpAddress()); - assertThat(destination.getPort()).isEqualTo(container.getMappedPort(9200)); + @Nullable + private String expectedNameEndpoint; + + @Nullable + private String expectedNamePath; + + @Nullable + private String expectedHttpUrl = "http://" + container.getHttpHostAddress(); + + public EsSpanValidationBuilder(Span spanToValidate) { + this.span = spanToValidate; } - } - private void validateHttpContextContent(Http http, int statusCode, String method) { - assertThat(http).isNotNull(); - assertThat(http.getMethod()).isEqualTo(method); - assertThat(http.getStatusCode()).isEqualTo(statusCode); - if (checkHttpUrl) { - assertThat(http.getUrl().toString()).isEqualTo("http://" + container.getHttpHostAddress()); + public EsSpanValidationBuilder expectNoStatement() { + statementExpectedNonNull = false; + expectedStatement = null; + return this; } - } - protected void validateSpanContentAfterIndexCreateRequest() { - validateSpanContentAfterIndexCreateRequest(true); - } + public EsSpanValidationBuilder expectAnyStatement() { + statementExpectedNonNull = true; + expectedStatement = null; + return this; + } - protected void validateSpanContentAfterIndexCreateRequest(boolean usePathPattern) { - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - if(usePathPattern){ - validateSpanContent(spans.get(0), "Elasticsearch: indices.create", 200, "PUT", Map.of("index", SECOND_INDEX)); - } else { - validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s", SECOND_INDEX), 200, "PUT" ); + public EsSpanValidationBuilder expectStatement(String statement) { + try { + this.expectedStatement = jackson.readTree(statement); + statementExpectedNonNull = true; + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(e); + } + return this; } - } - protected void validateSpanContentAfterIndexDeleteRequest() { - validateSpanContentAfterIndexDeleteRequest(true); - } + public EsSpanValidationBuilder expectPathPart(String key, String value) { + if (expectedPathParts == null) { + expectedPathParts = new HashMap<>(); + } + expectedPathParts.put(key, value); + return this; + } - protected void validateSpanContentAfterIndexDeleteRequest(boolean usePathPattern) { - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - if(usePathPattern){ - validateSpanContent(spans.get(0), "Elasticsearch: indices.delete", 200, "DELETE", Map.of("index", SECOND_INDEX)); - } else { - validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s", SECOND_INDEX), 200, "DELETE"); + public EsSpanValidationBuilder expectNoPathParts() { + expectedPathParts = new HashMap<>(); + return this; } - } + public EsSpanValidationBuilder statusCode(int expectedStatusCode) { + this.expectedStatusCode = expectedStatusCode; + return this; + } + + public EsSpanValidationBuilder method(String httpMethod) { + this.expectedHttpMethod = httpMethod; + return this; + } + + public EsSpanValidationBuilder disableHttpUrlCheck() { + expectedHttpUrl = null; + return this; + } + + public EsSpanValidationBuilder endpointName(String endpoint) { + this.expectedNameEndpoint = endpoint; + this.expectedNamePath = null; + return this; + } + + public EsSpanValidationBuilder pathName(String pathFormat, Object... args) { + this.expectedNameEndpoint = null; + this.expectedNamePath = String.format(pathFormat, args); + return this; + } + + public void check() { + assertThat(span) + .hasType(SPAN_TYPE) + .hasSubType(ELASTICSEARCH) + .hasAction(SPAN_ACTION); + + if (expectedNameEndpoint != null) { + assertThat(span.getOtelAttributes()).containsEntry("db.operation", expectedNameEndpoint); + assertThat(span).hasName("Elasticsearch: " + expectedNameEndpoint); + } else if (expectedNamePath != null) { + assertThat(span).hasName("Elasticsearch: " + expectedHttpMethod + " " + expectedNamePath); + } + + checkHttpContext(); + checkDbContext(); + checkPathPartAttributes(); + checkDestinationContext(); + } + + + private void checkHttpContext() { + Http http = span.getContext().getHttp(); + assertThat(http).isNotNull(); + if (expectedHttpMethod != null) { + assertThat(http.getMethod()).isEqualTo(expectedHttpMethod); + } + assertThat(http.getStatusCode()).isEqualTo(expectedStatusCode); + if (expectedHttpUrl != null) { + assertThat(http.getUrl().toString()).isEqualTo(expectedHttpUrl); + } + } + + private void checkDbContext() { + Db db = span.getContext().getDb(); + assertThat(db.getType()).isEqualTo(ELASTICSEARCH); + CharSequence statement = db.getStatementBuffer(); + if (statementExpectedNonNull) { + assertThat(statement).isNotNull(); + if (expectedStatement != null) { + //Comparing JsonNodes ensures that the child-order within JSON objects does not matter + JsonNode parsedStatement; + try { + parsedStatement = jackson.readTree(statement.toString()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + assertThat(parsedStatement).isEqualTo(expectedStatement); + } + } else { + assertThat(statement).isNull(); + } + } + + private void checkPathPartAttributes() { + if (expectedPathParts != null) { + expectedPathParts.forEach((partName, value) -> { + assertThat(span.getOtelAttributes()).containsEntry("db.elasticsearch.path_parts." + partName, value); + }); + List spanPartAttributes = span.getOtelAttributes().keySet().stream() + .filter(name -> name.startsWith("db.elasticsearch.path_parts.")) + .map(name -> name.substring("db.elasticsearch.path_parts.".length())) + .collect(Collectors.toList()); + assertThat(spanPartAttributes).containsExactlyElementsOf(expectedPathParts.keySet()); + } + } + + private void checkDestinationContext() { + Destination destination = span.getContext().getDestination(); + assertThat(destination).isNotNull(); + if (reporter.checkDestinationAddress()) { + assertThat(destination.getAddress().toString()).isEqualTo(container.getContainerIpAddress()); + assertThat(destination.getPort()).isEqualTo(container.getMappedPort(9200)); + } + } - protected void validateSpanContentAfterBulkRequest() { - List spans = reporter.getSpans(); - assertThat(spans).hasSize(1); - assertThat(spans.get(0).getNameAsString()).isEqualTo("Elasticsearch: POST /_bulk"); } } From b50352fb6e0ed450cb17820a4a5db34c42918ef9 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 12 Jul 2023 13:43:36 +0200 Subject: [PATCH 07/10] Added missing test --- .../ElasticsearchEndpointMapTest.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java new file mode 100644 index 0000000000..001b8d158e --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java @@ -0,0 +1,137 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package co.elastic.apm.agent.esrestclient; + +import co.elastic.apm.agent.tracer.Span; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; + +public class ElasticsearchEndpointMapTest { + + private static final Set SEARCH_ENDPOINTS = + new HashSet<>( + Arrays.asList( + "search", + "async_search.submit", + "msearch", + "eql.search", + "terms_enum", + "search_template", + "msearch_template", + "render_search_template")); + + private static List getPathParts(String route) { + List pathParts = new ArrayList<>(); + String routeFragment = route; + int paramStartIndex = routeFragment.indexOf('{'); + while (paramStartIndex >= 0) { + int paramEndIndex = routeFragment.indexOf('}'); + if (paramEndIndex < 0 || paramEndIndex <= paramStartIndex + 1) { + throw new IllegalStateException("Invalid route syntax!"); + } + pathParts.add(routeFragment.substring(paramStartIndex + 1, paramEndIndex)); + + int nextIdx = paramEndIndex + 1; + if (nextIdx >= routeFragment.length()) { + break; + } + + routeFragment = routeFragment.substring(nextIdx); + paramStartIndex = routeFragment.indexOf('{'); + } + return pathParts; + } + + @Test + public void testIsSearchEndpoint() { + for (ElasticsearchEndpointDefinition esEndpointDefinition : + ElasticsearchEndpointMap.getAllEndpoints()) { + String endpointId = esEndpointDefinition.getEndpointName(); + assertEquals(SEARCH_ENDPOINTS.contains(endpointId), esEndpointDefinition.isSearchEndpoint()); + } + } + + @Test + public void testProcessPathParts() { + for (ElasticsearchEndpointDefinition esEndpointDefinition : + ElasticsearchEndpointMap.getAllEndpoints()) { + for (String route : + esEndpointDefinition.getRoutes().stream() + .map(ElasticsearchEndpointDefinition.Route::getName) + .collect(Collectors.toList())) { + List pathParts = getPathParts(route); + String resolvedRoute = route.replace("{", "").replace("}", ""); + Map observedParams = new HashMap<>(); + + Span dummy = Mockito.mock(Span.class); + doAnswer((invoc) -> observedParams.put(invoc.getArgument(0), invoc.getArgument(1))) + .when(dummy).withOtelAttribute(any(), any()); + esEndpointDefinition.addPathPartAttributes(resolvedRoute, dummy); + + Map expectedMap = new HashMap<>(); + pathParts.forEach(part -> expectedMap.put("db.elasticsearch.path_parts." + part, part)); + + assertEquals(expectedMap, observedParams); + } + } + } + + @Test + public void testSearchEndpoint() { + ElasticsearchEndpointDefinition esEndpoint = ElasticsearchEndpointMap.get("search"); + + Map observedParams = new HashMap<>(); + Span dummy = Mockito.mock(Span.class); + doAnswer((invoc) -> observedParams.put(invoc.getArgument(0), invoc.getArgument(1))) + .when(dummy).withOtelAttribute(any(), any()); + + esEndpoint.addPathPartAttributes("/test-index-1,test-index-2/_search", dummy); + + assertEquals("test-index-1,test-index-2", observedParams.get("db.elasticsearch.path_parts.index")); + } + + @Test + public void testBuildRegexPattern() { + Pattern pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_nodes/{node_id}/shutdown"); + assertEquals("^/_nodes/(?[^/]+)/shutdown$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_snapshot/{repository}/{snapshot}/_mount"); + assertEquals("^/_snapshot/(?[^/]+)/(?[^/]+)/_mount$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_security/profile/_suggest"); + assertEquals("^/_security/profile/_suggest$", pattern.pattern()); + + pattern = + ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern( + "/_application/search_application/{name}"); + assertEquals("^/_application/search_application/(?[^/]+)$", pattern.pattern()); + + pattern = ElasticsearchEndpointDefinition.EndpointPattern.buildRegexPattern("/"); + assertEquals("^/$", pattern.pattern()); + } + + +} From 50da37cb9a75f315e55e8af6e8f7d36836173689 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 12 Jul 2023 13:56:25 +0200 Subject: [PATCH 08/10] Added missing comments --- .../RestClientTransportInstrumentation.java | 5 +++++ .../ElasticsearchEndpointMapTest.java | 19 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/RestClientTransportInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/RestClientTransportInstrumentation.java index c5977ce4eb..69241d1b8d 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/RestClientTransportInstrumentation.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/src/main/java/co/elastic/apm/agent/esrestclient/v8_x/RestClientTransportInstrumentation.java @@ -21,6 +21,7 @@ import co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentation; import co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentationHelper; import co.elastic.clients.transport.Endpoint; +import co.elastic.clients.transport.TransportOptions; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -32,6 +33,10 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +/** + * Instruments {@link co.elastic.clients.transport.rest_client.RestClientTransport#prepareLowLevelRequest(Object, Endpoint, TransportOptions)}. + */ +@SuppressWarnings("JavadocReference") public class RestClientTransportInstrumentation extends ElasticsearchRestClientInstrumentation { @Override diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java index 001b8d158e..d99cb82f22 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchEndpointMapTest.java @@ -1,8 +1,21 @@ /* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ - package co.elastic.apm.agent.esrestclient; import co.elastic.apm.agent.tracer.Span; From 31de2a31693f2d8e7172af79f4d8c97ed5dbc1e1 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 12 Jul 2023 15:01:06 +0200 Subject: [PATCH 09/10] Added changelog --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 711306301b..b5b2a5a967 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -34,6 +34,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: [float] ===== Features * Capture `container.id` for cgroups v2 - {pull}3199[#3199] +* Improved span naming and attribute collection for 7.16+ elasticsearch clients - {pull}3157[#3157] [float] ===== Bug fixes From 9d6a3f57536cf715f3b937d085f38167034f8b06 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 26 Jul 2023 11:43:47 +0200 Subject: [PATCH 10/10] Fix changelog merge --- CHANGELOG.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 261b301131..bd354ffa5b 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -35,6 +35,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: ===== Features * Added W3C baggage propagation - {pull}3236[#3236] * Added support for baggage in OpenTelemetry bridge - {pull}3249[#3249] +* Improved span naming and attribute collection for 7.16+ elasticsearch clients - {pull}3157[#3157] [[release-notes-1.x]] === Java Agent version 1.x @@ -45,7 +46,6 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: [float] ===== Features * Capture `container.id` for cgroups v2 - {pull}3199[#3199] -* Improved span naming and attribute collection for 7.16+ elasticsearch clients - {pull}3157[#3157] [float] ===== Bug fixes