/* * 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.Header; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.elasticsearch.client.Response; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.plugins.Plugin; import org.hamcrest.Matcher; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.elasticsearch.common.logging.DeprecationLogger.WARNING_HEADER_PATTERN; import static org.elasticsearch.http.TestDeprecationHeaderRestAction.TEST_DEPRECATED_SETTING_TRUE1; import static org.elasticsearch.http.TestDeprecationHeaderRestAction.TEST_DEPRECATED_SETTING_TRUE2; import static org.elasticsearch.http.TestDeprecationHeaderRestAction.TEST_NOT_DEPRECATED_SETTING; import static org.elasticsearch.rest.RestStatus.OK; import static org.elasticsearch.test.hamcrest.RegexMatcher.matches; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; /** * Tests {@code DeprecationLogger} uses the {@code ThreadContext} to add response headers. */ public class DeprecationHttpIT extends HttpSmokeTestCase { @Override protected Settings nodeSettings(int nodeOrdinal) { return Settings.builder() .put(super.nodeSettings(nodeOrdinal)) .put("force.http.enabled", true) // change values of deprecated settings so that accessing them is logged .put(TEST_DEPRECATED_SETTING_TRUE1.getKey(), ! TEST_DEPRECATED_SETTING_TRUE1.getDefault(Settings.EMPTY)) .put(TEST_DEPRECATED_SETTING_TRUE2.getKey(), ! TEST_DEPRECATED_SETTING_TRUE2.getDefault(Settings.EMPTY)) // non-deprecated setting to ensure not everything is logged .put(TEST_NOT_DEPRECATED_SETTING.getKey(), ! TEST_NOT_DEPRECATED_SETTING.getDefault(Settings.EMPTY)) .build(); } @Override protected Collection<Class<? extends Plugin>> nodePlugins() { ArrayList<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins()); plugins.add(TestDeprecationPlugin.class); return plugins; } /** * Attempts to do a scatter/gather request that expects unique responses per sub-request. */ @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/19222") public void testUniqueDeprecationResponsesMergedTogether() throws IOException { final String[] indices = new String[randomIntBetween(2, 5)]; // add at least one document for each index for (int i = 0; i < indices.length; ++i) { indices[i] = "test" + i; // create indices with a single shard to reduce noise; the query only deprecates uniquely by index anyway assertTrue(prepareCreate(indices[i]).setSettings(Settings.builder().put("number_of_shards", 1)).get().isAcknowledged()); int randomDocCount = randomIntBetween(1, 2); for (int j = 0; j < randomDocCount; ++j) { index(indices[i], "type", Integer.toString(j), "{\"field\":" + j + "}"); } } refresh(indices); final String commaSeparatedIndices = Stream.of(indices).collect(Collectors.joining(",")); final String body = "{\"query\":{\"bool\":{\"filter\":[{\"" + TestDeprecatedQueryBuilder.NAME + "\":{}}]}}}"; // trigger all index deprecations Response response = getRestClient().performRequest("GET", "/" + commaSeparatedIndices + "/_search", Collections.emptyMap(), new StringEntity(body, ContentType.APPLICATION_JSON)); assertThat(response.getStatusLine().getStatusCode(), equalTo(OK.getStatus())); final List<String> deprecatedWarnings = getWarningHeaders(response.getHeaders()); final List<Matcher<String>> headerMatchers = new ArrayList<>(indices.length); for (String index : indices) { headerMatchers.add(containsString(LoggerMessageFormat.format("[{}] index", (Object)index))); } assertThat(deprecatedWarnings, hasSize(headerMatchers.size())); for (Matcher<String> headerMatcher : headerMatchers) { assertThat(deprecatedWarnings, hasItem(headerMatcher)); } } public void testDeprecationWarningsAppearInHeaders() throws Exception { doTestDeprecationWarningsAppearInHeaders(); } public void testDeprecationHeadersDoNotGetStuck() throws Exception { doTestDeprecationWarningsAppearInHeaders(); doTestDeprecationWarningsAppearInHeaders(); if (rarely()) { doTestDeprecationWarningsAppearInHeaders(); } } /** * Run a request that receives a predictably randomized number of deprecation warnings. * <p> * Re-running this back-to-back helps to ensure that warnings are not being maintained across requests. */ private void doTestDeprecationWarningsAppearInHeaders() throws IOException { final boolean useDeprecatedField = randomBoolean(); final boolean useNonDeprecatedSetting = randomBoolean(); // deprecated settings should also trigger a deprecation warning final List<Setting<Boolean>> settings = new ArrayList<>(3); settings.add(TEST_DEPRECATED_SETTING_TRUE1); if (randomBoolean()) { settings.add(TEST_DEPRECATED_SETTING_TRUE2); } if (useNonDeprecatedSetting) { settings.add(TEST_NOT_DEPRECATED_SETTING); } Collections.shuffle(settings, random()); // trigger all deprecations Response response = getRestClient().performRequest("GET", "/_test_cluster/deprecated_settings", Collections.emptyMap(), buildSettingsRequest(settings, useDeprecatedField)); assertThat(response.getStatusLine().getStatusCode(), equalTo(OK.getStatus())); final List<String> deprecatedWarnings = getWarningHeaders(response.getHeaders()); final List<Matcher<String>> headerMatchers = new ArrayList<>(4); headerMatchers.add(equalTo(TestDeprecationHeaderRestAction.DEPRECATED_ENDPOINT)); if (useDeprecatedField) { headerMatchers.add(equalTo(TestDeprecationHeaderRestAction.DEPRECATED_USAGE)); } for (Setting<?> setting : settings) { if (setting.isDeprecated()) { headerMatchers.add(equalTo( "[" + setting.getKey() + "] setting was deprecated in Elasticsearch and will be removed in a future release! " + "See the breaking changes documentation for the next major version.")); } } assertThat(deprecatedWarnings, hasSize(headerMatchers.size())); for (final String deprecatedWarning : deprecatedWarnings) { assertThat(deprecatedWarning, matches(WARNING_HEADER_PATTERN.pattern())); } final List<String> actualWarningValues = deprecatedWarnings.stream().map(DeprecationLogger::extractWarningValueFromWarningHeader).collect(Collectors.toList()); for (Matcher<String> headerMatcher : headerMatchers) { assertThat(actualWarningValues, hasItem(headerMatcher)); } } private List<String> getWarningHeaders(Header[] headers) { List<String> warnings = new ArrayList<>(); for (Header header : headers) { if (header.getName().equals("Warning")) { warnings.add(header.getValue()); } } return warnings; } private HttpEntity buildSettingsRequest(List<Setting<Boolean>> settings, boolean useDeprecatedField) throws IOException { XContentBuilder builder = JsonXContent.contentBuilder(); builder.startObject().startArray(useDeprecatedField ? "deprecated_settings" : "settings"); for (Setting<Boolean> setting : settings) { builder.value(setting.getKey()); } builder.endArray().endObject(); return new StringEntity(builder.string(), ContentType.APPLICATION_JSON); } }