/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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 org.elasticsearch.http; import org.apache.http.message.BasicHeader; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.ActionFilter; import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; import org.elasticsearch.client.Client; import org.elasticsearch.client.Response; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.GeoShapeQueryBuilder; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder; import org.elasticsearch.index.query.MoreLikeThisQueryBuilder.Item; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.TermsQueryBuilder; import org.elasticsearch.indices.TermsLookup; import org.elasticsearch.plugins.ActionPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.threadpool.ThreadPool; import org.junit.After; import org.junit.Before; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import static java.util.Collections.singletonList; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.test.ESIntegTestCase.Scope.SUITE; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @ClusterScope(scope = SUITE) public class ContextAndHeaderTransportIT extends HttpSmokeTestCase { private static final List<RequestAndHeaders> requests = new CopyOnWriteArrayList<>(); private static final String CUSTOM_HEADER = "SomeCustomHeader"; private String randomHeaderValue = randomAlphaOfLength(20); private String queryIndex = "query-" + randomAlphaOfLength(10).toLowerCase(Locale.ROOT); private String lookupIndex = "lookup-" + randomAlphaOfLength(10).toLowerCase(Locale.ROOT); @Override protected Settings nodeSettings(int nodeOrdinal) { return Settings.builder() .put(super.nodeSettings(nodeOrdinal)) .put(NetworkModule.HTTP_ENABLED.getKey(), true) .build(); } @Override protected Collection<Class<? extends Plugin>> nodePlugins() { ArrayList<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins()); plugins.add(ActionLoggingPlugin.class); plugins.add(CustomHeadersPlugin.class); return plugins; } @Before public void createIndices() throws Exception { String mapping = jsonBuilder().startObject().startObject("type") .startObject("properties") .startObject("location").field("type", "geo_shape").endObject() .startObject("name").field("type", "text").endObject() .endObject() .endObject().endObject().string(); Settings settings = Settings.builder() .put(indexSettings()) .put(SETTING_NUMBER_OF_SHARDS, 1) // A single shard will help to keep the tests repeatable. .build(); assertAcked(transportClient().admin().indices().prepareCreate(lookupIndex) .setSettings(settings).addMapping("type", mapping, XContentType.JSON)); assertAcked(transportClient().admin().indices().prepareCreate(queryIndex) .setSettings(settings).addMapping("type", mapping, XContentType.JSON)); ensureGreen(queryIndex, lookupIndex); requests.clear(); } @After public void checkAllRequestsContainHeaders() { assertRequestsContainHeader(IndexRequest.class); assertRequestsContainHeader(RefreshRequest.class); } public void testThatTermsLookupGetRequestContainsContextAndHeaders() throws Exception { transportClient().prepareIndex(lookupIndex, "type", "1") .setSource(jsonBuilder().startObject().array("followers", "foo", "bar", "baz").endObject()).get(); transportClient().prepareIndex(queryIndex, "type", "1") .setSource(jsonBuilder().startObject().field("username", "foo").endObject()).get(); transportClient().admin().indices().prepareRefresh(queryIndex, lookupIndex).get(); TermsLookup termsLookup = new TermsLookup(lookupIndex, "type", "1", "followers"); TermsQueryBuilder termsLookupFilterBuilder = QueryBuilders.termsLookupQuery("username", termsLookup); BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchAllQuery()).must(termsLookupFilterBuilder); SearchResponse searchResponse = transportClient() .prepareSearch(queryIndex) .setQuery(queryBuilder) .get(); assertNoFailures(searchResponse); assertHitCount(searchResponse, 1); assertGetRequestsContainHeaders(); } public void testThatGeoShapeQueryGetRequestContainsContextAndHeaders() throws Exception { transportClient().prepareIndex(lookupIndex, "type", "1").setSource(jsonBuilder().startObject() .field("name", "Munich Suburban Area") .startObject("location") .field("type", "polygon") .startArray("coordinates").startArray() .startArray().value(11.34).value(48.25).endArray() .startArray().value(11.68).value(48.25).endArray() .startArray().value(11.65).value(48.06).endArray() .startArray().value(11.37).value(48.13).endArray() .startArray().value(11.34).value(48.25).endArray() // close the polygon .endArray().endArray() .endObject() .endObject()) .get(); // second document transportClient().prepareIndex(queryIndex, "type", "1").setSource(jsonBuilder().startObject() .field("name", "Munich Center") .startObject("location") .field("type", "point") .startArray("coordinates").value(11.57).value(48.13).endArray() .endObject() .endObject()) .get(); transportClient().admin().indices().prepareRefresh(lookupIndex, queryIndex).get(); GeoShapeQueryBuilder queryBuilder = QueryBuilders.geoShapeQuery("location", "1", "type") .indexedShapeIndex(lookupIndex) .indexedShapePath("location"); SearchResponse searchResponse = transportClient() .prepareSearch(queryIndex) .setQuery(queryBuilder) .get(); assertNoFailures(searchResponse); assertHitCount(searchResponse, 1); assertThat(requests, hasSize(greaterThan(0))); assertGetRequestsContainHeaders(); } public void testThatMoreLikeThisQueryMultiTermVectorRequestContainsContextAndHeaders() throws Exception { transportClient().prepareIndex(lookupIndex, "type", "1") .setSource(jsonBuilder().startObject().field("name", "Star Wars - The new republic").endObject()) .get(); transportClient().prepareIndex(queryIndex, "type", "1") .setSource(jsonBuilder().startObject().field("name", "Jar Jar Binks - A horrible mistake").endObject()) .get(); transportClient().prepareIndex(queryIndex, "type", "2") .setSource(jsonBuilder().startObject().field("name", "Star Wars - Return of the jedi").endObject()) .get(); transportClient().admin().indices().prepareRefresh(lookupIndex, queryIndex).get(); MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = QueryBuilders.moreLikeThisQuery(new String[]{"name"}, null, new Item[]{new Item(lookupIndex, "type", "1")}) .minTermFreq(1) .minDocFreq(1); SearchResponse searchResponse = transportClient() .prepareSearch(queryIndex) .setQuery(moreLikeThisQueryBuilder) .get(); assertNoFailures(searchResponse); assertHitCount(searchResponse, 1); assertRequestsContainHeader(MultiTermVectorsRequest.class); } public void testThatRelevantHttpHeadersBecomeRequestHeaders() throws IOException { final String IRRELEVANT_HEADER = "SomeIrrelevantHeader"; Response response = getRestClient().performRequest("GET", "/" + queryIndex + "/_search", new BasicHeader(CUSTOM_HEADER, randomHeaderValue), new BasicHeader(IRRELEVANT_HEADER, randomHeaderValue)); assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); List<RequestAndHeaders> searchRequests = getRequests(SearchRequest.class); assertThat(searchRequests, hasSize(greaterThan(0))); for (RequestAndHeaders requestAndHeaders : searchRequests) { assertThat(requestAndHeaders.headers.containsKey(CUSTOM_HEADER), is(true)); // was not specified, thus is not included assertThat(requestAndHeaders.headers.containsKey(IRRELEVANT_HEADER), is(false)); } } private List<RequestAndHeaders> getRequests(Class<?> clazz) { List<RequestAndHeaders> results = new ArrayList<>(); for (RequestAndHeaders request : requests) { if (request.request.getClass().equals(clazz)) { results.add(request); } } return results; } private void assertRequestsContainHeader(Class<? extends ActionRequest> clazz) { List<RequestAndHeaders> classRequests = getRequests(clazz); for (RequestAndHeaders request : classRequests) { assertRequestContainsHeader(request.request, request.headers); } } private void assertGetRequestsContainHeaders() { assertGetRequestsContainHeaders(this.lookupIndex); } private void assertGetRequestsContainHeaders(String index) { List<RequestAndHeaders> getRequests = getRequests(GetRequest.class); assertThat(getRequests, hasSize(greaterThan(0))); for (RequestAndHeaders request : getRequests) { if (!((GetRequest)request.request).index().equals(index)) { continue; } assertRequestContainsHeader(request.request, request.headers); } } private void assertRequestContainsHeader(ActionRequest request, Map<String, String> context) { String msg = String.format(Locale.ROOT, "Expected header %s to be in request %s", CUSTOM_HEADER, request.getClass().getName()); if (request instanceof IndexRequest) { IndexRequest indexRequest = (IndexRequest) request; msg = String.format(Locale.ROOT, "Expected header %s to be in index request %s/%s/%s", CUSTOM_HEADER, indexRequest.index(), indexRequest.type(), indexRequest.id()); } assertThat(msg, context.containsKey(CUSTOM_HEADER), is(true)); assertThat(context.get(CUSTOM_HEADER).toString(), is(randomHeaderValue)); } /** * a transport client that adds our random header */ private Client transportClient() { return internalCluster().transportClient().filterWithHeader(Collections.singletonMap(CUSTOM_HEADER, randomHeaderValue)); } public static class ActionLoggingPlugin extends Plugin implements ActionPlugin { @Override public Collection<Module> createGuiceModules() { return Collections.<Module>singletonList(new ActionLoggingModule()); } @Override public List<Class<? extends ActionFilter>> getActionFilters() { return singletonList(LoggingFilter.class); } } public static class ActionLoggingModule extends AbstractModule { @Override protected void configure() { bind(LoggingFilter.class).asEagerSingleton(); } } public static class LoggingFilter extends ActionFilter.Simple { private final ThreadPool threadPool; @Inject public LoggingFilter(Settings settings, ThreadPool pool) { super(settings); this.threadPool = pool; } @Override public int order() { return 999; } @Override protected boolean apply(String action, ActionRequest request, ActionListener<?> listener) { requests.add(new RequestAndHeaders(threadPool.getThreadContext().getHeaders(), request)); return true; } } private static class RequestAndHeaders { final Map<String, String> headers; final ActionRequest request; private RequestAndHeaders(Map<String, String> headers, ActionRequest request) { this.headers = headers; this.request = request; } } public static class CustomHeadersPlugin extends Plugin implements ActionPlugin { public Collection<String> getRestHeaders() { return Collections.singleton(CUSTOM_HEADER); } } }