blob: bfa9eaef092645fb84aa74ebea16d5ec77276dfb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.handler.admin;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.api.Api;
import org.apache.solr.api.ApiBag;
import org.apache.solr.api.Command;
import org.apache.solr.api.EndPoint;
import org.apache.solr.api.PayloadObj;
import org.apache.solr.api.V2HttpCall;
import org.apache.solr.api.V2HttpCall.CompositeApi;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.common.annotation.JsonProperty;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.JsonSchemaValidator;
import org.apache.solr.common.util.PathTrie;
import org.apache.solr.common.util.ReflectMapWriter;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.apache.solr.common.util.ValidatingJsonMap;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.PluginBag;
import org.apache.solr.handler.CollectionsAPI;
import org.apache.solr.handler.PingRequestHandler;
import org.apache.solr.handler.SchemaHandler;
import org.apache.solr.handler.SolrConfigHandler;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryRequestBase;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.PermissionNameProvider;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.solr.api.ApiBag.EMPTY_SPEC;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH;
import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH;
import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH;
import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
public class TestApiFramework extends SolrTestCaseJ4 {
public void testFramework() {
Map<String, Object[]> calls = new HashMap<>();
Map<String, Object> out = new HashMap<>();
CoreContainer mockCC = TestCoreAdminApis.getCoreContainerMock(calls, out);
PluginBag<SolrRequestHandler> containerHandlers = new PluginBag<>(SolrRequestHandler.class, null, false);
TestCollectionAPIs.MockCollectionsHandler collectionsHandler = new TestCollectionAPIs.MockCollectionsHandler();
containerHandlers.put(COLLECTIONS_HANDLER_PATH, collectionsHandler);
containerHandlers.getApiBag().registerObject(new CollectionsAPI(collectionsHandler));
containerHandlers.put(CORES_HANDLER_PATH, new CoreAdminHandler(mockCC));
containerHandlers.put(CONFIGSETS_HANDLER_PATH, new ConfigSetsHandler(mockCC));
out.put("getRequestHandlers", containerHandlers);
PluginBag<SolrRequestHandler> coreHandlers = new PluginBag<>(SolrRequestHandler.class, null, false);
coreHandlers.put("/schema", new SchemaHandler());
coreHandlers.put("/config", new SolrConfigHandler());
coreHandlers.put("/admin/ping", new PingRequestHandler());
Map<String, String> parts = new HashMap<>();
String fullPath = "/collections/hello/shards";
Api api = V2HttpCall.getApiInfo(containerHandlers, fullPath, "POST",
fullPath, parts);
assertNotNull(api);
assertConditions(api.getSpec(), Utils.makeMap(
"/methods[0]", "POST",
"/commands/create", NOT_NULL));
assertEquals("hello", parts.get("collection"));
parts = new HashMap<>();
api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards", "POST",
null, parts);
assertConditions(api.getSpec(), Utils.makeMap(
"/methods[0]", "POST",
"/commands/split", NOT_NULL,
"/commands/add-replica", NOT_NULL
));
parts = new HashMap<>();
api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards/shard1", "POST",
null, parts);
assertConditions(api.getSpec(), Utils.makeMap(
"/methods[0]", "POST",
"/commands/force-leader", NOT_NULL
));
assertEquals("hello", parts.get("collection"));
assertEquals("shard1", parts.get("shard"));
parts = new HashMap<>();
api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello", "POST",
null, parts);
assertConditions(api.getSpec(), Utils.makeMap(
"/methods[0]", "POST",
"/commands/add-replica-property", NOT_NULL,
"/commands/delete-replica-property", NOT_NULL
));
assertEquals("hello", parts.get("collection"));
api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards/shard1/replica1", "DELETE",
null, parts);
assertConditions(api.getSpec(), Utils.makeMap(
"/methods[0]", "DELETE",
"/url/params/onlyIfDown/type", "boolean"
));
assertEquals("hello", parts.get("collection"));
assertEquals("shard1", parts.get("shard"));
assertEquals("replica1", parts.get("replica"));
SolrQueryResponse rsp = invoke(containerHandlers, null, "/collections/_introspect", GET, mockCC);
Set<String> methodNames = new HashSet<>();
methodNames.add(rsp.getValues()._getStr("/spec[0]/methods[0]", null));
methodNames.add(rsp.getValues()._getStr("/spec[1]/methods[0]", null));
methodNames.add(rsp.getValues()._getStr("/spec[2]/methods[0]", null));
assertTrue(methodNames.contains("DELETE"));
assertTrue(methodNames.contains("POST"));
assertTrue(methodNames.contains("GET"));
methodNames = new HashSet<>();
rsp = invoke(coreHandlers, "/schema/_introspect", "/collections/hello/schema/_introspect", GET, mockCC);
methodNames.add(rsp.getValues()._getStr("/spec[0]/methods[0]", null));
methodNames.add(rsp.getValues()._getStr("/spec[1]/methods[0]", null));
assertTrue(methodNames.contains("POST"));
assertTrue(methodNames.contains("GET"));
rsp = invoke(coreHandlers, "/", "/collections/hello/_introspect", GET, mockCC);
assertConditions(rsp.getValues().asMap(2), Utils.makeMap(
"/availableSubPaths", NOT_NULL,
"availableSubPaths /collections/hello/config/jmx", NOT_NULL,
"availableSubPaths /collections/hello/schema", NOT_NULL,
"availableSubPaths /collections/hello/shards", NOT_NULL,
"availableSubPaths /collections/hello/shards/{shard}", NOT_NULL,
"availableSubPaths /collections/hello/shards/{shard}/{replica}", NOT_NULL
));
}
public void testPayload() throws IOException {
String json = "{package:pkg1, version: '0.1', files :[a.jar, b.jar]}";
Utils.fromJSONString(json);
ApiBag apiBag = new ApiBag(false);
List<Api> apis = apiBag.registerObject(new ApiTest());
ValidatingJsonMap spec = apis.get(0).getSpec();
assertEquals("POST", spec._getStr("/methods[0]",null) );
assertEquals("POST", spec._getStr("/methods[0]",null) );
assertEquals("/cluster/package", spec._getStr("/url/paths[0]",null) );
assertEquals("string", spec._getStr("/commands/add/properties/package/type",null) );
assertEquals("array", spec._getStr("/commands/add/properties/files/type",null) );
assertEquals("string", spec._getStr("/commands/add/properties/files/items/type",null) );
assertEquals("string", spec._getStr("/commands/delete/items/type",null) );
SolrQueryResponse rsp = v2ApiInvoke(apiBag, "/cluster/package", "POST", new ModifiableSolrParams(),
new ByteArrayInputStream("{add:{package:mypkg, version: '1.0', files : [a.jar, b.jar]}}".getBytes(UTF_8)));
AddVersion addversion = (AddVersion) rsp.getValues().get("add");
assertEquals("mypkg", addversion.pkg);
assertEquals("1.0", addversion.version);
assertEquals("a.jar", addversion.files.get(0));
assertEquals("b.jar", addversion.files.get(1));
apiBag.registerObject(new C());
rsp = v2ApiInvoke(apiBag, "/path1", "POST", new ModifiableSolrParams(),
new ByteArrayInputStream("{\"package\":\"mypkg\", \"version\": \"1.0\", \"files\" : [\"a.jar\", \"b.jar\"]}".getBytes(UTF_8)));
assertEquals("mypkg", rsp.getValues()._getStr("payload/package", null));
assertEquals("1.0", rsp.getValues()._getStr("payload/version", null));
}
public static class C {
@EndPoint(path = "/path1", method = POST, permission = PermissionNameProvider.Name.ALL)
public void m1(PayloadObj<AddVersion> add) {
add.getResponse().add("payload",add.get());
}
}
@EndPoint(method = POST, path = "/cluster/package", permission = PermissionNameProvider.Name.ALL)
public static class ApiTest {
@Command(name = "add")
public void add(SolrQueryRequest req, SolrQueryResponse rsp, AddVersion addVersion) {
rsp.add("add", addVersion);
}
@Command(name = "delete")
public void del(SolrQueryRequest req, SolrQueryResponse rsp, List<String> names) {
rsp.add("delete",names);
}
}
public static class AddVersion implements ReflectMapWriter {
@JsonProperty(value = "package", required = true)
public String pkg;
@JsonProperty(value = "version", required = true)
public String version;
@JsonProperty(value = "files", required = true)
public List<String> files;
}
public void testAnnotatedApi() {
ApiBag apiBag = new ApiBag(false);
apiBag.registerObject(new DummyTest());
SolrQueryResponse rsp = v2ApiInvoke(apiBag, "/node/filestore/package/mypkg/jar1.jar", "GET",
new ModifiableSolrParams(), null);
assertEquals("/package/mypkg/jar1.jar", rsp.getValues().get("path"));
apiBag = new ApiBag(false);
apiBag.registerObject(new DummyTest1());
rsp = v2ApiInvoke(apiBag, "/node/filestore/package/mypkg/jar1.jar", "GET",
new ModifiableSolrParams(), null);
assertEquals("/package/mypkg/jar1.jar", rsp.getValues().get("path"));
}
@EndPoint(
path = "/node/filestore/*",
method = SolrRequest.METHOD.GET,
permission = PermissionNameProvider.Name.ALL)
public class DummyTest {
@Command
public void read(SolrQueryRequest req, SolrQueryResponse rsp) {
rsp.add("FSRead.called", "true");
rsp.add("path", req.getPathTemplateValues().get("*"));
}
}
public class DummyTest1 {
@EndPoint(
path = "/node/filestore/*",
method = SolrRequest.METHOD.GET,
permission = PermissionNameProvider.Name.ALL)
public void read(SolrQueryRequest req, SolrQueryResponse rsp) {
rsp.add("FSRead.called", "true");
rsp.add("path", req.getPathTemplateValues().get("*"));
}
}
private static SolrQueryResponse v2ApiInvoke(ApiBag bag, String uri, String method, SolrParams params, InputStream payload) {
if (params == null) params = new ModifiableSolrParams();
SolrQueryResponse rsp = new SolrQueryResponse();
HashMap<String, String> templateVals = new HashMap<>();
Api[] currentApi = new Api[1];
SolrQueryRequestBase req = new SolrQueryRequestBase(null, params) {
@Override
public Map<String, String> getPathTemplateValues() {
return templateVals;
}
@Override
protected Map<String, JsonSchemaValidator> getValidators() {
return currentApi[0] == null?
Collections.emptyMap():
currentApi[0].getCommandSchema();
}
@Override
public Iterable<ContentStream> getContentStreams() {
return Collections.singletonList(new ContentStreamBase() {
@Override
public InputStream getStream() throws IOException {
return payload;
}
});
}
};
Api api = bag.lookup(uri, method, templateVals);
currentApi[0] = api;
api.call(req, rsp);
return rsp;
}
public void testTrailingTemplatePaths() {
PathTrie<Api> registry = new PathTrie<>();
Api api = new Api(EMPTY_SPEC) {
@Override
public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
}
};
Api intropsect = new ApiBag.IntrospectApi(api, false);
ApiBag.registerIntrospect(Collections.emptyMap(), registry, "/c/.system/blob/{name}", intropsect);
ApiBag.registerIntrospect(Collections.emptyMap(), registry, "/c/.system/{x}/{name}", intropsect);
assertEquals(intropsect, registry.lookup("/c/.system/blob/random_string/_introspect", new HashMap<>()));
assertEquals(intropsect, registry.lookup("/c/.system/blob/_introspect", new HashMap<>()));
assertEquals(intropsect, registry.lookup("/c/.system/_introspect", new HashMap<>()));
assertEquals(intropsect, registry.lookup("/c/.system/v1/_introspect", new HashMap<>()));
assertEquals(intropsect, registry.lookup("/c/.system/v1/v2/_introspect", new HashMap<>()));
}
private SolrQueryResponse invoke(PluginBag<SolrRequestHandler> reqHandlers, String path,
String fullPath, SolrRequest.METHOD method,
CoreContainer mockCC) {
HashMap<String, String> parts = new HashMap<>();
boolean containerHandlerLookup = mockCC.getRequestHandlers() == reqHandlers;
path = path == null ? fullPath : path;
Api api = null;
if (containerHandlerLookup) {
api = V2HttpCall.getApiInfo(reqHandlers, path, "GET", fullPath, parts);
} else {
api = V2HttpCall.getApiInfo(mockCC.getRequestHandlers(), fullPath, "GET", fullPath, parts);
if (api == null) api = new CompositeApi(null);
if (api instanceof CompositeApi) {
CompositeApi compositeApi = (CompositeApi) api;
api = V2HttpCall.getApiInfo(reqHandlers, path, "GET", fullPath, parts);
compositeApi.add(api);
api = compositeApi;
}
}
SolrQueryResponse rsp = new SolrQueryResponse();
LocalSolrQueryRequest req = new LocalSolrQueryRequest(null, new MapSolrParams(new HashMap<>())) {
@Override
public List<CommandOperation> getCommands(boolean validateInput) {
return Collections.emptyList();
}
};
api.call(req, rsp);
return rsp;
}
public static void assertConditions(@SuppressWarnings({"rawtypes"})Map root,
@SuppressWarnings({"rawtypes"})Map conditions) {
for (Object o : conditions.entrySet()) {
@SuppressWarnings({"rawtypes"})
Map.Entry e = (Map.Entry) o;
String path = (String) e.getKey();
List<String> parts = StrUtils.splitSmart(path, path.charAt(0) == '/' ? '/' : ' ', true);
Object val = Utils.getObjectByPath(root, false, parts);
if (e.getValue() instanceof ValidatingJsonMap.PredicateWithErrMsg) {
@SuppressWarnings({"rawtypes"})
ValidatingJsonMap.PredicateWithErrMsg value = (ValidatingJsonMap.PredicateWithErrMsg) e.getValue();
@SuppressWarnings({"unchecked"})
String err = value.test(val);
if (err != null) {
assertEquals(err + " for " + e.getKey() + " in :" + Utils.toJSONString(root), e.getValue(), val);
}
} else {
assertEquals("incorrect value for path " + e.getKey() + " in :" + Utils.toJSONString(root), e.getValue(), val);
}
}
}
}