Browse Source

Reintroduce system index APIs for Kibana (#54858)

This change reintroduces the system index APIs for Kibana without the
changes made for marking what system indices could be accessed using
these APIs. In essence, this is a partial revert of #53912. The changes
for marking what system indices should be allowed access will be
handled in a separate change.

The APIs introduced here are wrapped versions of the existing REST
endpoints. A new setting is also introduced since the Kibana system
indices' names are allowed to be changed by a user in case multiple
instances of Kibana use the same instance of Elasticsearch.

Relates #52385
Jay Modi 5 years ago
parent
commit
57bd6d2c4a
18 changed files with 559 additions and 22 deletions
  1. 31 0
      modules/kibana/build.gradle
  2. 143 0
      modules/kibana/src/main/java/org/elasticsearch/kibana/KibanaPlugin.java
  3. 49 0
      modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaPluginTests.java
  4. 260 0
      modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaSystemIndexIT.java
  5. 2 1
      modules/tasks/src/main/java/org/elasticsearch/tasksplugin/TasksPlugin.java
  6. 2 1
      modules/tasks/src/test/java/org/elasticsearch/tasksplugin/TasksPluginTests.java
  7. 1 1
      server/src/main/java/org/elasticsearch/action/ActionModule.java
  8. 1 1
      server/src/main/java/org/elasticsearch/node/Node.java
  9. 3 1
      server/src/main/java/org/elasticsearch/plugins/SystemIndexPlugin.java
  10. 53 0
      server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java
  11. 6 5
      server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java
  12. 1 6
      server/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionTests.java
  13. 1 1
      x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java
  14. 2 1
      x-pack/plugin/logstash/src/main/java/org/elasticsearch/xpack/logstash/Logstash.java
  15. 1 1
      x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java
  16. 1 1
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java
  17. 1 1
      x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/Transform.java
  18. 1 1
      x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/Watcher.java

+ 31 - 0
modules/kibana/build.gradle

@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+esplugin {
+  description 'Plugin exposing APIs for Kibana system indices'
+  classname 'org.elasticsearch.kibana.KibanaPlugin'
+}
+
+dependencies {
+  compile project(path: ':modules:reindex', configuration: 'runtime')
+}
+
+testClusters.integTest {
+  module file(project(':modules:reindex').tasks.bundlePlugin.archiveFile)
+}

+ 143 - 0
modules/kibana/src/main/java/org/elasticsearch/kibana/KibanaPlugin.java

@@ -0,0 +1,143 @@
+/*
+ * 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.kibana;
+
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.node.DiscoveryNodes;
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.IndexScopedSettings;
+import org.elasticsearch.common.settings.Setting;
+import org.elasticsearch.common.settings.Setting.Property;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.settings.SettingsFilter;
+import org.elasticsearch.index.reindex.RestDeleteByQueryAction;
+import org.elasticsearch.indices.SystemIndexDescriptor;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.plugins.SystemIndexPlugin;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestHandler;
+import org.elasticsearch.rest.action.admin.indices.RestCreateIndexAction;
+import org.elasticsearch.rest.action.admin.indices.RestGetAliasesAction;
+import org.elasticsearch.rest.action.admin.indices.RestGetIndicesAction;
+import org.elasticsearch.rest.action.admin.indices.RestIndexPutAliasAction;
+import org.elasticsearch.rest.action.admin.indices.RestRefreshAction;
+import org.elasticsearch.rest.action.admin.indices.RestUpdateSettingsAction;
+import org.elasticsearch.rest.action.document.RestBulkAction;
+import org.elasticsearch.rest.action.document.RestDeleteAction;
+import org.elasticsearch.rest.action.document.RestGetAction;
+import org.elasticsearch.rest.action.document.RestIndexAction;
+import org.elasticsearch.rest.action.document.RestIndexAction.AutoIdHandler;
+import org.elasticsearch.rest.action.document.RestIndexAction.CreateHandler;
+import org.elasticsearch.rest.action.document.RestMultiGetAction;
+import org.elasticsearch.rest.action.document.RestUpdateAction;
+import org.elasticsearch.rest.action.search.RestClearScrollAction;
+import org.elasticsearch.rest.action.search.RestSearchAction;
+import org.elasticsearch.rest.action.search.RestSearchScrollAction;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class KibanaPlugin extends Plugin implements SystemIndexPlugin {
+
+    public static final Setting<List<String>> KIBANA_INDEX_NAMES_SETTING = Setting.listSetting(
+        "kibana.system_indices",
+        List.of(".kibana*", ".reporting"),
+        Function.identity(),
+        Property.NodeScope
+    );
+
+    @Override
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
+        return KIBANA_INDEX_NAMES_SETTING.get(settings)
+            .stream()
+            .map(pattern -> new SystemIndexDescriptor(pattern, "System index used by kibana"))
+            .collect(Collectors.toUnmodifiableList());
+    }
+
+    @Override
+    public List<RestHandler> getRestHandlers(
+        Settings settings,
+        RestController restController,
+        ClusterSettings clusterSettings,
+        IndexScopedSettings indexScopedSettings,
+        SettingsFilter settingsFilter,
+        IndexNameExpressionResolver indexNameExpressionResolver,
+        Supplier<DiscoveryNodes> nodesInCluster
+    ) {
+        // TODO need to figure out what subset of system indices Kibana should have access to via these APIs
+        return List.of(
+            // Based on https://github.com/elastic/kibana/issues/49764
+            // apis needed to perform migrations... ideally these will go away
+            new KibanaWrappedRestHandler(new RestCreateIndexAction()),
+            new KibanaWrappedRestHandler(new RestGetAliasesAction()),
+            new KibanaWrappedRestHandler(new RestIndexPutAliasAction()),
+            new KibanaWrappedRestHandler(new RestRefreshAction()),
+
+            // apis needed to access saved objects
+            new KibanaWrappedRestHandler(new RestGetAction()),
+            new KibanaWrappedRestHandler(new RestMultiGetAction(settings)),
+            new KibanaWrappedRestHandler(new RestSearchAction()),
+            new KibanaWrappedRestHandler(new RestBulkAction(settings)),
+            new KibanaWrappedRestHandler(new RestDeleteAction()),
+            new KibanaWrappedRestHandler(new RestDeleteByQueryAction()),
+
+            // api used for testing
+            new KibanaWrappedRestHandler(new RestUpdateSettingsAction()),
+
+            // apis used specifically by reporting
+            new KibanaWrappedRestHandler(new RestGetIndicesAction()),
+            new KibanaWrappedRestHandler(new RestIndexAction()),
+            new KibanaWrappedRestHandler(new CreateHandler()),
+            new KibanaWrappedRestHandler(new AutoIdHandler(nodesInCluster)),
+            new KibanaWrappedRestHandler(new RestUpdateAction()),
+            new KibanaWrappedRestHandler(new RestSearchScrollAction()),
+            new KibanaWrappedRestHandler(new RestClearScrollAction())
+        );
+
+    }
+
+    @Override
+    public List<Setting<?>> getSettings() {
+        return List.of(KIBANA_INDEX_NAMES_SETTING);
+    }
+
+    static class KibanaWrappedRestHandler extends BaseRestHandler.Wrapper {
+
+        KibanaWrappedRestHandler(BaseRestHandler delegate) {
+            super(delegate);
+        }
+
+        @Override
+        public String getName() {
+            return "kibana_" + super.getName();
+        }
+
+        @Override
+        public List<Route> routes() {
+            return super.routes().stream()
+                .map(route -> new Route(route.getMethod(), "/_kibana" + route.getPath()))
+                .collect(Collectors.toUnmodifiableList());
+        }
+    }
+}

+ 49 - 0
modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaPluginTests.java

@@ -0,0 +1,49 @@
+/*
+ * 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.kibana;
+
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.indices.SystemIndexDescriptor;
+import org.elasticsearch.test.ESTestCase;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+
+public class KibanaPluginTests extends ESTestCase {
+
+    public void testKibanaIndexNames() {
+        assertThat(new KibanaPlugin().getSettings(), contains(KibanaPlugin.KIBANA_INDEX_NAMES_SETTING));
+        assertThat(
+            new KibanaPlugin().getSystemIndexDescriptors(Settings.EMPTY)
+                .stream()
+                .map(SystemIndexDescriptor::getIndexPattern)
+                .collect(Collectors.toUnmodifiableList()),
+            contains(".kibana*", ".reporting")
+        );
+        final List<String> names = List.of("." + randomAlphaOfLength(4), "." + randomAlphaOfLength(6));
+        final List<String> namesFromDescriptors = new KibanaPlugin().getSystemIndexDescriptors(
+            Settings.builder().putList(KibanaPlugin.KIBANA_INDEX_NAMES_SETTING.getKey(), names).build()
+        ).stream().map(SystemIndexDescriptor::getIndexPattern).collect(Collectors.toUnmodifiableList());
+        assertThat(namesFromDescriptors, is(names));
+    }
+}

+ 260 - 0
modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaSystemIndexIT.java

@@ -0,0 +1,260 @@
+/*
+ * 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.kibana;
+
+import org.apache.http.util.EntityUtils;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.elasticsearch.test.rest.ESRestTestCase;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+public class KibanaSystemIndexIT extends ESRestTestCase {
+
+    public void testCreateIndex() throws IOException {
+        Request request = new Request("PUT", "/_kibana/.kibana-1");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+    }
+
+    public void testAliases() throws IOException {
+        Request request = new Request("PUT", "/_kibana/.kibana-1");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        request = new Request("PUT", "/_kibana/.kibana-1/_alias/.kibana");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        request = new Request("GET", "/_kibana/_aliases");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+        assertThat(EntityUtils.toString(response.getEntity()), containsString(".kibana"));
+    }
+
+    public void testBulkToKibanaIndex() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity("{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+    }
+
+    public void testRefresh() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity("{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        request = new Request("GET", "/_kibana/.kibana/_refresh");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        Request getRequest = new Request("GET", "/_kibana/.kibana/_doc/1");
+        Response getResponse = client().performRequest(getRequest);
+        assertThat(getResponse.getStatusLine().getStatusCode(), is(200));
+        String responseBody = EntityUtils.toString(getResponse.getEntity());
+        assertThat(responseBody, containsString("foo"));
+        assertThat(responseBody, containsString("bar"));
+    }
+
+    public void testGetFromKibanaIndex() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity("{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n");
+        request.addParameter("refresh", "true");
+
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        Request getRequest = new Request("GET", "/_kibana/.kibana/_doc/1");
+        Response getResponse = client().performRequest(getRequest);
+        assertThat(getResponse.getStatusLine().getStatusCode(), is(200));
+        String responseBody = EntityUtils.toString(getResponse.getEntity());
+        assertThat(responseBody, containsString("foo"));
+        assertThat(responseBody, containsString("bar"));
+    }
+
+    public void testMultiGetFromKibanaIndex() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity(
+            "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n"
+                + "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"2\" } }\n{ \"baz\" : \"tag\" }\n"
+        );
+        request.addParameter("refresh", "true");
+
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        Request getRequest = new Request("GET", "/_kibana/_mget");
+        getRequest.setJsonEntity(
+            "{ \"docs\" : [ { \"_index\" : \".kibana\", \"_id\" : \"1\" }, " + "{ \"_index\" : \".kibana\", \"_id\" : \"2\" } ] }\n"
+        );
+        Response getResponse = client().performRequest(getRequest);
+        assertThat(getResponse.getStatusLine().getStatusCode(), is(200));
+        String responseBody = EntityUtils.toString(getResponse.getEntity());
+        assertThat(responseBody, containsString("foo"));
+        assertThat(responseBody, containsString("bar"));
+        assertThat(responseBody, containsString("baz"));
+        assertThat(responseBody, containsString("tag"));
+    }
+
+    public void testSearchFromKibanaIndex() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity(
+            "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n"
+                + "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"2\" } }\n{ \"baz\" : \"tag\" }\n"
+        );
+        request.addParameter("refresh", "true");
+
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        Request searchRequest = new Request("GET", "/_kibana/.kibana/_search");
+        searchRequest.setJsonEntity("{ \"query\" : { \"match_all\" : {} } }\n");
+        Response getResponse = client().performRequest(searchRequest);
+        assertThat(getResponse.getStatusLine().getStatusCode(), is(200));
+        String responseBody = EntityUtils.toString(getResponse.getEntity());
+        assertThat(responseBody, containsString("foo"));
+        assertThat(responseBody, containsString("bar"));
+        assertThat(responseBody, containsString("baz"));
+        assertThat(responseBody, containsString("tag"));
+    }
+
+    public void testDeleteFromKibanaIndex() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity(
+            "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n"
+                + "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"2\" } }\n{ \"baz\" : \"tag\" }\n"
+        );
+        request.addParameter("refresh", "true");
+
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        Request deleteRequest = new Request("DELETE", "/_kibana/.kibana/_doc/1");
+        Response deleteResponse = client().performRequest(deleteRequest);
+        assertThat(deleteResponse.getStatusLine().getStatusCode(), is(200));
+    }
+
+    public void testDeleteByQueryFromKibanaIndex() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity(
+            "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n"
+                + "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"2\" } }\n{ \"baz\" : \"tag\" }\n"
+        );
+        request.addParameter("refresh", "true");
+
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        Request dbqRequest = new Request("POST", "/_kibana/.kibana/_delete_by_query");
+        dbqRequest.setJsonEntity("{ \"query\" : { \"match_all\" : {} } }\n");
+        Response dbqResponse = client().performRequest(dbqRequest);
+        assertThat(dbqResponse.getStatusLine().getStatusCode(), is(200));
+    }
+
+    public void testUpdateIndexSettings() throws IOException {
+        Request request = new Request("PUT", "/_kibana/.kibana-1");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        request = new Request("PUT", "/_kibana/.kibana-1/_settings");
+        request.setJsonEntity("{ \"index.blocks.read_only\" : false }");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+    }
+
+    public void testGetIndex() throws IOException {
+        Request request = new Request("PUT", "/_kibana/.kibana-1");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        request = new Request("GET", "/_kibana/.kibana-1");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+        assertThat(EntityUtils.toString(response.getEntity()), containsString(".kibana-1"));
+    }
+
+    public void testIndexingAndUpdatingDocs() throws IOException {
+        Request request = new Request("PUT", "/_kibana/.kibana-1/_doc/1");
+        request.setJsonEntity("{ \"foo\" : \"bar\" }");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(201));
+
+        request = new Request("PUT", "/_kibana/.kibana-1/_create/2");
+        request.setJsonEntity("{ \"foo\" : \"bar\" }");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(201));
+
+        request = new Request("POST", "/_kibana/.kibana-1/_doc");
+        request.setJsonEntity("{ \"foo\" : \"bar\" }");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(201));
+
+        request = new Request("GET", "/_kibana/.kibana-1/_refresh");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        request = new Request("POST", "/_kibana/.kibana-1/_update/1");
+        request.setJsonEntity("{ \"doc\" : { \"foo\" : \"baz\" } }");
+        response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+    }
+
+    public void testScrollingDocs() throws IOException {
+        Request request = new Request("POST", "/_kibana/_bulk");
+        request.setJsonEntity(
+            "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"1\" } }\n{ \"foo\" : \"bar\" }\n"
+                + "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"2\" } }\n{ \"baz\" : \"tag\" }\n"
+                + "{ \"index\" : { \"_index\" : \".kibana\", \"_id\" : \"3\" } }\n{ \"baz\" : \"tag\" }\n"
+        );
+        request.addParameter("refresh", "true");
+        Response response = client().performRequest(request);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+
+        Request searchRequest = new Request("GET", "/_kibana/.kibana/_search");
+        searchRequest.setJsonEntity("{ \"size\" : 1,\n\"query\" : { \"match_all\" : {} } }\n");
+        searchRequest.addParameter("scroll", "1m");
+        response = client().performRequest(searchRequest);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+        Map<String, Object> map = XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
+        assertNotNull(map.get("_scroll_id"));
+        String scrollId = (String) map.get("_scroll_id");
+
+        Request scrollRequest = new Request("POST", "/_kibana/_search/scroll");
+        scrollRequest.addParameter("scroll_id", scrollId);
+        scrollRequest.addParameter("scroll", "1m");
+        response = client().performRequest(scrollRequest);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+        map = XContentHelper.convertToMap(JsonXContent.jsonXContent, EntityUtils.toString(response.getEntity()), false);
+        assertNotNull(map.get("_scroll_id"));
+        scrollId = (String) map.get("_scroll_id");
+
+        Request clearScrollRequest = new Request("DELETE", "/_kibana/_search/scroll");
+        clearScrollRequest.addParameter("scroll_id", scrollId);
+        response = client().performRequest(clearScrollRequest);
+        assertThat(response.getStatusLine().getStatusCode(), is(200));
+    }
+}

+ 2 - 1
modules/tasks/src/main/java/org/elasticsearch/tasksplugin/TasksPlugin.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.tasksplugin;
 
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.indices.SystemIndexDescriptor;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.plugins.SystemIndexPlugin;
@@ -34,7 +35,7 @@ import static org.elasticsearch.tasks.TaskResultsService.TASK_INDEX;
 public class TasksPlugin extends Plugin implements SystemIndexPlugin {
 
     @Override
-    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return Collections.singletonList(new SystemIndexDescriptor(TASK_INDEX, this.getClass().getSimpleName()));
     }
 }

+ 2 - 1
modules/tasks/src/test/java/org/elasticsearch/tasksplugin/TasksPluginTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.tasksplugin;
 
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.test.ESTestCase;
 import org.hamcrest.Matchers;
 
@@ -27,6 +28,6 @@ public class TasksPluginTests extends ESTestCase {
     public void testDummy() {
         // This is a dummy test case to satisfy the conventions
         TasksPlugin plugin = new TasksPlugin();
-        assertThat(plugin.getSystemIndexDescriptors(), Matchers.hasSize(1));
+        assertThat(plugin.getSystemIndexDescriptors(Settings.EMPTY), Matchers.hasSize(1));
     }
 }

+ 1 - 1
server/src/main/java/org/elasticsearch/action/ActionModule.java

@@ -716,7 +716,7 @@ public class ActionModule extends AbstractModule {
 
         registerHandler.accept(new RestIndexAction());
         registerHandler.accept(new CreateHandler());
-        registerHandler.accept(new AutoIdHandler(clusterService));
+        registerHandler.accept(new AutoIdHandler(nodesInCluster));
         registerHandler.accept(new RestGetAction());
         registerHandler.accept(new RestGetSourceAction());
         registerHandler.accept(new RestMultiGetAction(settings));

+ 1 - 1
server/src/main/java/org/elasticsearch/node/Node.java

@@ -434,7 +434,7 @@ public class Node implements Closeable {
                 .stream()
                 .collect(Collectors.toUnmodifiableMap(
                     plugin -> plugin.getClass().getSimpleName(),
-                    plugin -> plugin.getSystemIndexDescriptors()));
+                    plugin -> plugin.getSystemIndexDescriptors(settings)));
             SystemIndexDescriptor.checkForOverlappingPatterns(systemIndexDescriptorMap);
 
             final List<SystemIndexDescriptor> systemIndexDescriptors = systemIndexDescriptorMap.values().stream()

+ 3 - 1
server/src/main/java/org/elasticsearch/plugins/SystemIndexPlugin.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.plugins;
 
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.indices.SystemIndexDescriptor;
 
 import java.util.Collection;
@@ -33,9 +34,10 @@ public interface SystemIndexPlugin extends ActionPlugin {
     /**
      * Returns a {@link Collection} of {@link SystemIndexDescriptor}s that describe this plugin's system indices, including
      * name, mapping, and settings.
+     * @param settings The node's settings
      * @return Descriptions of the system indices managed by this plugin.
      */
-    default Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    default Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return Collections.emptyList();
     }
 }

+ 53 - 0
server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java

@@ -183,4 +183,57 @@ public abstract class BaseRestHandler implements RestHandler {
         return Collections.emptySet();
     }
 
+    public static class Wrapper extends BaseRestHandler {
+
+        protected final BaseRestHandler delegate;
+
+        public Wrapper(BaseRestHandler delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public String getName() {
+            return delegate.getName();
+        }
+
+        @Override
+        public List<Route> routes() {
+            return delegate.routes();
+        }
+
+        @Override
+        public List<DeprecatedRoute> deprecatedRoutes() {
+            return delegate.deprecatedRoutes();
+        }
+
+        @Override
+        public List<ReplacedRoute> replacedRoutes() {
+            return delegate.replacedRoutes();
+        }
+
+        @Override
+        protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
+            return delegate.prepareRequest(request, client);
+        }
+
+        @Override
+        protected Set<String> responseParams() {
+            return delegate.responseParams();
+        }
+
+        @Override
+        public boolean canTripCircuitBreaker() {
+            return delegate.canTripCircuitBreaker();
+        }
+
+        @Override
+        public boolean supportsContentStream() {
+            return delegate.supportsContentStream();
+        }
+
+        @Override
+        public boolean allowsUnsafeBuffers() {
+            return delegate.allowsUnsafeBuffers();
+        }
+    }
 }

+ 6 - 5
server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java

@@ -23,7 +23,7 @@ import org.elasticsearch.Version;
 import org.elasticsearch.action.index.IndexRequest;
 import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.node.NodeClient;
-import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.cluster.node.DiscoveryNodes;
 import org.elasticsearch.index.VersionType;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
@@ -33,6 +33,7 @@ import org.elasticsearch.rest.action.RestStatusToXContentListener;
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
+import java.util.function.Supplier;
 
 import static org.elasticsearch.rest.RestRequest.Method.POST;
 import static org.elasticsearch.rest.RestRequest.Method.PUT;
@@ -81,10 +82,10 @@ public class RestIndexAction extends BaseRestHandler {
 
     public static final class AutoIdHandler extends RestIndexAction {
 
-        private final ClusterService clusterService;
+        private final Supplier<DiscoveryNodes> nodesInCluster;
 
-        public AutoIdHandler(ClusterService clusterService) {
-            this.clusterService = clusterService;
+        public AutoIdHandler(Supplier<DiscoveryNodes> nodesInCluster) {
+            this.nodesInCluster = nodesInCluster;
         }
 
         @Override
@@ -100,7 +101,7 @@ public class RestIndexAction extends BaseRestHandler {
         @Override
         public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException {
             assert request.params().get("id") == null : "non-null id: " + request.params().get("id");
-            if (request.params().get("op_type") == null && clusterService.state().nodes().getMinNodeVersion().onOrAfter(Version.V_7_5_0)) {
+            if (request.params().get("op_type") == null && nodesInCluster.get().getMinNodeVersion().onOrAfter(Version.V_7_5_0)) {
                 // default to op_type create
                 request.params().put("op_type", "create");
             }

+ 1 - 6
server/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionTests.java

@@ -27,7 +27,6 @@ import org.elasticsearch.cluster.ClusterName;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.node.DiscoveryNode;
 import org.elasticsearch.cluster.node.DiscoveryNodes;
-import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.rest.RestRequest;
@@ -43,9 +42,7 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 public class RestIndexActionTests extends RestActionTestCase {
 
@@ -53,11 +50,9 @@ public class RestIndexActionTests extends RestActionTestCase {
 
     @Before
     public void setUpAction() {
-        ClusterService clusterService = mock(ClusterService.class);
-        when(clusterService.state()).thenAnswer(invocationOnMock -> clusterStateSupplier.get());
         controller().registerHandler(new RestIndexAction());
         controller().registerHandler(new CreateHandler());
-        controller().registerHandler(new AutoIdHandler(clusterService));
+        controller().registerHandler(new AutoIdHandler(() -> clusterStateSupplier.get().nodes()));
     }
 
     public void testCreateOpTypeValidation() {

+ 1 - 1
x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java

@@ -246,7 +246,7 @@ public class EnrichPlugin extends Plugin implements SystemIndexPlugin, IngestPlu
     }
 
     @Override
-    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return Collections.singletonList(
             new SystemIndexDescriptor(ENRICH_INDEX_PATTERN, "Contains data to support enrich ingest processors.")
         );

+ 2 - 1
x-pack/plugin/logstash/src/main/java/org/elasticsearch/xpack/logstash/Logstash.java

@@ -10,6 +10,7 @@ import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequest;
 import org.elasticsearch.action.ActionResponse;
 import org.elasticsearch.cluster.metadata.IndexTemplateMetadata;
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.indices.SystemIndexDescriptor;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.plugins.SystemIndexPlugin;
@@ -62,7 +63,7 @@ public class Logstash extends Plugin implements SystemIndexPlugin {
     }
 
     @Override
-    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return Collections.singletonList(
             new SystemIndexDescriptor(LOGSTASH_CONCRETE_INDEX_NAME, "Contains data for Logstash Central Management")
         );

+ 1 - 1
x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java

@@ -992,7 +992,7 @@ public class MachineLearning extends Plugin implements SystemIndexPlugin, Analys
     }
 
     @Override
-    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return List.of(
             new SystemIndexDescriptor(MlMetaIndex.INDEX_NAME, "Contains scheduling and anomaly tracking metadata"),
             new SystemIndexDescriptor(AnomalyDetectorsIndexFields.CONFIG_INDEX, "Contains ML configuration data"),

+ 1 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

@@ -1087,7 +1087,7 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
     }
 
     @Override
-    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return List.of(
             new SystemIndexDescriptor(SECURITY_MAIN_ALIAS, "Contains Security configuration"),
             new SystemIndexDescriptor(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, "Contains Security configuration"),

+ 1 - 1
x-pack/plugin/transform/src/main/java/org/elasticsearch/xpack/transform/Transform.java

@@ -389,7 +389,7 @@ public class Transform extends Plugin implements SystemIndexPlugin, PersistentTa
     }
 
     @Override
-    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return Collections.singletonList(
             new SystemIndexDescriptor(TransformInternalIndexConstants.INDEX_NAME_PATTERN, "Contains Transform configuration data")
         );

+ 1 - 1
x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/Watcher.java

@@ -690,7 +690,7 @@ public class Watcher extends Plugin implements SystemIndexPlugin, ScriptPlugin,
     }
 
     @Override
-    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
+    public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
         return List.of(
             new SystemIndexDescriptor(Watch.INDEX, "Contains Watch definitions"),
             new SystemIndexDescriptor(TriggeredWatchStoreField.INDEX_NAME, "Used to track current and queued Watch execution")