Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOLR-16396: Convert v2 configset APIs to JAX-RS #2928

Merged
merged 10 commits into from
Jan 3, 2025
4 changes: 4 additions & 0 deletions solr/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ Improvements
specific collections or cores. Collection information can be fetched by a call to `GET /api/collections/collectionName`, and core
information with a call to `GET /api/cores/coreName/segments`. (Jason Gerlowski)

* SOLR-16396: All v2 configset APIs have been moved to the slightly different path: `/api/configsets`, to better align with the design of
other v2 APIs. SolrJ now offers (experimental) SolrRequest implementations for all v2 configset APIs in
`org.apache.solr.client.solrj.request.ConfigsetsApi`. (Jason Gerlowski)

Optimizations
---------------------
* SOLR-17578: Remove ZkController internal core supplier, for slightly faster reconnection after Zookeeper session loss. (Pierre Salagnac)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.client.api.endpoint;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import java.io.IOException;
import java.io.InputStream;
import org.apache.solr.client.api.model.CloneConfigsetRequestBody;
import org.apache.solr.client.api.model.ListConfigsetsResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;

public interface ConfigsetsApi {

/** V2 API definition for listing the configsets available to this SolrCloud cluster. */
@Path("/configsets")
interface List {
@GET
@Operation(
summary = "List the configsets available to Solr.",
tags = {"configsets"})
ListConfigsetsResponse listConfigSet() throws Exception;
}

/**
* V2 API definition for creating a (possibly slightly modified) copy of an existing configset
*
* <p>Equivalent to the existing v1 API /admin/configs?action=CREATE
*/
@Path("/configsets")
interface Clone {
@POST
@Operation(
summary = "Create a new configset modeled on an existing one.",
tags = {"configsets"})
SolrJerseyResponse cloneExistingConfigSet(CloneConfigsetRequestBody requestBody)
throws Exception;
}

/**
* V2 API definition for deleting an existing configset.
*
* <p>Equivalent to the existing v1 API /admin/configs?action=DELETE
*/
@Path("/configsets/{configSetName}")
interface Delete {
@DELETE
@Operation(summary = "Delete an existing configset.", tags = "configsets")
SolrJerseyResponse deleteConfigSet(@PathParam("configSetName") String configSetName)
throws Exception;
}

/**
* V2 API definitions for uploading a configset, in whole or part.
*
* <p>Equivalent to the existing v1 API /admin/configs?action=UPLOAD
*/
@Path("/configsets/{configSetName}")
interface Upload {
@PUT
@Operation(summary = "Create a new configset.", tags = "configsets")
SolrJerseyResponse uploadConfigSet(
@PathParam("configSetName") String configSetName,
@QueryParam("overwrite") Boolean overwrite,
@QueryParam("cleanup") Boolean cleanup,
@RequestBody(required = true) InputStream requestBody)
throws IOException;

@PUT
@Path("{filePath:.+}")
SolrJerseyResponse uploadConfigSetFile(
@PathParam("configSetName") String configSetName,
@PathParam("filePath") String filePath,
@QueryParam("overwrite") Boolean overwrite,
@QueryParam("cleanup") Boolean cleanup,
@RequestBody(required = true) InputStream requestBody)
throws IOException;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.client.solrj.request.beans;
package org.apache.solr.client.api.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
import org.apache.solr.common.annotation.JsonProperty;
import org.apache.solr.common.util.ReflectMapWriter;

public class CreateConfigPayload implements ReflectMapWriter {
public static final String DEFAULT_CONFIGSET =
"_default"; // TODO Better location for this in SolrJ?
/** Request body for ConfigsetsApi.Clone */
public class CloneConfigsetRequestBody {
public static final String DEFAULT_CONFIGSET = "_default";

@JsonProperty(required = true)
public String name;

@JsonProperty public String baseConfigSet = DEFAULT_CONFIGSET;
@JsonProperty(defaultValue = DEFAULT_CONFIGSET)
public String baseConfigSet;

@JsonProperty public Map<String, Object> properties;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,30 @@
package org.apache.solr.handler.admin;

import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.handler.configsets.UploadConfigSetFileAPI.FILEPATH_PLACEHOLDER;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.solr.api.AnnotatedApi;
import org.apache.solr.api.Api;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.api.PayloadObj;
import org.apache.solr.client.solrj.request.beans.CreateConfigPayload;
import org.apache.solr.client.api.model.CloneConfigsetRequestBody;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.cloud.ConfigSetCmds;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.params.ConfigSetParams;
import org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
import org.apache.solr.common.params.DefaultSolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.handler.api.V2ApiUtils;
import org.apache.solr.handler.configsets.CreateConfigSetAPI;
import org.apache.solr.handler.configsets.DeleteConfigSetAPI;
import org.apache.solr.handler.configsets.CloneConfigSet;
import org.apache.solr.handler.configsets.ConfigSetAPIBase;
import org.apache.solr.handler.configsets.DeleteConfigSet;
import org.apache.solr.handler.configsets.ListConfigSets;
import org.apache.solr.handler.configsets.UploadConfigSetAPI;
import org.apache.solr.handler.configsets.UploadConfigSetFileAPI;
import org.apache.solr.request.DelegatingSolrQueryRequest;
import org.apache.solr.handler.configsets.UploadConfigSet;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
Expand Down Expand Up @@ -96,51 +90,30 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw

switch (action) {
case DELETE:
final DeleteConfigSetAPI deleteConfigSetAPI = new DeleteConfigSetAPI(coreContainer);
final SolrQueryRequest v2DeleteReq =
new DelegatingSolrQueryRequest(req) {
@Override
public Map<String, String> getPathTemplateValues() {
return Map.of(
DeleteConfigSetAPI.CONFIGSET_NAME_PLACEHOLDER,
req.getParams().required().get(NAME));
}
};
deleteConfigSetAPI.deleteConfigSet(v2DeleteReq, rsp);
final DeleteConfigSet deleteConfigSetAPI = new DeleteConfigSet(coreContainer, req, rsp);
final var deleteResponse =
deleteConfigSetAPI.deleteConfigSet(req.getParams().required().get(NAME));
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteResponse);
break;
case UPLOAD:
final SolrQueryRequest v2UploadReq =
new DelegatingSolrQueryRequest(req) {
@Override
public Map<String, String> getPathTemplateValues() {
final Map<String, String> templateValsByName = new HashMap<>();

templateValsByName.put(
UploadConfigSetAPI.CONFIGSET_NAME_PLACEHOLDER,
req.getParams().required().get(NAME));
if (!req.getParams().get(ConfigSetParams.FILE_PATH, "").isEmpty()) {
templateValsByName.put(
FILEPATH_PLACEHOLDER, req.getParams().get(ConfigSetParams.FILE_PATH));
}
return templateValsByName;
}

// Set the v1 default vals where they differ from v2's
@Override
public SolrParams getParams() {
final ModifiableSolrParams v1Defaults = new ModifiableSolrParams();
v1Defaults.add(ConfigSetParams.OVERWRITE, "false");
v1Defaults.add(ConfigSetParams.CLEANUP, "false");
return new DefaultSolrParams(super.getParams(), v1Defaults);
}
};
final var uploadApi = new UploadConfigSet(coreContainer, req, rsp);
final var configSetName = req.getParams().required().get(NAME);
final var overwrite = req.getParams().getBool(ConfigSetParams.OVERWRITE, false);
final var cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
final var configSetData = ConfigSetAPIBase.ensureNonEmptyInputStream(req);
SolrJerseyResponse uploadResponse;
if (req.getParams()
.get(ConfigSetParams.FILE_PATH, "")
.isEmpty()) { // Uploading a whole configset
new UploadConfigSetAPI(coreContainer).uploadConfigSet(v2UploadReq, rsp);
uploadResponse =
uploadApi.uploadConfigSet(configSetName, overwrite, cleanup, configSetData);
} else { // Uploading a single file
new UploadConfigSetFileAPI(coreContainer).updateConfigSetFile(v2UploadReq, rsp);
final var filePath = req.getParams().get(ConfigSetParams.FILE_PATH);
uploadResponse =
uploadApi.uploadConfigSetFile(
configSetName, filePath, overwrite, cleanup, configSetData);
}
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, uploadResponse);
break;
case LIST:
final ListConfigSets listConfigSetsAPI = new ListConfigSets(coreContainer);
Expand All @@ -153,12 +126,14 @@ public SolrParams getParams() {
}

// Map v1 parameters into v2 format and process request
final CreateConfigPayload createPayload = new CreateConfigPayload();
createPayload.name = newConfigSetName;
final var requestBody = new CloneConfigsetRequestBody();
requestBody.name = newConfigSetName;
if (req.getParams().get(ConfigSetCmds.BASE_CONFIGSET) != null) {
createPayload.baseConfigSet = req.getParams().get(ConfigSetCmds.BASE_CONFIGSET);
requestBody.baseConfigSet = req.getParams().get(ConfigSetCmds.BASE_CONFIGSET);
} else {
requestBody.baseConfigSet = "_default";
}
createPayload.properties = new HashMap<>();
requestBody.properties = new HashMap<>();
req.getParams().stream()
.filter(entry -> entry.getKey().startsWith(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX))
.forEach(
Expand All @@ -167,10 +142,11 @@ public SolrParams getParams() {
entry.getKey().substring(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX.length());
final Object value =
(entry.getValue().length == 1) ? entry.getValue()[0] : entry.getValue();
createPayload.properties.put(newKey, value);
requestBody.properties.put(newKey, value);
});
final CreateConfigSetAPI createConfigSetAPI = new CreateConfigSetAPI(coreContainer);
createConfigSetAPI.create(new PayloadObj<>("create", null, createPayload, req, rsp));
final CloneConfigSet createConfigSetAPI = new CloneConfigSet(coreContainer, req, rsp);
final var createResponse = createConfigSetAPI.cloneExistingConfigSet(requestBody);
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, createResponse);
break;
default:
throw new IllegalStateException("Unexpected ConfigSetAction detected: " + action);
Expand Down Expand Up @@ -207,18 +183,13 @@ public Boolean registerV2() {

@Override
public Collection<Api> getApis() {
final List<Api> apis = new ArrayList<>();
apis.addAll(AnnotatedApi.getApis(new CreateConfigSetAPI(coreContainer)));
apis.addAll(AnnotatedApi.getApis(new DeleteConfigSetAPI(coreContainer)));
apis.addAll(AnnotatedApi.getApis(new UploadConfigSetAPI(coreContainer)));
apis.addAll(AnnotatedApi.getApis(new UploadConfigSetFileAPI(coreContainer)));

return apis;
return new ArrayList<>();
}

@Override
public Collection<Class<? extends JerseyResource>> getJerseyResources() {
return List.of(ListConfigSets.class);
return List.of(
ListConfigSets.class, CloneConfigSet.class, DeleteConfigSet.class, UploadConfigSet.class);
}

@Override
Expand Down
Loading
Loading