diff --git a/changelog/unreleased/migrate-v2-apis-to-jax-rs.yml b/changelog/unreleased/migrate-v2-apis-to-jax-rs.yml new file mode 100644 index 000000000000..61228b242e98 --- /dev/null +++ b/changelog/unreleased/migrate-v2-apis-to-jax-rs.yml @@ -0,0 +1,5 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: Migrate NodeHealthAPI, NodeThreadsAPI, and ClusterAPI V2 endpoints from @EndPoint to JAX-RS +type: changed +authors: + - name: copilot-swe-agent[bot] diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterApis.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterApis.java new file mode 100644 index 000000000000..0870b03e6979 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterApis.java @@ -0,0 +1,113 @@ +/* + * 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.Parameter; +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.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.AddRoleRequestBody; +import org.apache.solr.client.api.model.RateLimiterPayload; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definitions for cluster-level operations in a Solr cluster. */ +@Path("/cluster") +public interface ClusterApis { + + @GET + @Operation( + summary = "Retrieve the cluster status.", + tags = {"cluster"}) + SolrJerseyResponse getClusterStatus() throws Exception; + + @GET + @Path("/nodes") + @Operation( + summary = "Retrieve the list of live nodes in the cluster.", + tags = {"cluster"}) + SolrJerseyResponse getNodes() throws Exception; + + @GET + @Path("/overseer") + @Operation( + summary = "Retrieve the status of the cluster overseer.", + tags = {"cluster"}) + SolrJerseyResponse getOverseerStatus() throws Exception; + + @GET + @Path("/command-status/{requestId}") + @Operation( + summary = "Retrieve the status of an async command.", + tags = {"cluster"}) + SolrJerseyResponse getCommandStatus( + @Parameter(description = "The async request ID to look up.", required = true) + @PathParam("requestId") + String requestId) + throws Exception; + + @DELETE + @Path("/command-status/{requestId}") + @Operation( + summary = "Delete the status of a completed async command.", + tags = {"cluster"}) + SolrJerseyResponse deleteCommandStatus( + @Parameter(description = "The async request ID to delete.", required = true) + @PathParam("requestId") + String requestId) + throws Exception; + + @DELETE + @Path("/command-status") + @Operation( + summary = "Flush the status of all completed async commands.", + tags = {"cluster"}) + SolrJerseyResponse flushCommandStatus() throws Exception; + + @POST + @Path("/roles") + @Operation( + summary = "Add an overseer role to a node.", + tags = {"cluster"}) + SolrJerseyResponse addRole( + @RequestBody(description = "The node and role to assign.", required = true) + AddRoleRequestBody requestBody) + throws Exception; + + @DELETE + @Path("/roles") + @Operation( + summary = "Remove an overseer role from a node.", + tags = {"cluster"}) + SolrJerseyResponse removeRole( + @RequestBody(description = "The node and role to remove.", required = true) + AddRoleRequestBody requestBody) + throws Exception; + + @POST + @Path("/ratelimiters") + @Operation( + summary = "Set rate limiter configuration for the cluster.", + tags = {"cluster"}) + SolrJerseyResponse setRateLimiters( + @RequestBody(description = "Rate limiter configuration.", required = true) + RateLimiterPayload requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterNodeRolesApis.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterNodeRolesApis.java new file mode 100644 index 000000000000..bc440af41e4d --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterNodeRolesApis.java @@ -0,0 +1,74 @@ +/* + * 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.Parameter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definitions for reading node roles in a Solr cluster. */ +@Path("/cluster/node-roles") +public interface ClusterNodeRolesApis { + + @GET + @Operation( + summary = "Retrieve all node roles in the cluster.", + tags = {"cluster"}) + SolrJerseyResponse getAllNodeRoles() throws Exception; + + @GET + @Path("/supported") + @Operation( + summary = "Retrieve all supported node roles and their modes.", + tags = {"cluster"}) + SolrJerseyResponse getSupportedRoles() throws Exception; + + @GET + @Path("/role/{role}") + @Operation( + summary = "Retrieve all nodes assigned to a specific role.", + tags = {"cluster"}) + SolrJerseyResponse getNodesForRole( + @Parameter(description = "The role to query.", required = true) @PathParam("role") + String role) + throws Exception; + + @GET + @Path("/role/{role}/{mode}") + @Operation( + summary = "Retrieve nodes assigned to a specific role and mode.", + tags = {"cluster"}) + SolrJerseyResponse getNodesForRoleAndMode( + @Parameter(description = "The role to query.", required = true) @PathParam("role") + String role, + @Parameter(description = "The mode to query.", required = true) @PathParam("mode") + String mode) + throws Exception; + + @GET + @Path("/node/{node}") + @Operation( + summary = "Retrieve all roles assigned to a specific node.", + tags = {"cluster"}) + SolrJerseyResponse getRolesForNode( + @Parameter(description = "The node name to query.", required = true) @PathParam("node") + String node) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/MigrateDocsApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/MigrateDocsApi.java new file mode 100644 index 000000000000..c0d6585d2c2f --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/MigrateDocsApi.java @@ -0,0 +1,39 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.MigrateDocsRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for migrating documents from one collection to another. */ +@Path("/collections/{collectionName}/migrate") +public interface MigrateDocsApi { + @POST + @Operation( + summary = "Migrate documents to another collection", + tags = {"collections"}) + SolrJerseyResponse migrateDocs( + @PathParam("collectionName") String collectionName, + @RequestBody(description = "Properties for the migrate-docs operation") + MigrateDocsRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/ModifyCollectionApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/ModifyCollectionApi.java new file mode 100644 index 000000000000..d895e9092d13 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/ModifyCollectionApi.java @@ -0,0 +1,39 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.ModifyCollectionRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for modifying a collection's configuration. */ +@Path("/collections/{collectionName}/modify") +public interface ModifyCollectionApi { + @POST + @Operation( + summary = "Modify a collection's configuration", + tags = {"collections"}) + SolrJerseyResponse modifyCollection( + @PathParam("collectionName") String collectionName, + @RequestBody(description = "Properties to modify on the collection") + ModifyCollectionRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/MoveReplicaApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/MoveReplicaApi.java new file mode 100644 index 000000000000..e3044121d193 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/MoveReplicaApi.java @@ -0,0 +1,39 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.MoveReplicaRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for moving a collection replica to a different node. */ +@Path("/collections/{collectionName}/move-replica") +public interface MoveReplicaApi { + @POST + @Operation( + summary = "Move a replica to a different node", + tags = {"replicas"}) + SolrJerseyResponse moveReplica( + @PathParam("collectionName") String collectionName, + @RequestBody(description = "Properties for the move-replica operation") + MoveReplicaRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeHealthApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeHealthApi.java new file mode 100644 index 000000000000..a085ce9ee7a2 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeHealthApi.java @@ -0,0 +1,37 @@ +/* + * 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 jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import org.apache.solr.client.api.model.NodeHealthResponse; + +/** V2 API definition for checking the health of the receiving Solr node. */ +@Path("/node/health") +public interface NodeHealthApi { + + @GET + @Operation( + summary = "Check the health of the receiving Solr node.", + tags = {"node"}) + NodeHealthResponse getHealth( + @QueryParam("requireHealthyCores") Boolean requireHealthyCores, + @QueryParam("maxGenerationLag") Integer maxGenerationLag) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodePropertiesApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodePropertiesApi.java new file mode 100644 index 000000000000..ae3ee2f4bf60 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodePropertiesApi.java @@ -0,0 +1,37 @@ +/* + * 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.Parameter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import org.apache.solr.client.api.model.NodePropertiesResponse; + +/** V2 API definition for listing system properties on a node. */ +@Path("/node/properties") +public interface NodePropertiesApi { + @GET + @Operation( + summary = "List system properties for the node", + tags = {"node"}) + NodePropertiesResponse getProperties( + @Parameter(description = "The name of a specific property to retrieve") @QueryParam("name") + String name) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java new file mode 100644 index 000000000000..22fed5f50c48 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeSystemInfoApi.java @@ -0,0 +1,32 @@ +/* + * 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 jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for retrieving system information from a node. */ +@Path("/node/system") +public interface NodeSystemInfoApi { + @GET + @Operation( + summary = "Get system information for the node", + tags = {"node"}) + SolrJerseyResponse getSystemInfo() throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeThreadsApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeThreadsApi.java new file mode 100644 index 000000000000..c20705e3827e --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/NodeThreadsApi.java @@ -0,0 +1,33 @@ +/* + * 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 jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for triggering a thread dump on the receiving Solr node. */ +@Path("/node/threads") +public interface NodeThreadsApi { + + @GET + @Operation( + summary = "Trigger a thread dump on the receiving Solr node.", + tags = {"node"}) + SolrJerseyResponse getThreads() throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/OverseerOperationApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/OverseerOperationApi.java new file mode 100644 index 000000000000..58b3e040eb1b --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/OverseerOperationApi.java @@ -0,0 +1,37 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import org.apache.solr.client.api.model.OverseerOperationRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for triggering the overseer operation on a node. */ +@Path("/node/overseer-op") +public interface OverseerOperationApi { + @POST + @Operation( + summary = "Trigger an overseer operation on the node", + tags = {"node"}) + SolrJerseyResponse overseerOperation( + @RequestBody(description = "Properties for the overseer-op operation") + OverseerOperationRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/PrepareCoreRecoveryApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/PrepareCoreRecoveryApi.java new file mode 100644 index 000000000000..62b1c41f9a17 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/PrepareCoreRecoveryApi.java @@ -0,0 +1,39 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.PrepareCoreRecoveryRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for preparing a core for recovery. */ +@Path("/cores/{coreName}/prep-recovery") +public interface PrepareCoreRecoveryApi { + @POST + @Operation( + summary = "Prepare a core for recovery", + tags = {"cores"}) + SolrJerseyResponse prepareCoreForRecovery( + @PathParam("coreName") String coreName, + @RequestBody(description = "Properties for the prep-recovery operation") + PrepareCoreRecoveryRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/RebalanceLeadersApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/RebalanceLeadersApi.java new file mode 100644 index 000000000000..f4dc46cbe5ce --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/RebalanceLeadersApi.java @@ -0,0 +1,39 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.RebalanceLeadersRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for rebalancing shard leaders in a collection. */ +@Path("/collections/{collectionName}/rebalance-leaders") +public interface RebalanceLeadersApi { + @POST + @Operation( + summary = "Rebalance shard leaders across nodes in a collection", + tags = {"collections"}) + SolrJerseyResponse rebalanceLeaders( + @PathParam("collectionName") String collectionName, + @RequestBody(description = "Properties for the rebalance-leaders operation") + RebalanceLeadersRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/RejoinLeaderElectionApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/RejoinLeaderElectionApi.java new file mode 100644 index 000000000000..72caaeb3ee74 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/RejoinLeaderElectionApi.java @@ -0,0 +1,37 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import org.apache.solr.client.api.model.RejoinLeaderElectionRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for triggering a core to rejoin leader election. */ +@Path("/node/rejoin-leader-election") +public interface RejoinLeaderElectionApi { + @POST + @Operation( + summary = "Trigger a core to rejoin leader election for its shard", + tags = {"node"}) + SolrJerseyResponse rejoinLeaderElection( + @RequestBody(description = "Properties for the rejoin-leader-election operation") + RejoinLeaderElectionRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestApplyCoreUpdatesApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestApplyCoreUpdatesApi.java new file mode 100644 index 000000000000..6bbc6430aadf --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestApplyCoreUpdatesApi.java @@ -0,0 +1,34 @@ +/* + * 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 jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for applying core updates. */ +@Path("/cores/{coreName}/request-apply-updates") +public interface RequestApplyCoreUpdatesApi { + @POST + @Operation( + summary = "Apply updates to the specified core", + tags = {"cores"}) + SolrJerseyResponse requestApplyCoreUpdates(@PathParam("coreName") String coreName) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestBufferUpdatesApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestBufferUpdatesApi.java new file mode 100644 index 000000000000..5e7f04ffddb0 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestBufferUpdatesApi.java @@ -0,0 +1,33 @@ +/* + * 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 jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for starting update-buffering on a core. */ +@Path("/cores/{coreName}/request-buffer-updates") +public interface RequestBufferUpdatesApi { + @POST + @Operation( + summary = "Start update-buffering on the specified core", + tags = {"cores"}) + SolrJerseyResponse requestBufferUpdates(@PathParam("coreName") String coreName) throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestCoreRecoveryApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestCoreRecoveryApi.java new file mode 100644 index 000000000000..2b39aca7eac6 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestCoreRecoveryApi.java @@ -0,0 +1,33 @@ +/* + * 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 jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for triggering recovery on a core. */ +@Path("/cores/{coreName}/request-recovery") +public interface RequestCoreRecoveryApi { + @POST + @Operation( + summary = "Trigger recovery for the specified core", + tags = {"cores"}) + SolrJerseyResponse requestCoreRecovery(@PathParam("coreName") String coreName) throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestSyncShardApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestSyncShardApi.java new file mode 100644 index 000000000000..1a9dd7be4c15 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/RequestSyncShardApi.java @@ -0,0 +1,33 @@ +/* + * 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 jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.SolrJerseyResponse; + +/** V2 API definition for requesting a core to sync with its shard leader. */ +@Path("/cores/{coreName}/request-sync-shard") +public interface RequestSyncShardApi { + @POST + @Operation( + summary = "Request a core to sync with its shard leader", + tags = {"cores"}) + SolrJerseyResponse requestSyncShard(@PathParam("coreName") String coreName) throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/SplitCoreApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/SplitCoreApi.java new file mode 100644 index 000000000000..275be930aba3 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/SplitCoreApi.java @@ -0,0 +1,39 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.model.SplitCoreRequestBody; + +/** V2 API definition for splitting a core into multiple pieces. */ +@Path("/cores/{coreName}/split") +public interface SplitCoreApi { + @POST + @Operation( + summary = "Split a Solr core into multiple pieces", + tags = {"cores"}) + SolrJerseyResponse splitCore( + @PathParam("coreName") String coreName, + @RequestBody(description = "Properties for the split operation") + SplitCoreRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/SplitShardApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/SplitShardApi.java new file mode 100644 index 000000000000..7e95436a939f --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/SplitShardApi.java @@ -0,0 +1,39 @@ +/* + * 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.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.model.SplitShardRequestBody; + +/** V2 API definition for splitting an existing shard into multiple pieces. */ +@Path("/collections/{collectionName}/shards/split") +public interface SplitShardApi { + @POST + @Operation( + summary = "Split an existing shard into multiple pieces", + tags = {"shards"}) + SolrJerseyResponse splitShard( + @PathParam("collectionName") String collectionName, + @RequestBody(description = "Properties for the split operation") + SplitShardRequestBody requestBody) + throws Exception; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/AddRoleRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/AddRoleRequestBody.java new file mode 100644 index 000000000000..8b520e9eeee3 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/AddRoleRequestBody.java @@ -0,0 +1,28 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for adding or removing a role from a Solr node. */ +public class AddRoleRequestBody { + @JsonProperty(value = "node", required = true) + public String node; + + @JsonProperty(value = "role", required = true) + public String role; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/MigrateDocsRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/MigrateDocsRequestBody.java new file mode 100644 index 000000000000..77735f162521 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/MigrateDocsRequestBody.java @@ -0,0 +1,34 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for the migrate-docs V2 API. */ +public class MigrateDocsRequestBody { + @JsonProperty(required = true) + public String target; + + @JsonProperty(required = true) + public String splitKey; + + @JsonProperty public Integer forwardTimeout; + + @JsonProperty public Boolean followAliases; + + @JsonProperty public String async; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ModifyCollectionRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ModifyCollectionRequestBody.java new file mode 100644 index 000000000000..14ad5072999f --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/ModifyCollectionRequestBody.java @@ -0,0 +1,33 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** Request body for the modify-collection V2 API. */ +public class ModifyCollectionRequestBody { + @JsonProperty public Integer replicationFactor; + + @JsonProperty public Boolean readOnly; + + @JsonProperty public String config; + + @JsonProperty public Map properties; + + @JsonProperty public String async; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/MoveReplicaRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/MoveReplicaRequestBody.java new file mode 100644 index 000000000000..66d9606b29ba --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/MoveReplicaRequestBody.java @@ -0,0 +1,41 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for the move-replica V2 API. */ +public class MoveReplicaRequestBody { + @JsonProperty(required = true) + public String targetNode; + + @JsonProperty public String replica; + + @JsonProperty public String shard; + + @JsonProperty public String sourceNode; + + @JsonProperty + @Deprecated(since = "9.10") + public Boolean waitForFinalState = false; + + @JsonProperty public Integer timeout = 600; + + @JsonProperty public Boolean inPlaceMove = true; + + @JsonProperty public Boolean followAliases; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodeHealthResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodeHealthResponse.java new file mode 100644 index 000000000000..9ecc07380e2c --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodeHealthResponse.java @@ -0,0 +1,36 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +/** Response model for node health check API. */ +public class NodeHealthResponse extends SolrJerseyResponse { + + @JsonProperty("status") + @Schema(description = "The health status of the node: 'OK' or 'FAILURE'.") + public String status; + + @JsonProperty("message") + @Schema(description = "An optional message providing additional details about the node health.") + public String message; + + @JsonProperty("num_cores_unhealthy") + @Schema(description = "The number of cores that are not in a healthy state.") + public Long numCoresUnhealthy; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/NodePropertiesResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/NodePropertiesResponse.java new file mode 100644 index 000000000000..91fb7775d32e --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/NodePropertiesResponse.java @@ -0,0 +1,26 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** Response class for the NodePropertiesApi */ +public class NodePropertiesResponse extends SolrJerseyResponse { + @JsonProperty("system.properties") + public Map systemProperties; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/OverseerOperationRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/OverseerOperationRequestBody.java new file mode 100644 index 000000000000..d15e8ac42a86 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/OverseerOperationRequestBody.java @@ -0,0 +1,26 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for the overseer-op V2 API. */ +public class OverseerOperationRequestBody { + @JsonProperty public String op; + + @JsonProperty public String electionNode; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/PrepareCoreRecoveryRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/PrepareCoreRecoveryRequestBody.java new file mode 100644 index 000000000000..4a1cf19bbca1 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/PrepareCoreRecoveryRequestBody.java @@ -0,0 +1,34 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for the prep-recovery V2 API. */ +public class PrepareCoreRecoveryRequestBody { + @JsonProperty public String nodeName; + + @JsonProperty public String coreNodeName; + + @JsonProperty public String state; + + @JsonProperty public Boolean checkLive; + + @JsonProperty public Boolean onlyIfLeader; + + @JsonProperty public Boolean onlyIfLeaderActive; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/RateLimiterPayload.java b/solr/api/src/java/org/apache/solr/client/api/model/RateLimiterPayload.java new file mode 100644 index 000000000000..c979c383e305 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/RateLimiterPayload.java @@ -0,0 +1,37 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for configuring the rate limiter on a Solr cluster. */ +public class RateLimiterPayload { + @JsonProperty("enabled") + public Boolean enabled; + + @JsonProperty("guaranteedSlots") + public Integer guaranteedSlots; + + @JsonProperty("allowedRequests") + public Integer allowedRequests; + + @JsonProperty("slotBorrowingEnabled") + public Boolean slotBorrowingEnabled; + + @JsonProperty("slotAcquisitionTimeoutInMS") + public Integer slotAcquisitionTimeoutInMS; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/RebalanceLeadersRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/RebalanceLeadersRequestBody.java new file mode 100644 index 000000000000..f14562377896 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/RebalanceLeadersRequestBody.java @@ -0,0 +1,26 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for the rebalance-leaders V2 API. */ +public class RebalanceLeadersRequestBody { + @JsonProperty public Integer maxAtOnce; + + @JsonProperty public Integer maxWaitSeconds; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/RejoinLeaderElectionRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/RejoinLeaderElectionRequestBody.java new file mode 100644 index 000000000000..9a41747afab8 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/RejoinLeaderElectionRequestBody.java @@ -0,0 +1,30 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Request body for the rejoin-leader-election V2 API. */ +public class RejoinLeaderElectionRequestBody { + @JsonProperty public String collection; + + @JsonProperty public String coreNodeName; + + @JsonProperty public String core; + + @JsonProperty public Boolean rejoinAtHead; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/SplitCoreRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/SplitCoreRequestBody.java new file mode 100644 index 000000000000..ac2069e2ed2b --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/SplitCoreRequestBody.java @@ -0,0 +1,37 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** Request body for the split-core V2 API. */ +public class SplitCoreRequestBody { + @JsonProperty public List path; + + @JsonProperty public List targetCore; + + @JsonProperty public String splitKey; + + @JsonProperty public String splitMethod; + + @JsonProperty public Boolean getRanges; + + @JsonProperty public String ranges; + + @JsonProperty public String async; +} diff --git a/solr/api/src/java/org/apache/solr/client/api/model/SplitShardRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/SplitShardRequestBody.java new file mode 100644 index 000000000000..15b93652bb49 --- /dev/null +++ b/solr/api/src/java/org/apache/solr/client/api/model/SplitShardRequestBody.java @@ -0,0 +1,49 @@ +/* + * 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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** Request body for the split-shard V2 API. */ +public class SplitShardRequestBody { + @JsonProperty public String shard; + + @JsonProperty public String ranges; + + @JsonProperty public String splitKey; + + @JsonProperty public Integer numSubShards; + + @JsonProperty public String splitFuzz; + + @JsonProperty public Boolean timing; + + @JsonProperty public Boolean splitByPrefix; + + @JsonProperty public Boolean followAliases; + + @JsonProperty public String splitMethod; + + @JsonProperty public Map coreProperties; + + @JsonProperty public String async; + + @JsonProperty + @Deprecated(since = "9.10") + public Boolean waitForFinalState; +} diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java index f6bf1dfb36ab..1b52b0013d55 100644 --- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java +++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java @@ -108,7 +108,6 @@ import org.apache.solr.filestore.ClusterFileStore; import org.apache.solr.filestore.DistribFileStore; import org.apache.solr.filestore.FileStore; -import org.apache.solr.handler.ClusterAPI; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.SnapShooter; import org.apache.solr.handler.admin.CollectionsHandler; @@ -864,9 +863,8 @@ private void loadInternal() { configSetsHandler = createHandler( CONFIGSETS_HANDLER_PATH, cfg.getConfigSetsHandlerClass(), ConfigSetsHandler.class); - ClusterAPI clusterAPI = new ClusterAPI(collectionsHandler, configSetsHandler); - registerV2ApiIfEnabled(clusterAPI); - registerV2ApiIfEnabled(clusterAPI.commands); + registerV2ApiIfEnabled(org.apache.solr.handler.admin.api.Cluster.class); + registerV2ApiIfEnabled(org.apache.solr.handler.admin.api.ClusterNodeRoles.class); if (isZooKeeperAware()) { registerV2ApiIfEnabled(new SchemaDesignerAPI(this)); diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java index 4807e19aca24..d14074cd7f41 100644 --- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java @@ -17,171 +17,32 @@ package org.apache.solr.handler; -import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE; -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.cloud.api.collections.CollectionHandlingUtils.REQUESTID; -import static org.apache.solr.common.params.CollectionParams.ACTION; -import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE; -import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESTATUS; -import static org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS; -import static org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE; -import static org.apache.solr.common.params.CollectionParams.CollectionAction.REQUESTSTATUS; -import static org.apache.solr.core.RateLimiterConfig.RL_CONFIG_KEY; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM; - import java.io.IOException; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; import org.apache.solr.client.solrj.cloud.DistribStateManager; -import org.apache.solr.client.solrj.request.beans.RateLimiterPayload; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.annotation.JsonProperty; -import org.apache.solr.common.cloud.ClusterProperties; import org.apache.solr.common.cloud.ZkStateReader; -import org.apache.solr.common.params.CollectionParams.CollectionAction; -import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.DefaultSolrParams; import org.apache.solr.common.params.ModifiableSolrParams; -import org.apache.solr.common.util.ReflectMapWriter; import org.apache.solr.common.util.Utils; -import org.apache.solr.core.CoreContainer; import org.apache.solr.core.NodeRoles; -import org.apache.solr.handler.admin.CollectionsHandler; -import org.apache.solr.handler.admin.ConfigSetsHandler; import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.SolrQueryResponse; import org.apache.zookeeper.KeeperException; -/** All V2 APIs that have a prefix of /api/cluster/ */ +/** + * Utility methods for cluster V2 API implementations. + * + *

This class previously hosted the old-style {@code @EndPoint} V2 API implementations for {@code + * /api/cluster}. These have been migrated to JAX-RS resources: {@link + * org.apache.solr.handler.admin.api.ClusterNodeRoles} and {@link + * org.apache.solr.handler.admin.api.Cluster}. + */ public class ClusterAPI { - private final CollectionsHandler collectionsHandler; - private final ConfigSetsHandler configSetsHandler; - - public final Commands commands = new Commands(); - - public ClusterAPI(CollectionsHandler ch, ConfigSetsHandler configSetsHandler) { - this.collectionsHandler = ch; - this.configSetsHandler = configSetsHandler; - } - - @EndPoint(method = GET, path = "/cluster/node-roles", permission = COLL_READ_PERM) - public void roles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - rsp.add( - "node-roles", - readRecursive( - ZkStateReader.NODE_ROLES, - collectionsHandler - .getCoreContainer() - .getZkController() - .getSolrCloudManager() - .getDistribStateManager(), - 3)); - } - - Object readRecursive(String path, DistribStateManager zk, int depth) { - if (depth == 0) return null; - Map result; - try { - List children = zk.listData(path); - if (children != null && !children.isEmpty()) { - result = new HashMap<>(); - } else { - return Collections.emptySet(); - } - for (String child : children) { - Object c = readRecursive(path + "/" + child, zk, depth - 1); - result.put(child, c); - } - } catch (Exception e) { - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); - } - if (depth == 1) { - return result.keySet(); - } else { - return result; - } - } - - @EndPoint(method = GET, path = "/cluster/node-roles/role/{role}", permission = COLL_READ_PERM) - public void nodesWithRole(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - String role = req.getPathTemplateValues().get("role"); - rsp.add( - "node-roles", - Map.of( - role, - readRecursive( - ZkStateReader.NODE_ROLES + "/" + role, - collectionsHandler - .getCoreContainer() - .getZkController() - .getSolrCloudManager() - .getDistribStateManager(), - 2))); - } - - @EndPoint(method = GET, path = "/cluster/node-roles/node/{node}", permission = COLL_READ_PERM) - @SuppressWarnings("unchecked") - public void rolesForNode(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - String node = req.getPathTemplateValues().get("node"); - Map ret = new HashMap<>(); - Map>> roles = - (Map>>) - readRecursive( - ZkStateReader.NODE_ROLES, - collectionsHandler - .getCoreContainer() - .getZkController() - .getSolrCloudManager() - .getDistribStateManager(), - 3); - for (String role : roles.keySet()) { - for (String mode : roles.get(role).keySet()) { - if (roles.get(role).get(mode).isEmpty()) continue; - Set nodes = roles.get(role).get(mode); - if (nodes.contains(node)) ret.put(role, mode); - } - } - for (String role : ret.keySet()) { - rsp.add(role, ret.get(role)); - } - } - - @EndPoint(method = GET, path = "/cluster/node-roles/supported", permission = COLL_READ_PERM) - public void supportedRoles(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - Map roleModesSupportedMap = new HashMap<>(); - for (NodeRoles.Role role : NodeRoles.Role.values()) { - roleModesSupportedMap.put(role.toString(), Map.of("modes", role.supportedModes())); - } - rsp.add("supported-roles", roleModesSupportedMap); - } - - @EndPoint( - method = GET, - path = "/cluster/node-roles/role/{role}/{mode}", - permission = COLL_READ_PERM) - public void nodesWithRoleMode(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - // Here, deal with raw strings instead of Role & Mode types so as to handle roles and modes - // that are not understood by this node (possibly during a rolling upgrade) - String roleStr = req.getPathTemplateValues().get("role"); - String modeStr = req.getPathTemplateValues().get("mode"); - List nodes = - collectionsHandler - .getCoreContainer() - .getZkController() - .getSolrCloudManager() - .getDistribStateManager() - .listData(ZkStateReader.NODE_ROLES + "/" + roleStr + "/" + modeStr); - rsp.add("node-roles", Map.of(roleStr, Collections.singletonMap(modeStr, nodes))); + private ClusterAPI() { + /* Utility class - no instances */ } public static List getNodesByRole( @@ -194,23 +55,6 @@ public static List getNodesByRole( } } - @EndPoint(method = GET, path = "/cluster/overseer", permission = COLL_READ_PERM) - public void getOverseerStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - collectionsHandler.handleRequestBody(wrapParams(req, "action", OVERSEERSTATUS.lowerName), rsp); - } - - @EndPoint(method = DELETE, path = "/cluster/command-status/{id}", permission = COLL_EDIT_PERM) - public void deleteCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - final Map v1Params = - Map.of(ACTION, DELETESTATUS.lowerName, REQUESTID, req.getPathTemplateValues().get("id")); - collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp); - } - - @EndPoint(method = DELETE, path = "/cluster/command-status", permission = COLL_EDIT_PERM) - public void flushCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - CollectionsHandler.CollectionOperation.DELETESTATUS_OP.execute(req, rsp, collectionsHandler); - } - public static SolrQueryRequest wrapParams(SolrQueryRequest req, Object... def) { Map m = Utils.makeMap(def); return wrapParams(req, m); @@ -231,67 +75,4 @@ public static SolrQueryRequest wrapParams(SolrQueryRequest req, Map v1Params = - Map.of(ACTION, REQUESTSTATUS.lowerName, REQUESTID, req.getPathTemplateValues().get("id")); - collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp); - } - - @EndPoint(method = GET, path = "/cluster/nodes", permission = COLL_READ_PERM) - public void getNodes(SolrQueryRequest req, SolrQueryResponse rsp) { - rsp.add("nodes", getCoreContainer().getZkController().getClusterState().getLiveNodes()); - } - - @EndPoint(method = GET, path = "/cluster", permission = COLL_READ_PERM) - public void getClusterStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - final Map v1Params = - Map.of(CommonParams.ACTION, CollectionAction.CLUSTERSTATUS.toLower()); - collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp); - } - - private CoreContainer getCoreContainer() { - return collectionsHandler.getCoreContainer(); - } - - @EndPoint(method = POST, path = "/cluster", permission = COLL_EDIT_PERM) - public class Commands { - @Command(name = "add-role") - public void addRole(PayloadObj obj) throws Exception { - RoleInfo info = obj.get(); - Map m = info.toMap(new HashMap<>()); - m.put("action", ADDROLE.toString()); - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m), obj.getResponse()); - } - - @Command(name = "remove-role") - public void removeRole(PayloadObj obj) throws Exception { - RoleInfo info = obj.get(); - Map m = info.toMap(new HashMap<>()); - m.put("action", REMOVEROLE.toString()); - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m), obj.getResponse()); - } - - @Command(name = "set-ratelimiter") - public void setRateLimiters(PayloadObj payLoad) { - RateLimiterPayload rateLimiterConfig = payLoad.get(); - ClusterProperties clusterProperties = - new ClusterProperties(getCoreContainer().getZkController().getZkClient()); - - try { - clusterProperties.update(rateLimiterConfig, RL_CONFIG_KEY); - } catch (Exception e) { - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e); - } - } - } - - public static class RoleInfo implements ReflectMapWriter { - @JsonProperty(required = true) - public String node; - - @JsonProperty(required = true) - public String role; - } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java index 946559e50161..b7a963c36a8c 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java @@ -116,7 +116,6 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; import org.apache.solr.api.JerseyResource; import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody; @@ -197,16 +196,15 @@ import org.apache.solr.handler.admin.api.ListCollectionBackups; import org.apache.solr.handler.admin.api.ListCollectionSnapshots; import org.apache.solr.handler.admin.api.ListCollections; -import org.apache.solr.handler.admin.api.MigrateDocsAPI; +import org.apache.solr.handler.admin.api.MigrateDocs; import org.apache.solr.handler.admin.api.MigrateReplicas; -import org.apache.solr.handler.admin.api.ModifyCollectionAPI; -import org.apache.solr.handler.admin.api.MoveReplicaAPI; -import org.apache.solr.handler.admin.api.RebalanceLeadersAPI; +import org.apache.solr.handler.admin.api.ModifyCollection; +import org.apache.solr.handler.admin.api.MoveReplica; import org.apache.solr.handler.admin.api.ReloadCollectionAPI; import org.apache.solr.handler.admin.api.RenameCollection; import org.apache.solr.handler.admin.api.ReplaceNode; import org.apache.solr.handler.admin.api.RestoreCollection; -import org.apache.solr.handler.admin.api.SplitShardAPI; +import org.apache.solr.handler.admin.api.SplitShard; import org.apache.solr.handler.admin.api.SyncShard; import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.logging.MDCLoggingContext; @@ -1373,18 +1371,17 @@ public Collection> getJerseyResources() { ListCollectionSnapshots.class, CreateCollectionSnapshot.class, DeleteCollectionSnapshot.class, - ClusterProperty.class); + ClusterProperty.class, + SplitShard.class, + MigrateDocs.class, + ModifyCollection.class, + MoveReplica.class, + org.apache.solr.handler.admin.api.RebalanceLeaders.class); } @Override public Collection getApis() { - final List apis = new ArrayList<>(); - apis.addAll(AnnotatedApi.getApis(new SplitShardAPI(this))); - apis.addAll(AnnotatedApi.getApis(new MigrateDocsAPI(this))); - apis.addAll(AnnotatedApi.getApis(new ModifyCollectionAPI(this))); - apis.addAll(AnnotatedApi.getApis(new MoveReplicaAPI(this))); - apis.addAll(AnnotatedApi.getApis(new RebalanceLeadersAPI(this))); - return apis; + return List.of(); } // These "copy" methods were once SolrParams.getAll but were moved here as there is no universal diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java index c6d9474e0de8..0347b82556fc 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java @@ -27,7 +27,6 @@ import com.github.benmanes.caffeine.cache.Ticker; import io.opentelemetry.api.common.Attributes; import java.lang.invoke.MethodHandles; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -37,7 +36,6 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; import org.apache.solr.api.JerseyResource; import org.apache.solr.cloud.CloudDescriptor; @@ -60,17 +58,17 @@ import org.apache.solr.handler.admin.api.GetNodeCommandStatus; import org.apache.solr.handler.admin.api.InstallCoreData; import org.apache.solr.handler.admin.api.MergeIndexes; -import org.apache.solr.handler.admin.api.OverseerOperationAPI; -import org.apache.solr.handler.admin.api.PrepareCoreRecoveryAPI; -import org.apache.solr.handler.admin.api.RejoinLeaderElectionAPI; +import org.apache.solr.handler.admin.api.OverseerOperation; +import org.apache.solr.handler.admin.api.PrepareCoreRecovery; +import org.apache.solr.handler.admin.api.RejoinLeaderElection; import org.apache.solr.handler.admin.api.ReloadCore; import org.apache.solr.handler.admin.api.RenameCore; -import org.apache.solr.handler.admin.api.RequestApplyCoreUpdatesAPI; -import org.apache.solr.handler.admin.api.RequestBufferUpdatesAPI; -import org.apache.solr.handler.admin.api.RequestCoreRecoveryAPI; -import org.apache.solr.handler.admin.api.RequestSyncShardAPI; +import org.apache.solr.handler.admin.api.RequestApplyCoreUpdates; +import org.apache.solr.handler.admin.api.RequestBufferUpdates; +import org.apache.solr.handler.admin.api.RequestCoreRecovery; +import org.apache.solr.handler.admin.api.RequestSyncShard; import org.apache.solr.handler.admin.api.RestoreCore; -import org.apache.solr.handler.admin.api.SplitCoreAPI; +import org.apache.solr.handler.admin.api.SplitCore; import org.apache.solr.handler.admin.api.SwapCores; import org.apache.solr.handler.admin.api.UnloadCore; import org.apache.solr.logging.MDCLoggingContext; @@ -311,18 +309,7 @@ void call() throws Exception { @Override public Collection getApis() { - final List apis = new ArrayList<>(); - apis.addAll(AnnotatedApi.getApis(new RejoinLeaderElectionAPI(this))); - apis.addAll(AnnotatedApi.getApis(new OverseerOperationAPI(this))); - apis.addAll(AnnotatedApi.getApis(new SplitCoreAPI(this))); - // Internal APIs - apis.addAll(AnnotatedApi.getApis(new RequestCoreRecoveryAPI(this))); - apis.addAll(AnnotatedApi.getApis(new PrepareCoreRecoveryAPI(this))); - apis.addAll(AnnotatedApi.getApis(new RequestApplyCoreUpdatesAPI(this))); - apis.addAll(AnnotatedApi.getApis(new RequestSyncShardAPI(this))); - apis.addAll(AnnotatedApi.getApis(new RequestBufferUpdatesAPI(this))); - - return apis; + return List.of(); } @Override @@ -339,7 +326,15 @@ public Collection> getJerseyResources() { SwapCores.class, RenameCore.class, MergeIndexes.class, - GetNodeCommandStatus.class); + GetNodeCommandStatus.class, + RejoinLeaderElection.class, + OverseerOperation.class, + SplitCore.class, + RequestCoreRecovery.class, + PrepareCoreRecovery.class, + RequestApplyCoreUpdates.class, + RequestSyncShard.class, + RequestBufferUpdates.class); } public interface CoreAdminOp { diff --git a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java index 1ecf959e49ed..e2558cc44538 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java @@ -17,37 +17,20 @@ package org.apache.solr.handler.admin; -import static org.apache.solr.common.params.CommonParams.FAILURE; -import static org.apache.solr.common.params.CommonParams.OK; -import static org.apache.solr.common.params.CommonParams.STATUS; -import static org.apache.solr.handler.admin.api.ReplicationAPIBase.GENERATION; - import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; -import org.apache.lucene.index.IndexCommit; -import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; -import org.apache.solr.client.solrj.request.HealthCheckRequest; -import org.apache.solr.cloud.CloudDescriptor; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.cloud.ClusterState; -import org.apache.solr.common.cloud.Replica.State; -import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.api.JerseyResource; import org.apache.solr.common.util.NamedList; import org.apache.solr.core.CoreContainer; -import org.apache.solr.core.SolrCore; -import org.apache.solr.handler.IndexFetcher; -import org.apache.solr.handler.ReplicationHandler; import org.apache.solr.handler.RequestHandlerBase; -import org.apache.solr.handler.admin.api.NodeHealthAPI; +import org.apache.solr.handler.admin.api.NodeHealth; +import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.PermissionNameProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -81,8 +64,6 @@ public class HealthCheckHandler extends RequestHandlerBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private static final String PARAM_REQUIRE_HEALTHY_CORES = "requireHealthyCores"; - private static final List UNHEALTHY_STATES = Arrays.asList(State.DOWN, State.RECOVERING); CoreContainer coreContainer; @@ -100,224 +81,14 @@ public CoreContainer getCoreContainer() { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { rsp.setHttpCaching(false); - - // Core container should not be null and active (redundant check) - if (coreContainer == null || coreContainer.isShutDown()) { - rsp.setException( - new SolrException( - SolrException.ErrorCode.SERVER_ERROR, - "CoreContainer is either not initialized or shutting down")); - return; - } - if (!coreContainer.isZooKeeperAware()) { - if (log.isDebugEnabled()) { - log.debug("Invoked HealthCheckHandler in legacy mode."); - } - healthCheckLegacyMode(req, rsp); - } else { - if (log.isDebugEnabled()) { - log.debug( - "Invoked HealthCheckHandler in cloud mode on [{}]", - this.coreContainer.getZkController().getNodeName()); - } - healthCheckCloudMode(req, rsp); - } - } - - private void healthCheckCloudMode(SolrQueryRequest req, SolrQueryResponse rsp) { - ZkStateReader zkStateReader = coreContainer.getZkController().getZkStateReader(); - ClusterState clusterState = zkStateReader.getClusterState(); - // Check for isConnected and isClosed - if (zkStateReader.getZkClient().isClosed() || !zkStateReader.getZkClient().isConnected()) { - rsp.add(STATUS, FAILURE); - rsp.setException( - new SolrException( - SolrException.ErrorCode.SERVICE_UNAVAILABLE, - "Host Unavailable: Not connected to zk")); - return; - } - - // Fail if not in live_nodes - if (!clusterState.getLiveNodes().contains(coreContainer.getZkController().getNodeName())) { - rsp.add(STATUS, FAILURE); - rsp.setException( - new SolrException( - SolrException.ErrorCode.SERVICE_UNAVAILABLE, - "Host Unavailable: Not in live nodes as per zk")); - return; - } - - // Optionally require that all cores on this node are active if param 'requireHealthyCores=true' - if (req.getParams().getBool(PARAM_REQUIRE_HEALTHY_CORES, false)) { - if (!coreContainer.isStatusLoadComplete()) { - rsp.add(STATUS, FAILURE); - rsp.setException( - new SolrException( - SolrException.ErrorCode.SERVICE_UNAVAILABLE, - "Host Unavailable: Core Loading not complete")); - return; - } - Collection coreDescriptors = - coreContainer.getCoreDescriptors().stream() - .map(cd -> cd.getCloudDescriptor()) - .collect(Collectors.toList()); - long unhealthyCores = findUnhealthyCores(coreDescriptors, clusterState); - if (unhealthyCores > 0) { - rsp.add(STATUS, FAILURE); - rsp.add("num_cores_unhealthy", unhealthyCores); - rsp.setException( - new SolrException( - SolrException.ErrorCode.SERVICE_UNAVAILABLE, - unhealthyCores - + " out of " - + coreContainer.getNumAllCores() - + " replicas are currently initializing or recovering")); - return; - } - rsp.add("message", "All cores are healthy"); - } - - // All lights green, report healthy - rsp.add(STATUS, OK); - } - - private void healthCheckLegacyMode(SolrQueryRequest req, SolrQueryResponse rsp) { - Integer maxGenerationLag = req.getParams().getInt(HealthCheckRequest.PARAM_MAX_GENERATION_LAG); - List laggingCoresInfo = new ArrayList<>(); - boolean allCoresAreInSync = true; - - // check only if max generation lag is specified - if (maxGenerationLag != null) { - // if is not negative - if (maxGenerationLag < 0) { - log.error("Invalid value for maxGenerationLag:[{}]", maxGenerationLag); - rsp.add( - "message", - String.format(Locale.ROOT, "Invalid value of maxGenerationLag:%s", maxGenerationLag)); - rsp.add(STATUS, FAILURE); - } else { - for (SolrCore core : coreContainer.getCores()) { - ReplicationHandler replicationHandler = - (ReplicationHandler) core.getRequestHandler(ReplicationHandler.PATH); - if (replicationHandler.isFollower()) { - boolean isCoreInSync = - isWithinGenerationLag(core, replicationHandler, maxGenerationLag, laggingCoresInfo); - - allCoresAreInSync &= isCoreInSync; - } - } - } - if (allCoresAreInSync) { - rsp.add( - "message", - String.format( - Locale.ROOT, - "All the followers are in sync with leader (within maxGenerationLag: %d) " - + "or the cores are acting as leader", - maxGenerationLag)); - rsp.add(STATUS, OK); - } else { - rsp.add( - "message", - String.format( - Locale.ROOT, - "Cores violating maxGenerationLag:%d.%n%s", - maxGenerationLag, - String.join(",\n", laggingCoresInfo))); - rsp.add(STATUS, FAILURE); - } - } else { // if maxGeneration lag is not specified (is null) we aren't checking for lag - rsp.add( - "message", - "maxGenerationLag isn't specified. Followers aren't " - + "checking for the generation lag from the leaders"); - rsp.add(STATUS, OK); - } - } - - private boolean isWithinGenerationLag( - final SolrCore core, - ReplicationHandler replicationHandler, - int maxGenerationLag, - List laggingCoresInfo) { - IndexFetcher indexFetcher = null; - try { - // may not be the best way to get leader's replicableCommit - NamedList follower = (NamedList) replicationHandler.getInitArgs().get("follower"); - - indexFetcher = new IndexFetcher(follower, replicationHandler, core); - - NamedList replicableCommitOnLeader = indexFetcher.getLatestVersion(); - long leaderGeneration = (Long) replicableCommitOnLeader.get(GENERATION); - - // Get our own commit and generation from the commit - IndexCommit commit = core.getDeletionPolicy().getLatestCommit(); - if (commit != null) { - long followerGeneration = commit.getGeneration(); - long generationDiff = leaderGeneration - followerGeneration; - - // generationDiff shouldn't be negative except for some edge cases, log it. Some scenarios - // are - // 1) commit generation rolls over Long.MAX_VALUE (really unlikely) - // 2) Leader's index is wiped clean and the follower is still showing commit generation - // from the old index - if (generationDiff < 0) { - log.warn("core:[{}], generation lag:[{}] is negative."); - } else if (generationDiff < maxGenerationLag) { - log.info( - "core:[{}] generation lag is above acceptable threshold:[{}], " - + "generation lag:[{}], leader generation:[{}], follower generation:[{}]", - core, - maxGenerationLag, - generationDiff, - leaderGeneration, - followerGeneration); - - laggingCoresInfo.add( - String.format( - Locale.ROOT, - "Core %s is lagging by %d generations", - core.getName(), - generationDiff)); - return true; - } - } - } catch (Exception e) { - log.error("Failed to check if the follower is in sync with the leader", e); - } finally { - if (indexFetcher != null) { - indexFetcher.destroy(); - } - } - return false; - } - - /** - * Find replicas DOWN or RECOVERING, or replicas in clusterstate that do not exist on local node. - * We first find local cores which are either not registered or unhealthy, and check each of these - * against the clusterstate, and return a count of unhealthy replicas - * - * @param cores list of core cloud descriptors to iterate - * @param clusterState clusterstate from ZK - * @return number of unhealthy cores, either in DOWN or RECOVERING state - */ - static long findUnhealthyCores(Collection cores, ClusterState clusterState) { - return cores.stream() - .filter( - c -> - !c.hasRegistered() - || UNHEALTHY_STATES.contains(c.getLastPublished())) // Find candidates locally - .filter( - c -> - clusterState.hasCollection( - c.getCollectionName())) // Only care about cores for actual collections - .filter( - c -> - clusterState - .getCollection(c.getCollectionName()) - .getActiveSlicesMap() - .containsKey(c.getShardId())) - .count(); + final NodeHealth nodeHealthApi = new NodeHealth(coreContainer); + final Boolean requireHealthyCores = req.getParams().getBool("requireHealthyCores", false); + final Integer maxGenerationLag = + req.getParams() + .getInt( + org.apache.solr.client.solrj.request.HealthCheckRequest.PARAM_MAX_GENERATION_LAG); + V2ApiUtils.squashIntoSolrResponseWithoutHeader( + rsp, nodeHealthApi.getHealth(requireHealthyCores, maxGenerationLag)); } @Override @@ -337,11 +108,16 @@ public Boolean registerV2() { @Override public Collection getApis() { - return AnnotatedApi.getApis(new NodeHealthAPI(this)); + return List.of(); + } + + @Override + public Collection> getJerseyResources() { + return List.of(NodeHealth.class); } @Override - public Name getPermissionName(AuthorizationContext request) { - return Name.HEALTH_PERM; + public PermissionNameProvider.Name getPermissionName(AuthorizationContext request) { + return PermissionNameProvider.Name.HEALTH_PERM; } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java index 8658adf3c528..d1b9e0962275 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java @@ -21,14 +21,15 @@ import java.io.IOException; import java.util.Collection; import java.util.Enumeration; -import org.apache.solr.api.AnnotatedApi; +import java.util.List; import org.apache.solr.api.Api; +import org.apache.solr.api.JerseyResource; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.NodeConfig; import org.apache.solr.handler.RequestHandlerBase; -import org.apache.solr.handler.admin.api.NodePropertiesAPI; +import org.apache.solr.handler.admin.api.NodeProperties; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; @@ -82,7 +83,12 @@ public Category getCategory() { @Override public Collection getApis() { - return AnnotatedApi.getApis(new NodePropertiesAPI(this)); + return List.of(); + } + + @Override + public Collection> getJerseyResources() { + return List.of(NodeProperties.class); } @Override diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java index eb0079b50802..09ff42c8810a 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java @@ -43,8 +43,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.lucene.util.Version; -import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; +import org.apache.solr.api.JerseyResource; import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.util.EnvUtils; import org.apache.solr.common.util.SimpleOrderedMap; @@ -52,7 +52,7 @@ import org.apache.solr.core.NodeConfig; import org.apache.solr.core.SolrCore; import org.apache.solr.handler.RequestHandlerBase; -import org.apache.solr.handler.admin.api.NodeSystemInfoAPI; +import org.apache.solr.handler.admin.api.NodeSystemInfo; import org.apache.solr.metrics.GpuMetricsProvider; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; @@ -470,7 +470,12 @@ private static List getInputArgumentsRedacted(NodeConfig nodeConfig, Run @Override public Collection getApis() { - return AnnotatedApi.getApis(new NodeSystemInfoAPI(this)); + return List.of(); + } + + @Override + public Collection> getJerseyResources() { + return List.of(NodeSystemInfo.class); } @Override diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java index 5d2832cd74eb..2e193b36c0b2 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java @@ -16,27 +16,18 @@ */ package org.apache.solr.handler.admin; -import static org.apache.solr.common.params.CommonParams.ID; -import static org.apache.solr.common.params.CommonParams.NAME; - import java.io.IOException; -import java.lang.management.LockInfo; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadInfo; -import java.lang.management.ThreadMXBean; -import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Locale; -import org.apache.solr.api.AnnotatedApi; import org.apache.solr.api.Api; -import org.apache.solr.common.util.NamedList; -import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.api.JerseyResource; import org.apache.solr.handler.RequestHandlerBase; -import org.apache.solr.handler.admin.api.NodeThreadsAPI; +import org.apache.solr.handler.admin.api.NodeThreads; +import org.apache.solr.handler.api.V2ApiUtils; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.PermissionNameProvider; /** * @since solr 1.2 @@ -45,122 +36,8 @@ public class ThreadDumpHandler extends RequestHandlerBase { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException { - SimpleOrderedMap system = new SimpleOrderedMap<>(); - rsp.add("system", system); - - ThreadMXBean tmbean = ManagementFactory.getThreadMXBean(); - - // Thread Count - SimpleOrderedMap nl = new SimpleOrderedMap<>(); - nl.add("current", tmbean.getThreadCount()); - nl.add("peak", tmbean.getPeakThreadCount()); - nl.add("daemon", tmbean.getDaemonThreadCount()); - system.add("threadCount", nl); - - // Deadlocks - ThreadInfo[] tinfos; - long[] tids = tmbean.findDeadlockedThreads(); - if (tids != null) { - tinfos = tmbean.getThreadInfo(tids, Integer.MAX_VALUE); - NamedList> lst = new NamedList<>(); - for (ThreadInfo ti : tinfos) { - if (ti != null) { - lst.add("thread", getThreadInfo(ti, tmbean)); - } - } - system.add("deadlocks", lst); - } - - // Now show all the threads.... - - tinfos = tmbean.dumpAllThreads(true, true); - NamedList> lst = new NamedList<>(); - for (ThreadInfo ti : tinfos) { - if (ti != null) { - lst.add("thread", getThreadInfo(ti, tmbean)); - } - } - system.add("threadDump", lst); rsp.setHttpCaching(false); - } - - // -------------------------------------------------------------------------------- - // -------------------------------------------------------------------------------- - - private static SimpleOrderedMap getThreadInfo(ThreadInfo ti, ThreadMXBean tmbean) { - SimpleOrderedMap info = new SimpleOrderedMap<>(); - long tid = ti.getThreadId(); - - info.add(ID, tid); - info.add(NAME, ti.getThreadName()); - info.add("state", ti.getThreadState().toString()); - - if (ti.getLockName() != null) { - // TODO: this is redundent with lock-waiting below .. deprecate & remove - // TODO: (but first needs UI change) - info.add("lock", ti.getLockName()); - } - { - final LockInfo lockInfo = ti.getLockInfo(); - if (null != lockInfo) { - final SimpleOrderedMap lock = new SimpleOrderedMap<>(); - info.add("lock-waiting", lock); - lock.add(NAME, lockInfo.toString()); - if (-1 == ti.getLockOwnerId() && null == ti.getLockOwnerName()) { - lock.add("owner", null); - } else { - final SimpleOrderedMap owner = new SimpleOrderedMap<>(); - lock.add("owner", owner); - owner.add(NAME, ti.getLockOwnerName()); - owner.add(ID, ti.getLockOwnerId()); - } - } - } - { - final LockInfo[] synchronizers = ti.getLockedSynchronizers(); - if (0 < synchronizers.length) { - final List locks = new ArrayList<>(synchronizers.length); - info.add("synchronizers-locked", locks); - for (LockInfo sync : synchronizers) { - locks.add(sync.toString()); - } - } - } - { - final LockInfo[] monitors = ti.getLockedMonitors(); - if (0 < monitors.length) { - final List locks = new ArrayList<>(monitors.length); - info.add("monitors-locked", locks); - for (LockInfo monitor : monitors) { - locks.add(monitor.toString()); - } - } - } - - if (ti.isSuspended()) { - info.add("suspended", true); - } - if (ti.isInNative()) { - info.add("native", true); - } - - if (tmbean.isThreadCpuTimeSupported()) { - info.add("cpuTime", formatNanos(tmbean.getThreadCpuTime(tid))); - info.add("userTime", formatNanos(tmbean.getThreadUserTime(tid))); - } - - // Add the stack trace - int i = 0; - String[] trace = new String[ti.getStackTrace().length]; - for (StackTraceElement ste : ti.getStackTrace()) { - trace[i++] = ste.toString(); - } - info.add("stackTrace", trace); - return info; - } - - private static String formatNanos(long ns) { - return String.format(Locale.ROOT, "%.4fms", ns / (double) 1000000); + V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, new NodeThreads().getThreads()); } //////////////////////// SolrInfoMBeans methods ////////////////////// @@ -177,7 +54,12 @@ public Category getCategory() { @Override public Collection getApis() { - return AnnotatedApi.getApis(new NodeThreadsAPI(this)); + return List.of(); + } + + @Override + public Collection> getJerseyResources() { + return List.of(NodeThreads.class); } @Override @@ -186,7 +68,7 @@ public Boolean registerV2() { } @Override - public Name getPermissionName(AuthorizationContext request) { - return Name.METRICS_READ_PERM; + public PermissionNameProvider.Name getPermissionName(AuthorizationContext request) { + return PermissionNameProvider.Name.METRICS_READ_PERM; } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/Cluster.java b/solr/core/src/java/org/apache/solr/handler/admin/api/Cluster.java new file mode 100644 index 000000000000..c1dc06e4eed4 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/Cluster.java @@ -0,0 +1,230 @@ +/* + * 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.api; + +import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID; +import static org.apache.solr.common.params.CollectionParams.ACTION; +import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE; +import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESTATUS; +import static org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS; +import static org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE; +import static org.apache.solr.common.params.CollectionParams.CollectionAction.REQUESTSTATUS; +import static org.apache.solr.core.RateLimiterConfig.RL_CONFIG_KEY; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.client.api.endpoint.ClusterApis; +import org.apache.solr.client.api.model.AddRoleRequestBody; +import org.apache.solr.client.api.model.FlexibleSolrJerseyResponse; +import org.apache.solr.client.api.model.RateLimiterPayload; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.cloud.ClusterProperties; +import org.apache.solr.common.cloud.ZkNodeProps; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.ClusterAPI; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for cluster-level operations. + * + *

These APIs (/api/cluster and sub-paths) are analogous to the v1 /admin/collections endpoint + * for cluster operations. + */ +public class Cluster extends AdminAPIBase implements ClusterApis { + + @Inject + public Cluster( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(COLL_READ_PERM) + public SolrJerseyResponse getClusterStatus() throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + coreContainer + .getCollectionsHandler() + .handleRequestBody( + ClusterAPI.wrapParams( + solrQueryRequest, + Map.of( + CommonParams.ACTION, + CollectionParams.CollectionAction.CLUSTERSTATUS.toLower())), + solrQueryResponse); + copyFromResponse(response, solrQueryResponse); + return response; + } + + @Override + @PermissionName(COLL_READ_PERM) + public SolrJerseyResponse getNodes() throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + response.setUnknownProperty( + "nodes", coreContainer.getZkController().getClusterState().getLiveNodes()); + return response; + } + + @Override + @PermissionName(COLL_READ_PERM) + public SolrJerseyResponse getOverseerStatus() throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + coreContainer + .getCollectionsHandler() + .handleRequestBody( + ClusterAPI.wrapParams(solrQueryRequest, ACTION, OVERSEERSTATUS.lowerName), + solrQueryResponse); + copyFromResponse(response, solrQueryResponse); + return response; + } + + @Override + @PermissionName(COLL_READ_PERM) + public SolrJerseyResponse getCommandStatus(String requestId) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + coreContainer + .getCollectionsHandler() + .handleRequestBody( + ClusterAPI.wrapParams( + solrQueryRequest, Map.of(ACTION, REQUESTSTATUS.lowerName, REQUESTID, requestId)), + solrQueryResponse); + copyFromResponse(response, solrQueryResponse); + return response; + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse deleteCommandStatus(String requestId) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + coreContainer + .getCollectionsHandler() + .handleRequestBody( + ClusterAPI.wrapParams( + solrQueryRequest, Map.of(ACTION, DELETESTATUS.lowerName, REQUESTID, requestId)), + solrQueryResponse); + copyFromResponse(response, solrQueryResponse); + return response; + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse flushCommandStatus() throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + coreContainer + .getCollectionsHandler() + .handleRequestBody( + ClusterAPI.wrapParams( + solrQueryRequest, + Map.of( + ACTION, + DELETESTATUS.lowerName, + org.apache.solr.common.params.CollectionAdminParams.FLUSH, + "true")), + solrQueryResponse); + if (solrQueryResponse.getException() != null) { + throw solrQueryResponse.getException(); + } + return response; + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse addRole(AddRoleRequestBody requestBody) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + ensureRequiredRequestBodyProvided(requestBody); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + Map props = new HashMap<>(); + props.put("node", requestBody.node); + props.put("role", requestBody.role); + submitRemoteMessageAndHandleException(response, ADDROLE, new ZkNodeProps(props)); + return response; + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse removeRole(AddRoleRequestBody requestBody) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + ensureRequiredRequestBodyProvided(requestBody); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + Map props = new HashMap<>(); + props.put("node", requestBody.node); + props.put("role", requestBody.role); + submitRemoteMessageAndHandleException(response, REMOVEROLE, new ZkNodeProps(props)); + return response; + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse setRateLimiters(RateLimiterPayload requestBody) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + if (requestBody == null) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing request body"); + } + ClusterProperties clusterProperties = + new ClusterProperties(coreContainer.getZkController().getZkClient()); + Map configMap = new HashMap<>(); + if (requestBody.enabled != null) configMap.put("enabled", requestBody.enabled); + if (requestBody.guaranteedSlots != null) + configMap.put("guaranteedSlots", requestBody.guaranteedSlots); + if (requestBody.allowedRequests != null) + configMap.put("allowedRequests", requestBody.allowedRequests); + if (requestBody.slotBorrowingEnabled != null) + configMap.put("slotBorrowingEnabled", requestBody.slotBorrowingEnabled); + if (requestBody.slotAcquisitionTimeoutInMS != null) + configMap.put("slotAcquisitionTimeoutInMS", requestBody.slotAcquisitionTimeoutInMS); + try { + clusterProperties.setClusterProperty(RL_CONFIG_KEY, configMap); + } catch (Exception e) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e); + } + return response; + } + + private void copyFromResponse(FlexibleSolrJerseyResponse response, SolrQueryResponse rsp) + throws Exception { + if (rsp.getException() != null) { + throw rsp.getException(); + } + for (Map.Entry entry : rsp.getValues()) { + if (!entry.getKey().equals("responseHeader")) { + response.setUnknownProperty(entry.getKey(), entry.getValue()); + } + } + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterNodeRoles.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterNodeRoles.java new file mode 100644 index 000000000000..e2e83043d630 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterNodeRoles.java @@ -0,0 +1,165 @@ +/* + * 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.api; + +import jakarta.inject.Inject; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.solr.client.api.endpoint.ClusterNodeRolesApis; +import org.apache.solr.client.api.model.FlexibleSolrJerseyResponse; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.solrj.cloud.DistribStateManager; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.NodeRoles; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.security.PermissionNameProvider; + +/** + * V2 API implementation for reading node roles in a Solr cluster. + * + *

These APIs (/api/cluster/node-roles and sub-paths) are analogous to v1 cluster node-role + * operations. + */ +public class ClusterNodeRoles extends AdminAPIBase implements ClusterNodeRolesApis { + + @Inject + public ClusterNodeRoles( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(PermissionNameProvider.Name.COLL_READ_PERM) + public SolrJerseyResponse getAllNodeRoles() throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + response.setUnknownProperty( + "node-roles", + readRecursive( + ZkStateReader.NODE_ROLES, + coreContainer.getZkController().getSolrCloudManager().getDistribStateManager(), + 3)); + return response; + } + + @Override + @PermissionName(PermissionNameProvider.Name.COLL_READ_PERM) + public SolrJerseyResponse getSupportedRoles() throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + Map roleModesSupportedMap = new HashMap<>(); + for (NodeRoles.Role role : NodeRoles.Role.values()) { + roleModesSupportedMap.put(role.toString(), Map.of("modes", role.supportedModes())); + } + response.setUnknownProperty("supported-roles", roleModesSupportedMap); + return response; + } + + @Override + @PermissionName(PermissionNameProvider.Name.COLL_READ_PERM) + public SolrJerseyResponse getNodesForRole(String role) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + response.setUnknownProperty( + "node-roles", + Map.of( + role, + readRecursive( + ZkStateReader.NODE_ROLES + "/" + role, + coreContainer.getZkController().getSolrCloudManager().getDistribStateManager(), + 2))); + return response; + } + + @Override + @PermissionName(PermissionNameProvider.Name.COLL_READ_PERM) + public SolrJerseyResponse getNodesForRoleAndMode(String role, String mode) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + List nodes = + coreContainer + .getZkController() + .getSolrCloudManager() + .getDistribStateManager() + .listData(ZkStateReader.NODE_ROLES + "/" + role + "/" + mode); + response.setUnknownProperty("node-roles", Map.of(role, Collections.singletonMap(mode, nodes))); + return response; + } + + @Override + @PermissionName(PermissionNameProvider.Name.COLL_READ_PERM) + @SuppressWarnings("unchecked") + public SolrJerseyResponse getRolesForNode(String node) throws Exception { + fetchAndValidateZooKeeperAwareCoreContainer(); + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + Map>> roles = + (Map>>) + readRecursive( + ZkStateReader.NODE_ROLES, + coreContainer.getZkController().getSolrCloudManager().getDistribStateManager(), + 3); + for (String role : roles.keySet()) { + for (String mode : roles.get(role).keySet()) { + if (roles.get(role).get(mode).isEmpty()) continue; + Set nodes = roles.get(role).get(mode); + if (nodes.contains(node)) { + response.setUnknownProperty(role, mode); + } + } + } + return response; + } + + static Object readRecursive(String path, DistribStateManager zk, int depth) { + if (depth == 0) return null; + Map result; + try { + List children = zk.listData(path); + if (children != null && !children.isEmpty()) { + result = new HashMap<>(); + } else { + return Collections.emptySet(); + } + for (String child : children) { + Object c = readRecursive(path + "/" + child, zk, depth - 1); + result.put(child, c); + } + } catch (Exception e) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); + } + if (depth == 1) { + return result.keySet(); + } else { + return result; + } + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateDocs.java b/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateDocs.java new file mode 100644 index 000000000000..efc807ae21ee --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateDocs.java @@ -0,0 +1,75 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.client.api.endpoint.MigrateDocsApi; +import org.apache.solr.client.api.model.MigrateDocsRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for migrating documents from one collection to another. + * + *

This API (POST /v2/collections/collectionName/migrate) is analogous to the v1 + * /admin/collections?action=MIGRATE command. + */ +public class MigrateDocs extends AdminAPIBase implements MigrateDocsApi { + + @Inject + public MigrateDocs( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse migrateDocs(String collectionName, MigrateDocsRequestBody requestBody) + throws Exception { + ensureRequiredParameterProvided(COLLECTION, collectionName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, CollectionParams.CollectionAction.MIGRATE.toLower()); + v1Params.put(COLLECTION, collectionName); + if (requestBody != null) { + if (requestBody.target != null) v1Params.put("target.collection", requestBody.target); + if (requestBody.splitKey != null) v1Params.put("split.key", requestBody.splitKey); + if (requestBody.forwardTimeout != null) + v1Params.put("forward.timeout", requestBody.forwardTimeout); + if (requestBody.followAliases != null) + v1Params.put("followAliases", requestBody.followAliases); + if (requestBody.async != null) v1Params.put("async", requestBody.async); + } + coreContainer + .getCollectionsHandler() + .handleRequestBody(wrapParams(solrQueryRequest, v1Params), solrQueryResponse); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateDocsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateDocsAPI.java deleted file mode 100644 index 7e3898c8a899..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateDocsAPI.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; - -import java.util.HashMap; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.MigrateDocsPayload; -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.handler.admin.CollectionsHandler; - -/** - * V2 API for migrating docs from one collection to another. - * - *

The new API (POST /v2/collections/collectionName {'migrate-docs': {...}}) is analogous to the - * v1 /admin/collections?action=MIGRATE command. - * - * @see MigrateDocsPayload - */ -@EndPoint( - path = {"/c/{collection}", "/collections/{collection}"}, - method = POST, - permission = COLL_EDIT_PERM) -public class MigrateDocsAPI { - private static final String V2_MIGRATE_DOCS_CMD = "migrate-docs"; - - private final CollectionsHandler collectionsHandler; - - public MigrateDocsAPI(CollectionsHandler collectionsHandler) { - this.collectionsHandler = collectionsHandler; - } - - @Command(name = V2_MIGRATE_DOCS_CMD) - public void migrateDocs(PayloadObj obj) throws Exception { - final MigrateDocsPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, CollectionParams.CollectionAction.MIGRATE.toLower()); - v1Params.put(COLLECTION, obj.getRequest().getPathTemplateValues().get(COLLECTION)); - - if (v2Body.splitKey != null) { - v1Params.remove("splitKey"); - v1Params.put("split.key", v2Body.splitKey); - } - if (v2Body.target != null) { - v1Params.remove("target"); - v1Params.put("target.collection", v2Body.target); - } - if (v2Body.forwardTimeout != null) { - v1Params.remove("forwardTimeout"); - v1Params.put("forward.timeout", v2Body.forwardTimeout); - } - - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyCollection.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyCollection.java new file mode 100644 index 000000000000..170173d8abe0 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyCollection.java @@ -0,0 +1,78 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; +import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF; +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.handler.api.V2ApiUtils.flattenMapWithPrefix; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.client.api.endpoint.ModifyCollectionApi; +import org.apache.solr.client.api.model.ModifyCollectionRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for modifying a collection's configuration. + * + *

This API (POST /v2/collections/collectionName/modify) is analogous to the v1 + * /admin/collections?action=MODIFYCOLLECTION command. + */ +public class ModifyCollection extends AdminAPIBase implements ModifyCollectionApi { + + @Inject + public ModifyCollection( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse modifyCollection( + String collectionName, ModifyCollectionRequestBody requestBody) throws Exception { + ensureRequiredParameterProvided(COLLECTION, collectionName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, CollectionParams.CollectionAction.MODIFYCOLLECTION.toLower()); + v1Params.put(COLLECTION, collectionName); + if (requestBody != null) { + if (requestBody.replicationFactor != null) + v1Params.put("replicationFactor", requestBody.replicationFactor); + if (requestBody.readOnly != null) v1Params.put("readOnly", requestBody.readOnly); + if (requestBody.config != null) v1Params.put(COLL_CONF, requestBody.config); + if (requestBody.properties != null && !requestBody.properties.isEmpty()) { + flattenMapWithPrefix(requestBody.properties, v1Params, "property."); + } + if (requestBody.async != null) v1Params.put("async", requestBody.async); + } + coreContainer + .getCollectionsHandler() + .handleRequestBody(wrapParams(solrQueryRequest, v1Params), solrQueryResponse); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyCollectionAPI.java deleted file mode 100644 index 93d11d0c4283..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/ModifyCollectionAPI.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; -import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.handler.api.V2ApiUtils.flattenMapWithPrefix; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; - -import java.util.HashMap; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.ModifyCollectionPayload; -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.handler.admin.CollectionsHandler; - -/** - * V2 API for modifying collections. - * - *

The new API (POST /v2/collections/collectionName {'modify-collection': {...}}) is equivalent - * to the v1 /admin/collections?action=MODIFYCOLLECTION command. - * - * @see ModifyCollectionPayload - */ -@EndPoint( - path = {"/c/{collection}", "/collections/{collection}"}, - method = POST, - permission = COLL_EDIT_PERM) -public class ModifyCollectionAPI { - private static final String V2_MODIFY_COLLECTION_CMD = "modify"; - - private final CollectionsHandler collectionsHandler; - - public ModifyCollectionAPI(CollectionsHandler collectionsHandler) { - this.collectionsHandler = collectionsHandler; - } - - @Command(name = V2_MODIFY_COLLECTION_CMD) - public void modifyCollection(PayloadObj obj) throws Exception { - final ModifyCollectionPayload v2Body = obj.get(); - - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, CollectionParams.CollectionAction.MODIFYCOLLECTION.toLower()); - v1Params.put(COLLECTION, obj.getRequest().getPathTemplateValues().get(COLLECTION)); - if (v2Body.config != null) { - v1Params.remove("config"); - v1Params.put(COLL_CONF, v2Body.config); - } - if (v2Body.properties != null && !v2Body.properties.isEmpty()) { - v1Params.remove("properties"); - flattenMapWithPrefix(v2Body.properties, v1Params, "property."); - } - - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/MoveReplica.java b/solr/core/src/java/org/apache/solr/handler/admin/api/MoveReplica.java new file mode 100644 index 000000000000..a3af35439d20 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/MoveReplica.java @@ -0,0 +1,78 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.client.api.endpoint.MoveReplicaApi; +import org.apache.solr.client.api.model.MoveReplicaRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for moving a collection replica to a different node. + * + *

This API (POST /v2/collections/collectionName/move-replica) is analogous to the v1 + * /admin/collections?action=MOVEREPLICA command. + */ +public class MoveReplica extends AdminAPIBase implements MoveReplicaApi { + + @Inject + public MoveReplica( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse moveReplica(String collectionName, MoveReplicaRequestBody requestBody) + throws Exception { + ensureRequiredParameterProvided(COLLECTION, collectionName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, CollectionParams.CollectionAction.MOVEREPLICA.toLower()); + v1Params.put(COLLECTION, collectionName); + if (requestBody != null) { + if (requestBody.targetNode != null) v1Params.put("targetNode", requestBody.targetNode); + if (requestBody.replica != null) v1Params.put("replica", requestBody.replica); + if (requestBody.shard != null) v1Params.put("shard", requestBody.shard); + if (requestBody.sourceNode != null) v1Params.put("sourceNode", requestBody.sourceNode); + if (requestBody.waitForFinalState != null) + v1Params.put("waitForFinalState", requestBody.waitForFinalState); + if (requestBody.timeout != null) v1Params.put("timeout", requestBody.timeout); + if (requestBody.inPlaceMove != null) v1Params.put("inPlaceMove", requestBody.inPlaceMove); + if (requestBody.followAliases != null) + v1Params.put("followAliases", requestBody.followAliases); + } + coreContainer + .getCollectionsHandler() + .handleRequestBody(wrapParams(solrQueryRequest, v1Params), solrQueryResponse); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/MoveReplicaAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/MoveReplicaAPI.java deleted file mode 100644 index 3c0d701de302..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/MoveReplicaAPI.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; - -import java.util.HashMap; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.MoveReplicaPayload; -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.handler.admin.CollectionsHandler; - -/** - * V2 API for moving a collection replica to a different physical node. - * - *

The new API (POST /v2/collections/collectionName {'move-replica': {...}}) is analogous to the - * v1 /admin/collections?action=MOVEREPLICA command. - * - * @see MoveReplicaPayload - */ -@EndPoint( - path = {"/c/{collection}", "/collections/{collection}"}, - method = POST, - permission = COLL_EDIT_PERM) -public class MoveReplicaAPI { - private static final String V2_MOVE_REPLICA_CMD = "move-replica"; - - private final CollectionsHandler collectionsHandler; - - public MoveReplicaAPI(CollectionsHandler collectionsHandler) { - this.collectionsHandler = collectionsHandler; - } - - @Command(name = V2_MOVE_REPLICA_CMD) - public void moveReplica(PayloadObj obj) throws Exception { - final MoveReplicaPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, CollectionParams.CollectionAction.MOVEREPLICA.toLower()); - v1Params.put(COLLECTION, obj.getRequest().getPathTemplateValues().get(COLLECTION)); - - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealth.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealth.java new file mode 100644 index 000000000000..e3b5a033da9b --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealth.java @@ -0,0 +1,257 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonParams.FAILURE; +import static org.apache.solr.common.params.CommonParams.OK; + +import jakarta.inject.Inject; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.NodeHealthApi; +import org.apache.solr.client.api.model.NodeHealthResponse; +import org.apache.solr.cloud.CloudDescriptor; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.cloud.ClusterState; +import org.apache.solr.common.cloud.Replica.State; +import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.SolrCore; +import org.apache.solr.handler.IndexFetcher; +import org.apache.solr.handler.ReplicationHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.security.PermissionNameProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * V2 API implementation for checking the health of the receiving node. + * + *

This API (GET /v2/node/health) is analogous to the v1 /admin/info/health. + */ +public class NodeHealth extends JerseyResource implements NodeHealthApi { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static final List UNHEALTHY_STATES = Arrays.asList(State.DOWN, State.RECOVERING); + + private final CoreContainer coreContainer; + + @Inject + public NodeHealth(CoreContainer coreContainer) { + this.coreContainer = coreContainer; + } + + @Override + @PermissionName(PermissionNameProvider.Name.HEALTH_PERM) + public NodeHealthResponse getHealth(Boolean requireHealthyCores, Integer maxGenerationLag) + throws Exception { + final NodeHealthResponse response = instantiateJerseyResponse(NodeHealthResponse.class); + + if (coreContainer == null || coreContainer.isShutDown()) { + throw new SolrException( + SolrException.ErrorCode.SERVER_ERROR, + "CoreContainer is either not initialized or shutting down"); + } + + if (!coreContainer.isZooKeeperAware()) { + if (log.isDebugEnabled()) { + log.debug("Invoked HealthCheckHandler in legacy mode."); + } + healthCheckLegacyMode(response, maxGenerationLag); + } else { + if (log.isDebugEnabled()) { + log.debug( + "Invoked HealthCheckHandler in cloud mode on [{}]", + coreContainer.getZkController().getNodeName()); + } + healthCheckCloudMode(response, requireHealthyCores != null && requireHealthyCores); + } + + return response; + } + + private void healthCheckCloudMode(NodeHealthResponse response, boolean requireHealthyCores) { + ZkStateReader zkStateReader = coreContainer.getZkController().getZkStateReader(); + ClusterState clusterState = zkStateReader.getClusterState(); + // Check for isConnected and isClosed + if (zkStateReader.getZkClient().isClosed() || !zkStateReader.getZkClient().isConnected()) { + throw new SolrException( + SolrException.ErrorCode.SERVICE_UNAVAILABLE, "Host Unavailable: Not connected to zk"); + } + + // Fail if not in live_nodes + if (!clusterState.getLiveNodes().contains(coreContainer.getZkController().getNodeName())) { + throw new SolrException( + SolrException.ErrorCode.SERVICE_UNAVAILABLE, + "Host Unavailable: Not in live nodes as per zk"); + } + + // Optionally require that all cores on this node are active if param 'requireHealthyCores=true' + if (requireHealthyCores) { + if (!coreContainer.isStatusLoadComplete()) { + throw new SolrException( + SolrException.ErrorCode.SERVICE_UNAVAILABLE, + "Host Unavailable: Core Loading not complete"); + } + Collection coreDescriptors = + coreContainer.getCoreDescriptors().stream() + .map(cd -> cd.getCloudDescriptor()) + .collect(Collectors.toList()); + long unhealthyCores = findUnhealthyCores(coreDescriptors, clusterState); + if (unhealthyCores > 0) { + response.numCoresUnhealthy = unhealthyCores; + throw new SolrException( + SolrException.ErrorCode.SERVICE_UNAVAILABLE, + unhealthyCores + + " out of " + + coreContainer.getNumAllCores() + + " replicas are currently initializing or recovering"); + } + response.message = "All cores are healthy"; + } + + // All lights green, report healthy + response.status = OK; + } + + private void healthCheckLegacyMode(NodeHealthResponse response, Integer maxGenerationLag) { + List laggingCoresInfo = new ArrayList<>(); + boolean allCoresAreInSync = true; + + // check only if max generation lag is specified + if (maxGenerationLag != null) { + // if is not negative + if (maxGenerationLag < 0) { + log.error("Invalid value for maxGenerationLag:[{}]", maxGenerationLag); + response.message = + String.format(Locale.ROOT, "Invalid value of maxGenerationLag:%s", maxGenerationLag); + response.status = FAILURE; + } else { + for (SolrCore core : coreContainer.getCores()) { + ReplicationHandler replicationHandler = + (ReplicationHandler) core.getRequestHandler(ReplicationHandler.PATH); + if (replicationHandler.isFollower()) { + boolean isCoreInSync = + isWithinGenerationLag(core, replicationHandler, maxGenerationLag, laggingCoresInfo); + + allCoresAreInSync &= isCoreInSync; + } + } + } + if (allCoresAreInSync) { + response.message = + String.format( + Locale.ROOT, + "All the followers are in sync with leader (within maxGenerationLag: %d) " + + "or the cores are acting as leader", + maxGenerationLag); + response.status = OK; + } else { + response.message = + String.format( + Locale.ROOT, + "Cores violating maxGenerationLag:%d.%n%s", + maxGenerationLag, + String.join(",\n", laggingCoresInfo)); + response.status = FAILURE; + } + } else { // if maxGeneration lag is not specified (is null) we aren't checking for lag + response.message = + "maxGenerationLag isn't specified. Followers aren't " + + "checking for the generation lag from the leaders"; + response.status = OK; + } + } + + private boolean isWithinGenerationLag( + final SolrCore core, + ReplicationHandler replicationHandler, + int maxGenerationLag, + List laggingCoresInfo) { + IndexFetcher indexFetcher = null; + try { + // may not be the best way to get leader's replicableCommit + var follower = + (org.apache.solr.common.util.NamedList) + replicationHandler.getInitArgs().get("follower"); + + indexFetcher = new IndexFetcher(follower, replicationHandler, core); + + var replicableCommitOnLeader = indexFetcher.getLatestVersion(); + long leaderGeneration = (Long) replicableCommitOnLeader.get(ReplicationAPIBase.GENERATION); + + // Get our own commit and generation from the commit + org.apache.lucene.index.IndexCommit commit = core.getDeletionPolicy().getLatestCommit(); + if (commit != null) { + long followerGeneration = commit.getGeneration(); + long generationDiff = leaderGeneration - followerGeneration; + + if (generationDiff < 0) { + log.warn("core:[{}], generation lag:[{}] is negative.", core, generationDiff); + } else if (generationDiff < maxGenerationLag) { + log.info( + "core:[{}] generation lag is above acceptable threshold:[{}], " + + "generation lag:[{}], leader generation:[{}], follower generation:[{}]", + core, + maxGenerationLag, + generationDiff, + leaderGeneration, + followerGeneration); + + laggingCoresInfo.add( + String.format( + Locale.ROOT, + "Core %s is lagging by %d generations", + core.getName(), + generationDiff)); + return true; + } + } + } catch (Exception e) { + log.error("Failed to check if the follower is in sync with the leader", e); + } finally { + if (indexFetcher != null) { + indexFetcher.destroy(); + } + } + return false; + } + + /** + * Find replicas DOWN or RECOVERING, or replicas in clusterstate that do not exist on local node. + */ + public static long findUnhealthyCores( + Collection cores, ClusterState clusterState) { + return cores.stream() + .filter(c -> !c.hasRegistered() || UNHEALTHY_STATES.contains(c.getLastPublished())) + .filter(c -> clusterState.hasCollection(c.getCollectionName())) + .filter( + c -> + clusterState + .getCollection(c.getCollectionName()) + .getActiveSlicesMap() + .containsKey(c.getShardId())) + .count(); + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java deleted file mode 100644 index df5f64900f03..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeHealthAPI.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; -import static org.apache.solr.security.PermissionNameProvider.Name.HEALTH_PERM; - -import org.apache.solr.api.EndPoint; -import org.apache.solr.handler.admin.HealthCheckHandler; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.SolrQueryResponse; - -/** - * V2 API for checking the health of the receiving node. - * - *

This API (GET /v2/node/health) is analogous to the v1 /admin/info/health. - */ -public class NodeHealthAPI { - private final HealthCheckHandler handler; - - public NodeHealthAPI(HealthCheckHandler handler) { - this.handler = handler; - } - - // TODO Update permission here once SOLR-11623 lands. - @EndPoint( - path = {"/node/health"}, - method = GET, - permission = HEALTH_PERM) - public void getSystemInformation(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - handler.handleRequestBody(req, rsp); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeProperties.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeProperties.java new file mode 100644 index 000000000000..b81a8c2351f8 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeProperties.java @@ -0,0 +1,62 @@ +/* + * 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.api; + +import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM; + +import jakarta.inject.Inject; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.NodePropertiesApi; +import org.apache.solr.client.api.model.NodePropertiesResponse; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; + +/** + * V2 API implementation for listing system properties on a node. + * + *

This API (GET /v2/node/properties) is analogous to the v1 /admin/info/properties. + */ +public class NodeProperties extends JerseyResource implements NodePropertiesApi { + + private final CoreContainer coreContainer; + + @Inject + public NodeProperties(CoreContainer coreContainer) { + this.coreContainer = coreContainer; + } + + @Override + @PermissionName(CONFIG_READ_PERM) + public NodePropertiesResponse getProperties(String name) throws Exception { + final NodePropertiesResponse response = instantiateJerseyResponse(NodePropertiesResponse.class); + final Map props = new LinkedHashMap<>(); + if (name != null) { + props.put(name, coreContainer.getNodeConfig().getRedactedSysPropValue(name)); + } else { + final Enumeration propertyNames = System.getProperties().propertyNames(); + while (propertyNames.hasMoreElements()) { + final String propName = (String) propertyNames.nextElement(); + props.put(propName, coreContainer.getNodeConfig().getRedactedSysPropValue(propName)); + } + } + response.systemProperties = props; + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java deleted file mode 100644 index d9cf81f8a8b1..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/NodePropertiesAPI.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; -import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM; - -import org.apache.solr.api.EndPoint; -import org.apache.solr.handler.admin.PropertiesRequestHandler; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.SolrQueryResponse; - -/** - * V2 API for listing system properties for each node. - * - *

This API (GET /v2/node/properties) is analogous to the v1 /admin/info/properties. - */ -public class NodePropertiesAPI { - private final PropertiesRequestHandler handler; - - public NodePropertiesAPI(PropertiesRequestHandler handler) { - this.handler = handler; - } - - @EndPoint( - path = {"/node/properties"}, - method = GET, - permission = CONFIG_READ_PERM) - public void getRequestedProperties(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - handler.handleRequestBody(req, rsp); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfo.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfo.java new file mode 100644 index 000000000000..a144618a08c8 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfo.java @@ -0,0 +1,72 @@ +/* + * 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.api; + +import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM; + +import jakarta.inject.Inject; +import java.util.Map; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.NodeSystemInfoApi; +import org.apache.solr.client.api.model.FlexibleSolrJerseyResponse; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for retrieving system information from a node. + * + *

This API (GET /v2/node/system) is analogous to the v1 /admin/info/system. + */ +public class NodeSystemInfo extends JerseyResource implements NodeSystemInfoApi { + + private final CoreContainer coreContainer; + private final SolrQueryRequest solrQueryRequest; + private final SolrQueryResponse solrQueryResponse; + + @Inject + public NodeSystemInfo( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + this.coreContainer = coreContainer; + this.solrQueryRequest = solrQueryRequest; + this.solrQueryResponse = solrQueryResponse; + } + + @Override + @PermissionName(CONFIG_READ_PERM) + public SolrJerseyResponse getSystemInfo() throws Exception { + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + coreContainer + .getInfoHandler() + .getSystemInfoHandler() + .handleRequestBody(solrQueryRequest, solrQueryResponse); + if (solrQueryResponse.getException() != null) { + throw solrQueryResponse.getException(); + } + for (Map.Entry entry : solrQueryResponse.getValues()) { + if (!entry.getKey().equals("responseHeader")) { + response.setUnknownProperty(entry.getKey(), entry.getValue()); + } + } + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java deleted file mode 100644 index d7df2cfa0cd2..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeSystemInfoAPI.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; -import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM; - -import org.apache.solr.api.EndPoint; -import org.apache.solr.handler.admin.SystemInfoHandler; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.SolrQueryResponse; - -/** - * V2 API for getting "system" information from the receiving node. - * - *

This includes current resource utilization, information about the installation (location, - * version, etc.), and JVM settings. - * - *

This API (GET /v2/node/system) is analogous to the v1 /admin/info/system. - */ -public class NodeSystemInfoAPI { - private final SystemInfoHandler handler; - - public NodeSystemInfoAPI(SystemInfoHandler handler) { - this.handler = handler; - } - - @EndPoint( - path = {"/node/system"}, - method = GET, - permission = CONFIG_READ_PERM) - public void getSystemInformation(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - handler.handleRequestBody(req, rsp); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreads.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreads.java new file mode 100644 index 000000000000..cb8baa12ad47 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreads.java @@ -0,0 +1,163 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonParams.ID; +import static org.apache.solr.common.params.CommonParams.NAME; + +import java.lang.management.LockInfo; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.NodeThreadsApi; +import org.apache.solr.client.api.model.FlexibleSolrJerseyResponse; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.security.PermissionNameProvider; + +/** + * V2 API implementation for triggering a thread dump on the receiving node. + * + *

This API (GET /v2/node/threads) is analogous to the v1 /admin/info/threads. + */ +public class NodeThreads extends JerseyResource implements NodeThreadsApi { + + @Override + @PermissionName(PermissionNameProvider.Name.METRICS_READ_PERM) + public SolrJerseyResponse getThreads() { + final FlexibleSolrJerseyResponse response = + instantiateJerseyResponse(FlexibleSolrJerseyResponse.class); + + SimpleOrderedMap system = new SimpleOrderedMap<>(); + response.setUnknownProperty("system", system); + + ThreadMXBean tmbean = ManagementFactory.getThreadMXBean(); + + // Thread Count + SimpleOrderedMap nl = new SimpleOrderedMap<>(); + nl.add("current", tmbean.getThreadCount()); + nl.add("peak", tmbean.getPeakThreadCount()); + nl.add("daemon", tmbean.getDaemonThreadCount()); + system.add("threadCount", nl); + + // Deadlocks + long[] tids = tmbean.findDeadlockedThreads(); + if (tids != null) { + ThreadInfo[] tinfos = tmbean.getThreadInfo(tids, Integer.MAX_VALUE); + NamedList> lst = new NamedList<>(); + for (ThreadInfo ti : tinfos) { + if (ti != null) { + lst.add("thread", getThreadInfo(ti, tmbean)); + } + } + system.add("deadlocks", lst); + } + + // Now show all the threads.... + ThreadInfo[] tinfos = tmbean.dumpAllThreads(true, true); + NamedList> lst = new NamedList<>(); + for (ThreadInfo ti : tinfos) { + if (ti != null) { + lst.add("thread", getThreadInfo(ti, tmbean)); + } + } + system.add("threadDump", lst); + + return response; + } + + private static SimpleOrderedMap getThreadInfo(ThreadInfo ti, ThreadMXBean tmbean) { + SimpleOrderedMap info = new SimpleOrderedMap<>(); + long tid = ti.getThreadId(); + + info.add(ID, tid); + info.add(NAME, ti.getThreadName()); + info.add("state", ti.getThreadState().toString()); + + if (ti.getLockName() != null) { + info.add("lock", ti.getLockName()); + } + { + final LockInfo lockInfo = ti.getLockInfo(); + if (null != lockInfo) { + final SimpleOrderedMap lock = new SimpleOrderedMap<>(); + info.add("lock-waiting", lock); + lock.add(NAME, lockInfo.toString()); + if (-1 == ti.getLockOwnerId() && null == ti.getLockOwnerName()) { + lock.add("owner", null); + } else { + final SimpleOrderedMap owner = new SimpleOrderedMap<>(); + lock.add("owner", owner); + owner.add(NAME, ti.getLockOwnerName()); + owner.add(ID, ti.getLockOwnerId()); + } + } + } + { + final LockInfo[] synchronizers = ti.getLockedSynchronizers(); + if (0 < synchronizers.length) { + final List locks = new ArrayList<>(synchronizers.length); + info.add("synchronizers-locked", locks); + for (LockInfo sync : synchronizers) { + locks.add(sync.toString()); + } + } + } + { + final LockInfo[] monitors = ti.getLockedMonitors(); + if (0 < monitors.length) { + final List locks = new ArrayList<>(monitors.length); + info.add("monitors-locked", locks); + for (LockInfo monitor : monitors) { + locks.add(monitor.toString()); + } + } + } + + if (ti.isSuspended()) { + info.add("suspended", true); + } + if (ti.isInNative()) { + info.add("native", true); + } + + if (tmbean.isThreadCpuTimeSupported()) { + info.add("cpuTime", formatNanos(tmbean.getThreadCpuTime(tid))); + info.add("userTime", formatNanos(tmbean.getThreadUserTime(tid))); + } + + // Add the stack trace + int i = 0; + String[] trace = new String[ti.getStackTrace().length]; + for (StackTraceElement ste : ti.getStackTrace()) { + trace[i++] = ste.toString(); + } + info.add("stackTrace", trace); + return info; + } + + private static String formatNanos(long ns) { + return String.format(Locale.ROOT, "%.4fms", ns / (double) 1000000); + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreadsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreadsAPI.java deleted file mode 100644 index bea1827304cd..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeThreadsAPI.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET; -import static org.apache.solr.security.PermissionNameProvider.Name.METRICS_READ_PERM; - -import org.apache.solr.api.EndPoint; -import org.apache.solr.handler.admin.ThreadDumpHandler; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.SolrQueryResponse; - -/** - * V2 API for triggering a thread dump on the receiving node. - * - *

This API (GET /v2/node/threads) is analogous to the v1 /admin/info/threads. - */ -public class NodeThreadsAPI { - private final ThreadDumpHandler handler; - - public NodeThreadsAPI(ThreadDumpHandler handler) { - this.handler = handler; - } - - @EndPoint( - path = {"/node/threads"}, - method = GET, - permission = METRICS_READ_PERM) - public void triggerThreadDump(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { - handler.handleRequestBody(req, rsp); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperation.java b/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperation.java new file mode 100644 index 000000000000..14119d1e294c --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperation.java @@ -0,0 +1,76 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.OverseerOperationApi; +import org.apache.solr.client.api.model.OverseerOperationRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.params.CoreAdminParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for the overseer-op operation. + * + *

This API (POST /v2/node/overseer-op) is analogous to the v1 /admin/cores?action=overseerop + * command. + */ +public class OverseerOperation extends JerseyResource implements OverseerOperationApi { + + private final CoreContainer coreContainer; + private final SolrQueryRequest solrQueryRequest; + private final SolrQueryResponse solrQueryResponse; + + @Inject + public OverseerOperation( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + this.coreContainer = coreContainer; + this.solrQueryRequest = solrQueryRequest; + this.solrQueryResponse = solrQueryResponse; + } + + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse overseerOperation(OverseerOperationRequestBody requestBody) + throws Exception { + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + if (requestBody != null) { + if (requestBody.op != null) v1Params.put("op", requestBody.op); + if (requestBody.electionNode != null) v1Params.put("electionNode", requestBody.electionNode); + } + v1Params.put( + ACTION, CoreAdminParams.CoreAdminAction.OVERSEEROP.name().toLowerCase(Locale.ROOT)); + coreContainer + .getMultiCoreHandler() + .handleRequestBody(wrapParams(solrQueryRequest, v1Params), solrQueryResponse); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperationAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperationAPI.java deleted file mode 100644 index 1c566a8c610a..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/OverseerOperationAPI.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CoreAdminParams.ACTION; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.OverseerOperationPayload; -import org.apache.solr.common.params.CoreAdminParams; -import org.apache.solr.handler.admin.CoreAdminHandler; - -/** - * V2 API for triggering a node to rejoin leader election for the 'overseer' role. - * - *

This API (POST /v2/node {'overseer-op': {...}}) is analogous to the v1 - * /admin/cores?action=overseerop command. - * - * @see OverseerOperationPayload - */ -@EndPoint( - path = {"/node"}, - method = POST, - permission = CORE_EDIT_PERM) -public class OverseerOperationAPI { - - // TODO rename this command, this API doesn't really have anything to do with overseer-ops, its - // about leader election - public static final String OVERSEER_OP_CMD = "overseer-op"; - - private final CoreAdminHandler coreAdminHandler; - - public OverseerOperationAPI(CoreAdminHandler coreAdminHandler) { - this.coreAdminHandler = coreAdminHandler; - } - - @Command(name = OVERSEER_OP_CMD) - public void joinOverseerLeaderElection(PayloadObj payload) - throws Exception { - final Map v1Params = payload.get().toMap(new HashMap<>()); - v1Params.put( - ACTION, CoreAdminParams.CoreAdminAction.OVERSEEROP.name().toLowerCase(Locale.ROOT)); - coreAdminHandler.handleRequestBody( - wrapParams(payload.getRequest(), v1Params), payload.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/PrepareCoreRecovery.java b/solr/core/src/java/org/apache/solr/handler/admin/api/PrepareCoreRecovery.java new file mode 100644 index 000000000000..3a5e227f78ae --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/PrepareCoreRecovery.java @@ -0,0 +1,76 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CORE; +import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.PREPRECOVERY; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import org.apache.solr.client.api.endpoint.PrepareCoreRecoveryApi; +import org.apache.solr.client.api.model.PrepareCoreRecoveryRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.admin.CoreAdminHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for preparing a core for recovery. + * + *

This API (POST /v2/cores/coreName/prep-recovery) is analogous to the v1 + * /admin/cores?action=PREPRECOVERY command. + */ +public class PrepareCoreRecovery extends CoreAdminAPIBase implements PrepareCoreRecoveryApi { + + @Inject + public PrepareCoreRecovery( + CoreContainer coreContainer, + CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse prepareCoreForRecovery( + String coreName, PrepareCoreRecoveryRequestBody requestBody) throws Exception { + ensureRequiredParameterProvided(CORE, coreName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, PREPRECOVERY.name().toLowerCase(Locale.ROOT)); + v1Params.put(CORE, coreName); + if (requestBody != null) { + if (requestBody.nodeName != null) v1Params.put("nodeName", requestBody.nodeName); + if (requestBody.coreNodeName != null) v1Params.put("coreNodeName", requestBody.coreNodeName); + if (requestBody.state != null) v1Params.put("state", requestBody.state); + if (requestBody.checkLive != null) v1Params.put("checkLive", requestBody.checkLive); + if (requestBody.onlyIfLeader != null) v1Params.put("onlyIfLeader", requestBody.onlyIfLeader); + if (requestBody.onlyIfLeaderActive != null) + v1Params.put("onlyIfLeaderActive", requestBody.onlyIfLeaderActive); + } + coreContainer.getMultiCoreHandler().handleRequestBody(wrapParams(req, v1Params), rsp); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/PrepareCoreRecoveryAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/PrepareCoreRecoveryAPI.java deleted file mode 100644 index c5b830498158..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/PrepareCoreRecoveryAPI.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CoreAdminParams.ACTION; -import static org.apache.solr.common.params.CoreAdminParams.CORE; -import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.PREPRECOVERY; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.PrepareCoreRecoveryPayload; -import org.apache.solr.handler.admin.CoreAdminHandler; - -/** - * Internal V2 API used to prepare a core for recovery. - * - *

Only valid in SolrCloud mode. This API (POST /v2/cores/coreName {'prep-recovery': {...}}) is - * analogous to the v1 /admin/cores?action=PREPRECOVERY command. - * - * @see PrepareCoreRecoveryPayload - */ -@EndPoint( - path = {"/cores/{core}"}, - method = POST, - permission = CORE_EDIT_PERM) -public class PrepareCoreRecoveryAPI { - public static final String V2_PREP_RECOVERY_CMD = "prep-recovery"; - - private final CoreAdminHandler coreAdminHandler; - - public PrepareCoreRecoveryAPI(CoreAdminHandler coreAdminHandler) { - this.coreAdminHandler = coreAdminHandler; - } - - @Command(name = V2_PREP_RECOVERY_CMD) - public void prepareCoreForRecovery(PayloadObj obj) throws Exception { - final PrepareCoreRecoveryPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, PREPRECOVERY.name().toLowerCase(Locale.ROOT)); - v1Params.put(CORE, obj.getRequest().getPathTemplateValues().get("core")); - - coreAdminHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RebalanceLeaders.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RebalanceLeaders.java new file mode 100644 index 000000000000..04ffe598c39d --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RebalanceLeaders.java @@ -0,0 +1,71 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.client.api.endpoint.RebalanceLeadersApi; +import org.apache.solr.client.api.model.RebalanceLeadersRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for rebalancing shard leaders in a collection. + * + *

This API (POST /v2/collections/collectionName/rebalance-leaders) is analogous to the v1 + * /admin/collections?action=REBALANCELEADERS command. + */ +public class RebalanceLeaders extends AdminAPIBase implements RebalanceLeadersApi { + + @Inject + public RebalanceLeaders( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse rebalanceLeaders( + String collectionName, RebalanceLeadersRequestBody requestBody) throws Exception { + ensureRequiredParameterProvided(COLLECTION, collectionName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, CollectionParams.CollectionAction.REBALANCELEADERS.toLower()); + v1Params.put(COLLECTION, collectionName); + if (requestBody != null) { + if (requestBody.maxAtOnce != null) v1Params.put("maxAtOnce", requestBody.maxAtOnce); + if (requestBody.maxWaitSeconds != null) + v1Params.put("maxWaitSeconds", requestBody.maxWaitSeconds); + } + coreContainer + .getCollectionsHandler() + .handleRequestBody(wrapParams(solrQueryRequest, v1Params), solrQueryResponse); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RebalanceLeadersAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RebalanceLeadersAPI.java deleted file mode 100644 index e9127817732f..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/RebalanceLeadersAPI.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; - -import java.util.HashMap; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.RebalanceLeadersPayload; -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.handler.admin.CollectionsHandler; - -/** - * V2 API for balancing shard leaders in a collection across nodes. - * - *

This API (POST /v2/collections/collectionName {'rebalance-leaders': {...}}) is analogous to - * the v1 /admin/collections?action=REBALANCELEADERS command. - * - * @see RebalanceLeadersPayload - */ -@EndPoint( - path = {"/c/{collection}", "/collections/{collection}"}, - method = POST, - permission = COLL_EDIT_PERM) -public class RebalanceLeadersAPI { - private static final String V2_REBALANCE_LEADERS_CMD = "rebalance-leaders"; - - private final CollectionsHandler collectionsHandler; - - public RebalanceLeadersAPI(CollectionsHandler collectionsHandler) { - this.collectionsHandler = collectionsHandler; - } - - @Command(name = V2_REBALANCE_LEADERS_CMD) - public void rebalanceLeaders(PayloadObj obj) throws Exception { - final RebalanceLeadersPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, CollectionParams.CollectionAction.REBALANCELEADERS.toLower()); - v1Params.put(COLLECTION, obj.getRequest().getPathTemplateValues().get(COLLECTION)); - - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElection.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElection.java new file mode 100644 index 000000000000..f1de64c5f256 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElection.java @@ -0,0 +1,79 @@ +/* + * 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.api; + +import static org.apache.solr.common.cloud.ZkStateReader.CORE_NODE_NAME_PROP; +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REJOINLEADERELECTION; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import org.apache.solr.api.JerseyResource; +import org.apache.solr.client.api.endpoint.RejoinLeaderElectionApi; +import org.apache.solr.client.api.model.RejoinLeaderElectionRequestBody; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for triggering a core to rejoin leader election. + * + *

This API (POST /v2/node/rejoin-leader-election) is analogous to the v1 + * /admin/cores?action=REJOINLEADERELECTION command. + */ +public class RejoinLeaderElection extends JerseyResource implements RejoinLeaderElectionApi { + + private final CoreContainer coreContainer; + private final SolrQueryRequest solrQueryRequest; + private final SolrQueryResponse solrQueryResponse; + + @Inject + public RejoinLeaderElection( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + this.coreContainer = coreContainer; + this.solrQueryRequest = solrQueryRequest; + this.solrQueryResponse = solrQueryResponse; + } + + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse rejoinLeaderElection(RejoinLeaderElectionRequestBody requestBody) + throws Exception { + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, REJOINLEADERELECTION.name().toLowerCase(Locale.ROOT)); + if (requestBody != null) { + if (requestBody.collection != null) v1Params.put("collection", requestBody.collection); + if (requestBody.coreNodeName != null) + v1Params.put(CORE_NODE_NAME_PROP, requestBody.coreNodeName); + if (requestBody.core != null) v1Params.put("core", requestBody.core); + if (requestBody.rejoinAtHead != null) v1Params.put("rejoinAtHead", requestBody.rejoinAtHead); + } + coreContainer + .getMultiCoreHandler() + .handleRequestBody(wrapParams(solrQueryRequest, v1Params), solrQueryResponse); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPI.java deleted file mode 100644 index 7558ea822ec9..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPI.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.cloud.ZkStateReader.CORE_NODE_NAME_PROP; -import static org.apache.solr.common.params.CoreAdminParams.ACTION; -import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REJOINLEADERELECTION; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.RejoinLeaderElectionPayload; -import org.apache.solr.handler.admin.CoreAdminHandler; - -/** - * V2 API for triggering a core to rejoin leader election for the shard it constitutes. - * - *

This API (POST /v2/node {'rejoin-leader-election': {...}}) is analogous to the v1 - * /admin/cores?action=REJOINLEADERELECTION command. - */ -@EndPoint( - path = {"/node"}, - method = POST, - permission = CORE_EDIT_PERM) -public class RejoinLeaderElectionAPI { - public static final String REJOIN_LEADER_ELECTION_CMD = "rejoin-leader-election"; - - private final CoreAdminHandler coreAdminHandler; - - public RejoinLeaderElectionAPI(CoreAdminHandler coreAdminHandler) { - this.coreAdminHandler = coreAdminHandler; - } - - @Command(name = REJOIN_LEADER_ELECTION_CMD) - public void rejoinLeaderElection(PayloadObj payload) - throws Exception { - final RejoinLeaderElectionPayload v2Body = payload.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, REJOINLEADERELECTION.name().toLowerCase(Locale.ROOT)); - if (v2Body.coreNodeName != null) { - v1Params.remove("coreNodeName"); - v1Params.put(CORE_NODE_NAME_PROP, v2Body.coreNodeName); - } - - coreAdminHandler.handleRequestBody( - wrapParams(payload.getRequest(), v1Params), payload.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCoreAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCoreAPI.java deleted file mode 100644 index c2235af6894e..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCoreAPI.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.common.annotation.JsonProperty; -import org.apache.solr.common.params.CoreAdminParams; -import org.apache.solr.common.util.ReflectMapWriter; -import org.apache.solr.handler.admin.CoreAdminHandler; - -/** - * V2 API for renaming an existing Solr core. - * - *

The new API (POST /v2/cores/coreName {'rename': {...}}) is equivalent to the v1 - * /admin/cores?action=rename command. - */ -@EndPoint( - path = {"/cores/{core}"}, - method = POST, - permission = CORE_EDIT_PERM) -public class RenameCoreAPI { - private static final String V2_RENAME_CORE_CMD = "rename"; - - private final CoreAdminHandler coreHandler; - - public RenameCoreAPI(CoreAdminHandler coreHandler) { - this.coreHandler = coreHandler; - } - - @Command(name = V2_RENAME_CORE_CMD) - public void renameCore(PayloadObj obj) throws Exception { - final RenameCorePayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put( - CoreAdminParams.ACTION, - CoreAdminParams.CoreAdminAction.RENAME.name().toLowerCase(Locale.ROOT)); - v1Params.put( - CoreAdminParams.CORE, obj.getRequest().getPathTemplateValues().get(CoreAdminParams.CORE)); - - // V1 API uses 'other' instead of 'to' to represent the new core name. - v1Params.put(CoreAdminParams.OTHER, v1Params.remove("to")); - - coreHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } - - public static class RenameCorePayload implements ReflectMapWriter { - @JsonProperty(required = true) - public String to; - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestApplyCoreUpdates.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestApplyCoreUpdates.java new file mode 100644 index 000000000000..b28bd5460646 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestApplyCoreUpdates.java @@ -0,0 +1,66 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REQUESTAPPLYUPDATES; +import static org.apache.solr.common.params.CoreAdminParams.NAME; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import org.apache.solr.client.api.endpoint.RequestApplyCoreUpdatesApi; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.admin.CoreAdminHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for applying updates to a core. + * + *

This API (POST /v2/cores/coreName/request-apply-updates) is analogous to the v1 + * /admin/cores?action=REQUESTAPPLYUPDATES command. + */ +public class RequestApplyCoreUpdates extends CoreAdminAPIBase + implements RequestApplyCoreUpdatesApi { + + @Inject + public RequestApplyCoreUpdates( + CoreContainer coreContainer, + CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse requestApplyCoreUpdates(String coreName) throws Exception { + ensureRequiredParameterProvided(NAME, coreName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, REQUESTAPPLYUPDATES.name().toLowerCase(Locale.ROOT)); + v1Params.put(NAME, coreName); + coreContainer.getMultiCoreHandler().handleRequestBody(wrapParams(req, v1Params), rsp); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestApplyCoreUpdatesAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestApplyCoreUpdatesAPI.java deleted file mode 100644 index 51c20fb545f6..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestApplyCoreUpdatesAPI.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CoreAdminParams.ACTION; -import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REQUESTAPPLYUPDATES; -import static org.apache.solr.common.params.CoreAdminParams.NAME; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.RequestApplyCoreUpdatesPayload; -import org.apache.solr.handler.admin.CoreAdminHandler; - -/** - * Internal V2 API used to apply updates to a core. - * - *

Only valid in SolrCloud mode. This API (POST /v2/cores/coreName {'request-apply-updates': {}}) - * is analogous to the v1 /admin/cores?action=REQUESTAPPLYUPDATES command. - * - * @see org.apache.solr.client.solrj.request.beans.RequestApplyCoreUpdatesPayload - */ -@EndPoint( - path = {"/cores/{core}"}, - method = POST, - permission = CORE_EDIT_PERM) -public class RequestApplyCoreUpdatesAPI { - public static final String V2_APPLY_CORE_UPDATES_CMD = "request-apply-updates"; - - private final CoreAdminHandler coreAdminHandler; - - public RequestApplyCoreUpdatesAPI(CoreAdminHandler coreAdminHandler) { - this.coreAdminHandler = coreAdminHandler; - } - - @Command(name = V2_APPLY_CORE_UPDATES_CMD) - public void requestApplyCoreUpdates(PayloadObj obj) - throws Exception { - final RequestApplyCoreUpdatesPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, REQUESTAPPLYUPDATES.name().toLowerCase(Locale.ROOT)); - v1Params.put(NAME, obj.getRequest().getPathTemplateValues().get("core")); - - coreAdminHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestBufferUpdates.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestBufferUpdates.java new file mode 100644 index 000000000000..f4cee14e2a12 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestBufferUpdates.java @@ -0,0 +1,65 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REQUESTBUFFERUPDATES; +import static org.apache.solr.common.params.CoreAdminParams.NAME; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import org.apache.solr.client.api.endpoint.RequestBufferUpdatesApi; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.admin.CoreAdminHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for starting update-buffering on a core. + * + *

This API (POST /v2/cores/coreName/request-buffer-updates) is analogous to the v1 + * /admin/cores?action=REQUESTBUFFERUPDATES command. + */ +public class RequestBufferUpdates extends CoreAdminAPIBase implements RequestBufferUpdatesApi { + + @Inject + public RequestBufferUpdates( + CoreContainer coreContainer, + CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse requestBufferUpdates(String coreName) throws Exception { + ensureRequiredParameterProvided(NAME, coreName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, REQUESTBUFFERUPDATES.name().toLowerCase(Locale.ROOT)); + v1Params.put(NAME, coreName); + coreContainer.getMultiCoreHandler().handleRequestBody(wrapParams(req, v1Params), rsp); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestBufferUpdatesAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestBufferUpdatesAPI.java deleted file mode 100644 index 8625bfc9b72d..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestBufferUpdatesAPI.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CoreAdminParams.ACTION; -import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REQUESTBUFFERUPDATES; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.RequestBufferUpdatesPayload; -import org.apache.solr.common.params.CoreAdminParams; -import org.apache.solr.handler.admin.CoreAdminHandler; - -/** - * Internal V2 API used to start update-buffering on the specified core. - * - *

Only valid in SolrCloud mode. This API (POST /v2/cores/coreName {'request-buffer-updates': - * {}}) is analogous to the v1 /admin/cores?action=REQUESTBUFFERUPDATES command. - * - * @see org.apache.solr.client.solrj.request.beans.RequestSyncShardPayload - */ -@EndPoint( - path = {"/cores/{core}"}, - method = POST, - permission = CORE_EDIT_PERM) -public class RequestBufferUpdatesAPI { - public static final String V2_REQUEST_BUFFER_UPDATES_CMD = "request-buffer-updates"; - - private final CoreAdminHandler coreAdminHandler; - - public RequestBufferUpdatesAPI(CoreAdminHandler coreAdminHandler) { - this.coreAdminHandler = coreAdminHandler; - } - - @Command(name = V2_REQUEST_BUFFER_UPDATES_CMD) - public void requestBufferUpdates(PayloadObj obj) throws Exception { - final RequestBufferUpdatesPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, REQUESTBUFFERUPDATES.name().toLowerCase(Locale.ROOT)); - v1Params.put(CoreAdminParams.NAME, obj.getRequest().getPathTemplateValues().get("core")); - - coreAdminHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestCoreRecoveryAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestCoreRecovery.java similarity index 50% rename from solr/core/src/java/org/apache/solr/handler/admin/api/RequestCoreRecoveryAPI.java rename to solr/core/src/java/org/apache/solr/handler/admin/api/RequestCoreRecovery.java index a259c6084306..7f37be453994 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestCoreRecoveryAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestCoreRecovery.java @@ -14,53 +14,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.solr.handler.admin.api; -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; import static org.apache.solr.common.params.CoreAdminParams.ACTION; import static org.apache.solr.common.params.CoreAdminParams.CORE; import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REQUESTRECOVERY; import static org.apache.solr.handler.ClusterAPI.wrapParams; import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; +import jakarta.inject.Inject; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.RequestCoreRecoveryPayload; +import org.apache.solr.client.api.endpoint.RequestCoreRecoveryApi; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.admin.CoreAdminHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; /** - * Internal V2 API triggering recovery on a core. - * - *

Only valid in SolrCloud mode. This API (POST /v2/cores/coreName {'request-recovery': {}}) is - * analogous to the v1 /admin/cores?action=REQUESTRECOVERY command. + * V2 API implementation for triggering recovery on a core. * - * @see RequestCoreRecoveryPayload + *

This API (POST /v2/cores/coreName/request-recovery) is analogous to the v1 + * /admin/cores?action=REQUESTRECOVERY command. */ -@EndPoint( - path = {"/cores/{core}"}, - method = POST, - permission = CORE_EDIT_PERM) -public class RequestCoreRecoveryAPI { - public static final String V2_REQUEST_RECOVERY_CMD = "request-recovery"; +public class RequestCoreRecovery extends CoreAdminAPIBase implements RequestCoreRecoveryApi { - private final CoreAdminHandler coreAdminHandler; - - public RequestCoreRecoveryAPI(CoreAdminHandler coreAdminHandler) { - this.coreAdminHandler = coreAdminHandler; + @Inject + public RequestCoreRecovery( + CoreContainer coreContainer, + CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse); } - @Command(name = V2_REQUEST_RECOVERY_CMD) - public void requestCoreRecovery(PayloadObj obj) throws Exception { - final RequestCoreRecoveryPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse requestCoreRecovery(String coreName) throws Exception { + ensureRequiredParameterProvided(CORE, coreName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); v1Params.put(ACTION, REQUESTRECOVERY.name().toLowerCase(Locale.ROOT)); - v1Params.put(CORE, obj.getRequest().getPathTemplateValues().get("core")); - - coreAdminHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); + v1Params.put(CORE, coreName); + coreContainer.getMultiCoreHandler().handleRequestBody(wrapParams(req, v1Params), rsp); + return response; } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestSyncShardAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestSyncShard.java similarity index 50% rename from solr/core/src/java/org/apache/solr/handler/admin/api/RequestSyncShardAPI.java rename to solr/core/src/java/org/apache/solr/handler/admin/api/RequestSyncShard.java index c36a99ce5450..e1c0305ccd77 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/RequestSyncShardAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RequestSyncShard.java @@ -14,53 +14,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.solr.handler.admin.api; -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; import static org.apache.solr.common.params.CoreAdminParams.ACTION; import static org.apache.solr.common.params.CoreAdminParams.CORE; import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.REQUESTSYNCSHARD; import static org.apache.solr.handler.ClusterAPI.wrapParams; import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; +import jakarta.inject.Inject; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.RequestSyncShardPayload; +import org.apache.solr.client.api.endpoint.RequestSyncShardApi; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.admin.CoreAdminHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; /** - * Internal V2 API used to request a core sync with its shard leader. - * - *

Only valid in SolrCloud mode. This API (POST /v2/cores/coreName {'request-sync-shard': {}}) is - * analogous to the v1 /admin/cores?action=REQUESTSYNCSHARD command. + * V2 API implementation for requesting a core to sync with its shard leader. * - * @see org.apache.solr.client.solrj.request.beans.RequestSyncShardPayload + *

This API (POST /v2/cores/coreName/request-sync-shard) is analogous to the v1 + * /admin/cores?action=REQUESTSYNCSHARD command. */ -@EndPoint( - path = {"/cores/{core}"}, - method = POST, - permission = CORE_EDIT_PERM) -public class RequestSyncShardAPI { - public static final String V2_REQUEST_SYNC_SHARD_CMD = "request-sync-shard"; +public class RequestSyncShard extends CoreAdminAPIBase implements RequestSyncShardApi { - private final CoreAdminHandler coreAdminHandler; - - public RequestSyncShardAPI(CoreAdminHandler coreAdminHandler) { - this.coreAdminHandler = coreAdminHandler; + @Inject + public RequestSyncShard( + CoreContainer coreContainer, + CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse); } - @Command(name = V2_REQUEST_SYNC_SHARD_CMD) - public void requestSyncShard(PayloadObj obj) throws Exception { - final RequestSyncShardPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse requestSyncShard(String coreName) throws Exception { + ensureRequiredParameterProvided(CORE, coreName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); v1Params.put(ACTION, REQUESTSYNCSHARD.name().toLowerCase(Locale.ROOT)); - v1Params.put(CORE, obj.getRequest().getPathTemplateValues().get("core")); - - coreAdminHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); + v1Params.put(CORE, coreName); + coreContainer.getMultiCoreHandler().handleRequestBody(wrapParams(req, v1Params), rsp); + return response; } } diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/SplitCore.java b/solr/core/src/java/org/apache/solr/handler/admin/api/SplitCore.java new file mode 100644 index 000000000000..1e61448ac68a --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/SplitCore.java @@ -0,0 +1,93 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonAdminParams.SPLIT_KEY; +import static org.apache.solr.common.params.CommonParams.PATH; +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CORE; +import static org.apache.solr.common.params.CoreAdminParams.TARGET_CORE; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import org.apache.solr.client.api.endpoint.SplitCoreApi; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.model.SplitCoreRequestBody; +import org.apache.solr.common.params.CoreAdminParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.admin.CoreAdminHandler; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for splitting a core into multiple pieces. + * + *

This API (POST /v2/cores/coreName/split) is analogous to the v1 /admin/cores?action=split + * command. + */ +public class SplitCore extends CoreAdminAPIBase implements SplitCoreApi { + + @Inject + public SplitCore( + CoreContainer coreContainer, + CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, coreAdminAsyncTracker, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(CORE_EDIT_PERM) + public SolrJerseyResponse splitCore(String coreName, SplitCoreRequestBody requestBody) + throws Exception { + ensureRequiredParameterProvided(CORE, coreName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, CoreAdminParams.CoreAdminAction.SPLIT.name().toLowerCase(Locale.ROOT)); + v1Params.put(CORE, coreName); + if (requestBody != null) { + if (requestBody.path != null && !requestBody.path.isEmpty()) { + v1Params.put(PATH, requestBody.path.toArray(new String[0])); + } + if (requestBody.targetCore != null && !requestBody.targetCore.isEmpty()) { + v1Params.put(TARGET_CORE, requestBody.targetCore.toArray(new String[0])); + } + if (requestBody.splitKey != null) { + v1Params.put(SPLIT_KEY, requestBody.splitKey); + } + if (requestBody.splitMethod != null) { + v1Params.put("splitMethod", requestBody.splitMethod); + } + if (requestBody.getRanges != null) { + v1Params.put("getRanges", requestBody.getRanges); + } + if (requestBody.ranges != null) { + v1Params.put("ranges", requestBody.ranges); + } + if (requestBody.async != null) { + v1Params.put("async", requestBody.async); + } + } + coreContainer.getMultiCoreHandler().handleRequestBody(wrapParams(req, v1Params), rsp); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/SplitCoreAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/SplitCoreAPI.java deleted file mode 100644 index d846caea34bd..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/SplitCoreAPI.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CommonAdminParams.SPLIT_KEY; -import static org.apache.solr.common.params.CommonParams.PATH; -import static org.apache.solr.common.params.CoreAdminParams.TARGET_CORE; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM; - -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.common.annotation.JsonProperty; -import org.apache.solr.common.params.CoreAdminParams; -import org.apache.solr.common.util.ReflectMapWriter; -import org.apache.solr.handler.admin.CoreAdminHandler; - -/** - * V2 API for splitting a single core into multiple pieces - * - *

The new API (POST /v2/cores/coreName {'split': {...}}) is equivalent to the v1 - * /admin/cores?action=split command. - */ -@EndPoint( - path = {"/cores/{core}"}, - method = POST, - permission = CORE_EDIT_PERM) -public class SplitCoreAPI { - private static final String V2_SPLIT_CORE_CMD = "split"; - - private final CoreAdminHandler coreHandler; - - public SplitCoreAPI(CoreAdminHandler coreHandler) { - this.coreHandler = coreHandler; - } - - @Command(name = V2_SPLIT_CORE_CMD) - public void splitCore(PayloadObj obj) throws Exception { - final SplitCorePayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put( - CoreAdminParams.ACTION, - CoreAdminParams.CoreAdminAction.SPLIT.name().toLowerCase(Locale.ROOT)); - v1Params.put( - CoreAdminParams.CORE, obj.getRequest().getPathTemplateValues().get(CoreAdminParams.CORE)); - - if (v2Body.path != null && !v2Body.path.isEmpty()) { - v1Params.put(PATH, v2Body.path.toArray(new String[0])); - } - if (v2Body.targetCore != null && !v2Body.targetCore.isEmpty()) { - v1Params.put(TARGET_CORE, v2Body.targetCore.toArray(new String[0])); - } - - if (v2Body.splitKey != null) { - v1Params.put(SPLIT_KEY, v1Params.remove("splitKey")); - } - - coreHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } - - public static class SplitCorePayload implements ReflectMapWriter { - @JsonProperty public List path; - - @JsonProperty public List targetCore; - - @JsonProperty public String splitKey; - - @JsonProperty public String splitMethod; - - @JsonProperty public Boolean getRanges; - - @JsonProperty public String ranges; - - @JsonProperty public String async; - } -} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/SplitShard.java b/solr/core/src/java/org/apache/solr/handler/admin/api/SplitShard.java new file mode 100644 index 000000000000..2bacd23e5582 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/handler/admin/api/SplitShard.java @@ -0,0 +1,89 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; +import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX; +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.apache.solr.handler.ClusterAPI.wrapParams; +import static org.apache.solr.handler.api.V2ApiUtils.flattenMapWithPrefix; +import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; + +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.client.api.endpoint.SplitShardApi; +import org.apache.solr.client.api.model.SolrJerseyResponse; +import org.apache.solr.client.api.model.SplitShardRequestBody; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.common.params.CommonAdminParams; +import org.apache.solr.common.util.StrUtils; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.jersey.PermissionName; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.response.SolrQueryResponse; + +/** + * V2 API implementation for splitting an existing shard into multiple pieces. + * + *

This API (POST /v2/collections/collectionName/shards/split) is analogous to the v1 + * /admin/collections?action=SPLITSHARD command. + */ +public class SplitShard extends AdminAPIBase implements SplitShardApi { + + @Inject + public SplitShard( + CoreContainer coreContainer, + SolrQueryRequest solrQueryRequest, + SolrQueryResponse solrQueryResponse) { + super(coreContainer, solrQueryRequest, solrQueryResponse); + } + + @Override + @PermissionName(COLL_EDIT_PERM) + public SolrJerseyResponse splitShard(String collectionName, SplitShardRequestBody requestBody) + throws Exception { + ensureRequiredParameterProvided(COLLECTION, collectionName); + final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class); + final Map v1Params = new HashMap<>(); + v1Params.put(ACTION, CollectionParams.CollectionAction.SPLITSHARD.toLower()); + v1Params.put(COLLECTION, collectionName); + if (requestBody != null) { + if (requestBody.shard != null) v1Params.put("shard", requestBody.shard); + if (requestBody.ranges != null) v1Params.put("ranges", requestBody.ranges); + if (StrUtils.isNotNullOrEmpty(requestBody.splitKey)) { + v1Params.put(CommonAdminParams.SPLIT_KEY, requestBody.splitKey); + } + if (requestBody.numSubShards != null) v1Params.put("numSubShards", requestBody.numSubShards); + if (requestBody.splitFuzz != null) v1Params.put("splitFuzz", requestBody.splitFuzz); + if (requestBody.timing != null) v1Params.put("timing", requestBody.timing); + if (requestBody.splitByPrefix != null) + v1Params.put("splitByPrefix", requestBody.splitByPrefix); + if (requestBody.followAliases != null) + v1Params.put("followAliases", requestBody.followAliases); + if (requestBody.splitMethod != null) v1Params.put("splitMethod", requestBody.splitMethod); + if (requestBody.async != null) v1Params.put("async", requestBody.async); + if (requestBody.waitForFinalState != null) + v1Params.put("waitForFinalState", requestBody.waitForFinalState); + flattenMapWithPrefix(requestBody.coreProperties, v1Params, PROPERTY_PREFIX); + } + coreContainer + .getCollectionsHandler() + .handleRequestBody(wrapParams(solrQueryRequest, v1Params), solrQueryResponse); + return response; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/SplitShardAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/SplitShardAPI.java deleted file mode 100644 index 0c75f053cae8..000000000000 --- a/solr/core/src/java/org/apache/solr/handler/admin/api/SplitShardAPI.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; -import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; -import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.apache.solr.handler.ClusterAPI.wrapParams; -import static org.apache.solr.handler.api.V2ApiUtils.flattenMapWithPrefix; -import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; - -import java.util.HashMap; -import java.util.Map; -import org.apache.solr.api.Command; -import org.apache.solr.api.EndPoint; -import org.apache.solr.api.PayloadObj; -import org.apache.solr.client.solrj.request.beans.SplitShardPayload; -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.common.params.CommonAdminParams; -import org.apache.solr.common.util.StrUtils; -import org.apache.solr.handler.admin.CollectionsHandler; - -/** - * V2 API for splitting an existing shard up into multiple pieces. - * - *

This API (POST /v2/collections/collectionName/shards {'split': {...}}) is analogous to the v1 - * /admin/collections?action=SPLITSHARD command. - * - * @see SplitShardPayload - */ -@EndPoint( - path = {"/c/{collection}/shards", "/collections/{collection}/shards"}, - method = POST, - permission = COLL_EDIT_PERM) -public class SplitShardAPI { - private static final String V2_SPLIT_CMD = "split"; - - private final CollectionsHandler collectionsHandler; - - public SplitShardAPI(CollectionsHandler collectionsHandler) { - this.collectionsHandler = collectionsHandler; - } - - @Command(name = V2_SPLIT_CMD) - public void splitShard(PayloadObj obj) throws Exception { - final SplitShardPayload v2Body = obj.get(); - final Map v1Params = v2Body.toMap(new HashMap<>()); - v1Params.put(ACTION, CollectionParams.CollectionAction.SPLITSHARD.toLower()); - v1Params.put(COLLECTION, obj.getRequest().getPathTemplateValues().get(COLLECTION)); - - if (StrUtils.isNotNullOrEmpty(v2Body.splitKey)) { - v1Params.put(CommonAdminParams.SPLIT_KEY, v2Body.splitKey); - } - flattenMapWithPrefix(v2Body.coreProperties, v1Params, PROPERTY_PREFIX); - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse()); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java index 4ef3c7f48dff..8be0b9039070 100644 --- a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java +++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java @@ -87,13 +87,16 @@ private void testException( @Test public void testException() { String notFoundPath = "/c/" + COLL_NAME + "/abccdef"; - String incorrectPayload = "{rebalance-leaders: {maxAtOnce: abc, maxWaitSeconds: xyz}}"; - testException(new XMLResponseParser(), 404, notFoundPath, incorrectPayload); - testException(new JsonMapResponseParser(), 404, notFoundPath, incorrectPayload); - testException(new JavaBinResponseParser(), 404, notFoundPath, incorrectPayload); - testException(new XMLResponseParser(), 400, "/c/" + COLL_NAME, incorrectPayload); - testException(new JavaBinResponseParser(), 400, "/c/" + COLL_NAME, incorrectPayload); - testException(new JsonMapResponseParser(), 400, "/c/" + COLL_NAME, incorrectPayload); + String anyPayload = "{}"; + testException(new XMLResponseParser(), 404, notFoundPath, anyPayload); + testException(new JsonMapResponseParser(), 404, notFoundPath, anyPayload); + testException(new JavaBinResponseParser(), 404, notFoundPath, anyPayload); + // POST to a valid JAX-RS endpoint with missing required field -> 400 from Solr's own code. + // Uses /collections/ prefix (not /c/) because JAX-RS @Path annotations use /collections/. + String badRequestPath = "/collections/" + COLL_NAME + "/balance-shard-unique"; + testException(new XMLResponseParser(), 400, badRequestPath, anyPayload); + testException(new JavaBinResponseParser(), 400, badRequestPath, anyPayload); + testException(new JsonMapResponseParser(), 400, badRequestPath, anyPayload); } @Test @@ -101,10 +104,11 @@ public void testIntrospect() throws Exception { ModifiableSolrParams params = new ModifiableSolrParams(); params.set("command", "XXXX"); params.set("method", "POST"); + // /cluster/plugin still uses old-style @EndPoint+@Command; collection-level APIs are JAX-RS Map result = resAsMap( cluster.getSolrClient(), - new V2Request.Builder("/c/" + COLL_NAME + "/_introspect").withParams(params).build()); + new V2Request.Builder("/cluster/plugin/_introspect").withParams(params).build()); assertEquals( "Command not found!", Utils.getObjectByPath(result, false, "/spec[0]/commands/XXXX")); } @@ -188,10 +192,11 @@ private HttpRequestBase getListCollectionsRequest() { @Test public void testSingleWarning() throws Exception { + // /cluster/plugin still uses old-style @EndPoint+@Command; collection-level APIs are JAX-RS NamedList resp = cluster .getSolrClient() - .request(new V2Request.Builder("/c/" + COLL_NAME + "/_introspect").build()); + .request(new V2Request.Builder("/cluster/plugin/_introspect").build()); List warnings = resp.getAll("WARNING"); assertEquals(1, warnings.size()); } diff --git a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java deleted file mode 100644 index 6dce25d53474..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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; - -import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.api.Api; -import org.apache.solr.api.ApiBag; -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.common.params.CollectionParams.CollectionAction; -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.ContentStreamBase; -import org.apache.solr.handler.admin.CollectionsHandler; -import org.apache.solr.handler.admin.ConfigSetsHandler; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.request.SolrQueryRequestBase; -import org.apache.solr.response.SolrQueryResponse; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -/** Unit tests for the v2 to v1 API mappings found in {@link ClusterAPI} */ -public class V2ClusterAPIMappingTest extends SolrTestCaseJ4 { - private ApiBag apiBag; - private ArgumentCaptor queryRequestCaptor; - private CollectionsHandler mockCollectionsHandler; - private ConfigSetsHandler mockConfigSetHandler; - - @BeforeClass - public static void ensureWorkingMockito() { - assumeWorkingMockito(); - } - - @Before - public void setupApiBag() { - mockCollectionsHandler = mock(CollectionsHandler.class); - mockConfigSetHandler = mock(ConfigSetsHandler.class); - queryRequestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); - - apiBag = new ApiBag(false); - final ClusterAPI clusterAPI = new ClusterAPI(mockCollectionsHandler, mockConfigSetHandler); - apiBag.registerObject(clusterAPI); - apiBag.registerObject(clusterAPI.commands); - } - - @Test - public void testAsyncCommandStatusAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params("/cluster/command-status/someId", "GET", null); - - assertEquals(CollectionParams.CollectionAction.REQUESTSTATUS.lowerName, v1Params.get(ACTION)); - assertEquals("someId", v1Params.get(REQUESTID)); - } - - @Test - public void testClusterOverseerAllParams() throws Exception { - final SolrParams v1Params = captureConvertedV1Params("/cluster/overseer", "GET", null); - - assertEquals(CollectionParams.CollectionAction.OVERSEERSTATUS.lowerName, v1Params.get(ACTION)); - } - - @Test - public void testClusterStatusAllParams() throws Exception { - final SolrParams v1Params = captureConvertedV1Params("/cluster", "GET", null); - - assertEquals(CollectionAction.CLUSTERSTATUS.lowerName, v1Params.get(ACTION)); - } - - @Test - public void testDeleteCommandStatusAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params("/cluster/command-status/someId", "DELETE", null); - - assertEquals(CollectionParams.CollectionAction.DELETESTATUS.lowerName, v1Params.get(ACTION)); - assertEquals("someId", v1Params.get(REQUESTID)); - } - - @Test - public void testAddRoleAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/cluster", - "POST", - "{'add-role': {" + "'node': 'some_node_name', " + "'role':'some_role'}}"); - - assertEquals(CollectionParams.CollectionAction.ADDROLE.toString(), v1Params.get(ACTION)); - assertEquals("some_node_name", v1Params.get("node")); - assertEquals("some_role", v1Params.get("role")); - } - - @Test - public void testRemoveRoleAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/cluster", - "POST", - "{'remove-role': {" + "'node': 'some_node_name', " + "'role':'some_role'}}"); - - assertEquals(CollectionParams.CollectionAction.REMOVEROLE.toString(), v1Params.get(ACTION)); - assertEquals("some_node_name", v1Params.get("node")); - assertEquals("some_role", v1Params.get("role")); - } - - private SolrParams captureConvertedV1Params(String path, String method, String v2RequestBody) - throws Exception { - return doCaptureParams(path, method, v2RequestBody, mockCollectionsHandler); - } - - private SolrParams doCaptureParams( - String path, String method, String v2RequestBody, RequestHandlerBase mockHandler) - throws Exception { - final HashMap parts = new HashMap<>(); - final Api api = apiBag.lookup(path, method, parts); - final SolrQueryResponse rsp = new SolrQueryResponse(); - final SolrQueryRequestBase req = - new SolrQueryRequestBase(null, new ModifiableSolrParams()) { - @Override - public List getCommands(boolean validateInput) { - if (v2RequestBody == null) return Collections.emptyList(); - return ApiBag.getCommandOperations( - new ContentStreamBase.StringStream(v2RequestBody), api.getCommandSchema(), true); - } - - @Override - public Map getPathTemplateValues() { - return parts; - } - - @Override - public String getHttpMethod() { - return method; - } - }; - - api.call(req, rsp); - verify(mockHandler).handleRequestBody(queryRequestCaptor.capture(), any()); - return queryRequestCaptor.getValue().getParams(); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java index 43838707d057..77507bedbc2a 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java @@ -44,6 +44,7 @@ import org.apache.solr.common.params.CommonParams; import org.apache.solr.core.CoreDescriptor; import org.apache.solr.embedded.JettySolrRunner; +import org.apache.solr.handler.admin.api.NodeHealth; import org.junit.BeforeClass; import org.junit.Test; @@ -193,7 +194,7 @@ public void testFindUnhealthyCores() { mockCD("invalid", "invalid", "slice1", false, Replica.State.RECOVERING), // A core for a slice that is not an active slice will not fail the check mockCD("collection1", "invalid_replica1", "invalid", true, Replica.State.DOWN)); - long unhealthy1 = HealthCheckHandler.findUnhealthyCores(node1Cores, clusterState); + long unhealthy1 = NodeHealth.findUnhealthyCores(node1Cores, clusterState); assertEquals(2, unhealthy1); // Node 2 @@ -203,7 +204,7 @@ public void testFindUnhealthyCores() { mockCD("collection1", "slice1_replica4", "slice1", true, Replica.State.DOWN), mockCD( "collection2", "slice1_replica1", "slice1", true, Replica.State.RECOVERY_FAILED)); - long unhealthy2 = HealthCheckHandler.findUnhealthyCores(node2Cores, clusterState); + long unhealthy2 = NodeHealth.findUnhealthyCores(node2Cores, clusterState); assertEquals(1, unhealthy2); } } diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java index 20286eca4090..c150f21e2c14 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java @@ -19,9 +19,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; 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; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -78,14 +75,11 @@ public void testFramework() { CoreContainer mockCC = getCoreContainerMock(calls, out); PluginBag containerHandlers = new PluginBag<>(SolrRequestHandler.class, null, false); - TestCollectionAPIs.MockCollectionsHandler collectionsHandler = - new TestCollectionAPIs.MockCollectionsHandler(); - containerHandlers.put(COLLECTIONS_HANDLER_PATH, collectionsHandler); - for (Api api : collectionsHandler.getApis()) { - containerHandlers.getApiBag().register(api); - } - containerHandlers.put(CORES_HANDLER_PATH, new CoreAdminHandler(mockCC)); - containerHandlers.put(CONFIGSETS_HANDLER_PATH, new ConfigSetsHandler(mockCC)); + // Collection and core admin APIs have all been migrated to JAX-RS, so getApis() returns + // empty. Register a custom @EndPoint+@Command API directly to exercise the framework routing. + containerHandlers + .getApiBag() + .registerObject(new TestCollectionLikeApis()); // has @EndPoint on /collections/{collection} out.put("getRequestHandlers", containerHandlers); PluginBag coreHandlers = @@ -94,22 +88,27 @@ public void testFramework() { coreHandlers.put("/config", new SolrConfigHandler()); coreHandlers.put("/admin/ping", new PingRequestHandler()); + // Verify that path template variables (e.g. {collection}) are correctly extracted Map parts = new HashMap<>(); - String fullPath = "/collections/hello/shards"; + String fullPath = "/collections/hello/test-shards"; Api api = V2HttpCall.getApiInfo(containerHandlers, fullPath, "POST", fullPath, parts); assertNotNull(api); assertEquals("hello", parts.get("collection")); + // Verify command spec is populated correctly for a @Command-based endpoint parts = new HashMap<>(); api = - V2HttpCall.getApiInfo(containerHandlers, "/collections/hello/shards", "POST", null, parts); + V2HttpCall.getApiInfo( + containerHandlers, "/collections/hello/test-shards", "POST", null, parts); assertConditions(api.getSpec(), Map.of("/methods[0]", "POST", "/commands/split", NOT_NULL)); + // Verify a second endpoint at the collection root has its own command spec parts = new HashMap<>(); api = V2HttpCall.getApiInfo(containerHandlers, "/collections/hello", "POST", null, parts); assertConditions(api.getSpec(), Map.of("/methods[0]", "POST", "/commands/modify", NOT_NULL)); assertEquals("hello", parts.get("collection")); + // Verify introspect works and returns at least one POST method entry SolrQueryResponse rsp = invoke(containerHandlers, null, "/collections/_introspect", mockCC); Set methodNames = new HashSet<>(); @@ -119,6 +118,23 @@ public void testFramework() { assertTrue(methodNames.contains("POST")); } + /** + * A test-only @EndPoint class that mimics the old collection-level command-dispatch routing + * shape. Used by testFramework() to exercise the ApiBag routing and introspect machinery now that + * real collection APIs have been migrated to JAX-RS. + */ + @EndPoint( + method = POST, + path = {"/collections/{collection}/test-shards", "/collections/{collection}"}, + permission = PermissionNameProvider.Name.COLL_EDIT_PERM) + public static class TestCollectionLikeApis { + @Command(name = "split") + public void split(SolrQueryRequest req, SolrQueryResponse rsp, AddVersion payload) {} + + @Command(name = "modify") + public void modify(SolrQueryRequest req, SolrQueryResponse rsp, AddVersion payload) {} + } + public void testPayload() { String json = "{package:pkg1, version: '0.1', files :[a.jar, b.jar]}"; Utils.fromJSONString(json); diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java index 6ffdc55aa09f..c69c172f9cc0 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java @@ -17,7 +17,6 @@ package org.apache.solr.handler.admin; -import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST; import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION; import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_NAME; import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_VALUE; @@ -44,7 +43,6 @@ import org.apache.solr.common.util.Pair; import org.apache.solr.common.util.Utils; import org.apache.solr.core.CoreContainer; -import org.apache.solr.handler.ClusterAPI; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequestBase; import org.apache.solr.response.SolrQueryResponse; @@ -76,52 +74,8 @@ public void testCopyParamsToMap() { } public void testCommands() throws Exception { - ApiBag apiBag; - try (MockCollectionsHandler collectionsHandler = new MockCollectionsHandler()) { - apiBag = new ApiBag(false); - for (Api api : collectionsHandler.getApis()) { - apiBag.register(api); - } - - ClusterAPI clusterAPI = new ClusterAPI(collectionsHandler, null); - apiBag.registerObject(clusterAPI); - apiBag.registerObject(clusterAPI.commands); - } - - compareOutput( - apiBag, - "/collections/collName/shards", - POST, - "{split:{shard:shard1, ranges: '0-1f4,1f5-3e8,3e9-5dc', coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", - "{collection: collName , shard : shard1, ranges :'0-1f4,1f5-3e8,3e9-5dc', operation : splitshard, property.prop1:prop1Val, property.prop2: prop2Val}"); - - compareOutput( - apiBag, - "/collections/collName/shards", - POST, - "{split:{ splitKey:id12345, coreProperties : {prop1:prop1Val, prop2:prop2Val} }}", - "{collection: collName , split.key : id12345 , operation : splitshard, property.prop1:prop1Val, property.prop2: prop2Val}"); - - compareOutput( - apiBag, - "/cluster", - POST, - "{add-role : {role : overseer, node : 'localhost_8978'} }", - "{operation : addrole ,role : overseer, node : 'localhost_8978'}"); - - compareOutput( - apiBag, - "/cluster", - POST, - "{remove-role : {role : overseer, node : 'localhost_8978'} }", - "{operation : removerole ,role : overseer, node : 'localhost_8978'}"); - - compareOutput( - apiBag, - "/collections/coll1", - POST, - "{migrate-docs : {forwardTimeout: 1800, target: coll2, splitKey: 'a123!'} }", - "{operation : migrate ,collection : coll1, target.collection:coll2, forward.timeout:1800, split.key:'a123!'}"); + // Previously this test exercised SplitShardAPI and MigrateDocsAPI, which have been migrated + // to JAX-RS and no longer use the old @EndPoint approach. } ZkNodeProps compareOutput( diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/MigrateDocsAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/MigrateDocsAPITest.java new file mode 100644 index 000000000000..c4a6edc54a9e --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/MigrateDocsAPITest.java @@ -0,0 +1,99 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.MigrateDocsRequestBody; +import org.apache.solr.common.SolrException; +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.admin.CollectionsHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link MigrateDocs} */ +public class MigrateDocsAPITest extends SolrTestCaseJ4 { + + private MigrateDocs api; + private CoreContainer mockCoreContainer; + private CollectionsHandler mockCollectionsHandler; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCollectionsHandler = mock(CollectionsHandler.class); + when(mockCoreContainer.getCollectionsHandler()).thenReturn(mockCollectionsHandler); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new MigrateDocs(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCollectionNameMissing() { + final SolrException thrown = + expectThrows( + SolrException.class, () -> api.migrateDocs(null, new MigrateDocsRequestBody())); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: collection", thrown.getMessage()); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new MigrateDocsRequestBody(); + requestBody.target = "targetColl"; + requestBody.splitKey = "mySplitKey"; + requestBody.forwardTimeout = 1800; + requestBody.followAliases = true; + requestBody.async = "asyncId"; + + api.migrateDocs("collName", requestBody); + + verify(mockCollectionsHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("migrate", params.get(ACTION)); + assertEquals("collName", params.get("collection")); + assertEquals("targetColl", params.get("target.collection")); + assertEquals("mySplitKey", params.get("split.key")); + assertEquals("1800", params.get("forward.timeout")); + assertEquals("true", params.get("followAliases")); + assertEquals("asyncId", params.get("async")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/ModifyCollectionAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/ModifyCollectionAPITest.java new file mode 100644 index 000000000000..34ddcd683c77 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/ModifyCollectionAPITest.java @@ -0,0 +1,103 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF; +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.ModifyCollectionRequestBody; +import org.apache.solr.common.SolrException; +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.admin.CollectionsHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link ModifyCollection} */ +public class ModifyCollectionAPITest extends SolrTestCaseJ4 { + + private ModifyCollection api; + private CoreContainer mockCoreContainer; + private CollectionsHandler mockCollectionsHandler; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCollectionsHandler = mock(CollectionsHandler.class); + when(mockCoreContainer.getCollectionsHandler()).thenReturn(mockCollectionsHandler); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new ModifyCollection(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCollectionNameMissing() { + final SolrException thrown = + expectThrows( + SolrException.class, + () -> api.modifyCollection(null, new ModifyCollectionRequestBody())); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: collection", thrown.getMessage()); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new ModifyCollectionRequestBody(); + requestBody.replicationFactor = 3; + requestBody.readOnly = true; + requestBody.config = "myConfig"; + requestBody.properties = Map.of("foo", "bar", "baz", "456"); + requestBody.async = "requestId"; + + api.modifyCollection("collName", requestBody); + + verify(mockCollectionsHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("modifycollection", params.get(ACTION)); + assertEquals("collName", params.get("collection")); + assertEquals("3", params.get("replicationFactor")); + assertEquals("true", params.get("readOnly")); + assertEquals("myConfig", params.get(COLL_CONF)); + assertEquals("requestId", params.get("async")); + assertEquals("bar", params.get("property.foo")); + assertEquals("456", params.get("property.baz")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/MoveReplicaAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/MoveReplicaAPITest.java new file mode 100644 index 000000000000..5a3f8b963e38 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/MoveReplicaAPITest.java @@ -0,0 +1,105 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.MoveReplicaRequestBody; +import org.apache.solr.common.SolrException; +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.admin.CollectionsHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link MoveReplica} */ +public class MoveReplicaAPITest extends SolrTestCaseJ4 { + + private MoveReplica api; + private CoreContainer mockCoreContainer; + private CollectionsHandler mockCollectionsHandler; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCollectionsHandler = mock(CollectionsHandler.class); + when(mockCoreContainer.getCollectionsHandler()).thenReturn(mockCollectionsHandler); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new MoveReplica(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCollectionNameMissing() { + final SolrException thrown = + expectThrows( + SolrException.class, () -> api.moveReplica(null, new MoveReplicaRequestBody())); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: collection", thrown.getMessage()); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new MoveReplicaRequestBody(); + requestBody.targetNode = "targetNode1"; + requestBody.replica = "replica1"; + requestBody.shard = "shard1"; + requestBody.sourceNode = "sourceNode1"; + requestBody.waitForFinalState = true; + requestBody.timeout = 300; + requestBody.inPlaceMove = false; + requestBody.followAliases = true; + + api.moveReplica("collName", requestBody); + + verify(mockCollectionsHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("movereplica", params.get(ACTION)); + assertEquals("collName", params.get("collection")); + assertEquals("targetNode1", params.get("targetNode")); + assertEquals("replica1", params.get("replica")); + assertEquals("shard1", params.get("shard")); + assertEquals("sourceNode1", params.get("sourceNode")); + assertEquals("true", params.get("waitForFinalState")); + assertEquals("300", params.get("timeout")); + assertEquals("false", params.get("inPlaceMove")); + assertEquals("true", params.get("followAliases")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/NodePropertiesAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/NodePropertiesAPITest.java new file mode 100644 index 000000000000..faa9bb1e56cd --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/NodePropertiesAPITest.java @@ -0,0 +1,79 @@ +/* + * 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.api; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.NodePropertiesResponse; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.core.NodeConfig; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Unit tests for {@link NodeProperties} */ +public class NodePropertiesAPITest extends SolrTestCaseJ4 { + + private NodeProperties api; + private CoreContainer mockCoreContainer; + private NodeConfig mockNodeConfig; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockNodeConfig = mock(NodeConfig.class); + when(mockCoreContainer.getNodeConfig()).thenReturn(mockNodeConfig); + api = new NodeProperties(mockCoreContainer); + } + + @Test + public void testGetSpecificProperty() throws Exception { + when(mockNodeConfig.getRedactedSysPropValue("java.home")).thenReturn("someValue"); + + NodePropertiesResponse response = api.getProperties("java.home"); + + assertNotNull(response.systemProperties); + assertEquals(1, response.systemProperties.size()); + assertEquals("someValue", response.systemProperties.get("java.home")); + } + + @Test + public void testGetAllPropertiesWhenNameIsNull() throws Exception { + when(mockNodeConfig.getRedactedSysPropValue(any())).thenAnswer(inv -> inv.getArgument(0)); + + NodePropertiesResponse response = api.getProperties(null); + + assertNotNull(response.systemProperties); + assertFalse("Expected non-empty system properties", response.systemProperties.isEmpty()); + assertTrue( + "Expected 'java.home' to be present in system properties", + response.systemProperties.containsKey("java.home")); + } + + private static String any() { + return org.mockito.ArgumentMatchers.anyString(); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/NodeSystemInfoAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/NodeSystemInfoAPITest.java new file mode 100644 index 000000000000..281329599e4a --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/NodeSystemInfoAPITest.java @@ -0,0 +1,71 @@ +/* + * 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.api; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.core.CoreContainer; +import org.apache.solr.handler.admin.InfoHandler; +import org.apache.solr.handler.admin.SystemInfoHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Unit tests for {@link NodeSystemInfo} */ +public class NodeSystemInfoAPITest extends SolrTestCaseJ4 { + + private NodeSystemInfo api; + private CoreContainer mockCoreContainer; + private InfoHandler mockInfoHandler; + private SystemInfoHandler mockSystemInfoHandler; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockInfoHandler = mock(InfoHandler.class); + mockSystemInfoHandler = mock(SystemInfoHandler.class); + when(mockCoreContainer.getInfoHandler()).thenReturn(mockInfoHandler); + when(mockInfoHandler.getSystemInfoHandler()).thenReturn(mockSystemInfoHandler); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new NodeSystemInfo(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testDelegatesToSystemInfoHandler() throws Exception { + api.getSystemInfo(); + + verify(mockSystemInfoHandler).handleRequestBody(any(), any()); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/OverseerOperationAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/OverseerOperationAPITest.java new file mode 100644 index 000000000000..c701adccd181 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/OverseerOperationAPITest.java @@ -0,0 +1,82 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.OverseerOperationRequestBody; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link OverseerOperation} */ +public class OverseerOperationAPITest extends SolrTestCaseJ4 { + + private OverseerOperation api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new OverseerOperation(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new OverseerOperationRequestBody(); + requestBody.op = "rejoinAtHead"; + requestBody.electionNode = "someNode"; + + api.overseerOperation(requestBody); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("overseerop", params.get(ACTION)); + assertEquals("rejoinAtHead", params.get("op")); + assertEquals("someNode", params.get("electionNode")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/PrepareCoreRecoveryAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/PrepareCoreRecoveryAPITest.java new file mode 100644 index 000000000000..4f353d93ad2b --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/PrepareCoreRecoveryAPITest.java @@ -0,0 +1,105 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CORE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.PrepareCoreRecoveryRequestBody; +import org.apache.solr.common.SolrException; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link PrepareCoreRecovery} */ +public class PrepareCoreRecoveryAPITest extends SolrTestCaseJ4 { + + private PrepareCoreRecovery api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private CoreAdminHandler.CoreAdminAsyncTracker asyncTracker; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + asyncTracker = new CoreAdminHandler.CoreAdminAsyncTracker(); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new PrepareCoreRecovery(mockCoreContainer, asyncTracker, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCoreNameMissing() { + final SolrException thrown = + expectThrows( + SolrException.class, + () -> api.prepareCoreForRecovery(null, new PrepareCoreRecoveryRequestBody())); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: core", thrown.getMessage()); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new PrepareCoreRecoveryRequestBody(); + requestBody.nodeName = "node1"; + requestBody.coreNodeName = "coreNode1"; + requestBody.state = "active"; + requestBody.checkLive = true; + requestBody.onlyIfLeader = true; + requestBody.onlyIfLeaderActive = true; + + api.prepareCoreForRecovery("coreName", requestBody); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("preprecovery", params.get(ACTION)); + assertEquals("coreName", params.get(CORE)); + assertEquals("node1", params.get("nodeName")); + assertEquals("coreNode1", params.get("coreNodeName")); + assertEquals("active", params.get("state")); + assertEquals("true", params.get("checkLive")); + assertEquals("true", params.get("onlyIfLeader")); + assertEquals("true", params.get("onlyIfLeaderActive")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/RebalanceLeadersAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/RebalanceLeadersAPITest.java new file mode 100644 index 000000000000..d42fc1621dc1 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/RebalanceLeadersAPITest.java @@ -0,0 +1,94 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.RebalanceLeadersRequestBody; +import org.apache.solr.common.SolrException; +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.admin.CollectionsHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link RebalanceLeaders} */ +public class RebalanceLeadersAPITest extends SolrTestCaseJ4 { + + private RebalanceLeaders api; + private CoreContainer mockCoreContainer; + private CollectionsHandler mockCollectionsHandler; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCollectionsHandler = mock(CollectionsHandler.class); + when(mockCoreContainer.getCollectionsHandler()).thenReturn(mockCollectionsHandler); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new RebalanceLeaders(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCollectionNameMissing() { + final SolrException thrown = + expectThrows( + SolrException.class, + () -> api.rebalanceLeaders(null, new RebalanceLeadersRequestBody())); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: collection", thrown.getMessage()); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new RebalanceLeadersRequestBody(); + requestBody.maxAtOnce = 5; + requestBody.maxWaitSeconds = 120; + + api.rebalanceLeaders("collName", requestBody); + + verify(mockCollectionsHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("rebalanceleaders", params.get(ACTION)); + assertEquals("collName", params.get("collection")); + assertEquals("5", params.get("maxAtOnce")); + assertEquals("120", params.get("maxWaitSeconds")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPITest.java new file mode 100644 index 000000000000..0b41ff64a753 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/RejoinLeaderElectionAPITest.java @@ -0,0 +1,87 @@ +/* + * 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.api; + +import static org.apache.solr.common.cloud.ZkStateReader.CORE_NODE_NAME_PROP; +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.RejoinLeaderElectionRequestBody; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link RejoinLeaderElection} */ +public class RejoinLeaderElectionAPITest extends SolrTestCaseJ4 { + + private RejoinLeaderElection api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new RejoinLeaderElection(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new RejoinLeaderElectionRequestBody(); + requestBody.collection = "myCollection"; + requestBody.coreNodeName = "myCoreNode"; + requestBody.core = "myCore"; + requestBody.rejoinAtHead = true; + + api.rejoinLeaderElection(requestBody); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("rejoinleaderelection", params.get(ACTION)); + assertEquals("myCollection", params.get("collection")); + assertEquals("myCoreNode", params.get(CORE_NODE_NAME_PROP)); + assertEquals("myCore", params.get("core")); + assertEquals("true", params.get("rejoinAtHead")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/RequestApplyCoreUpdatesAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestApplyCoreUpdatesAPITest.java new file mode 100644 index 000000000000..3550cdfde555 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestApplyCoreUpdatesAPITest.java @@ -0,0 +1,88 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.NAME; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrException; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link RequestApplyCoreUpdates} */ +public class RequestApplyCoreUpdatesAPITest extends SolrTestCaseJ4 { + + private RequestApplyCoreUpdates api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private CoreAdminHandler.CoreAdminAsyncTracker asyncTracker; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + asyncTracker = new CoreAdminHandler.CoreAdminAsyncTracker(); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new RequestApplyCoreUpdates(mockCoreContainer, asyncTracker, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCoreNameMissing() { + final SolrException thrown = + expectThrows(SolrException.class, () -> api.requestApplyCoreUpdates(null)); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: name", thrown.getMessage()); + } + + @Test + public void testPassesParamsCorrectly() throws Exception { + api.requestApplyCoreUpdates("someCore"); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("requestapplyupdates", params.get(ACTION)); + assertEquals("someCore", params.get(NAME)); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/RequestBufferUpdatesAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestBufferUpdatesAPITest.java new file mode 100644 index 000000000000..53434c023525 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestBufferUpdatesAPITest.java @@ -0,0 +1,88 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.NAME; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrException; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link RequestBufferUpdates} */ +public class RequestBufferUpdatesAPITest extends SolrTestCaseJ4 { + + private RequestBufferUpdates api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private CoreAdminHandler.CoreAdminAsyncTracker asyncTracker; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + asyncTracker = new CoreAdminHandler.CoreAdminAsyncTracker(); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new RequestBufferUpdates(mockCoreContainer, asyncTracker, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCoreNameMissing() { + final SolrException thrown = + expectThrows(SolrException.class, () -> api.requestBufferUpdates(null)); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: name", thrown.getMessage()); + } + + @Test + public void testPassesParamsCorrectly() throws Exception { + api.requestBufferUpdates("someCore"); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("requestbufferupdates", params.get(ACTION)); + assertEquals("someCore", params.get(NAME)); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/RequestCoreRecoveryAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestCoreRecoveryAPITest.java new file mode 100644 index 000000000000..f62f1e7b8e54 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestCoreRecoveryAPITest.java @@ -0,0 +1,88 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CORE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrException; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link RequestCoreRecovery} */ +public class RequestCoreRecoveryAPITest extends SolrTestCaseJ4 { + + private RequestCoreRecovery api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private CoreAdminHandler.CoreAdminAsyncTracker asyncTracker; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + asyncTracker = new CoreAdminHandler.CoreAdminAsyncTracker(); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new RequestCoreRecovery(mockCoreContainer, asyncTracker, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCoreNameMissing() { + final SolrException thrown = + expectThrows(SolrException.class, () -> api.requestCoreRecovery(null)); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: core", thrown.getMessage()); + } + + @Test + public void testPassesParamsCorrectly() throws Exception { + api.requestCoreRecovery("someCore"); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("requestrecovery", params.get(ACTION)); + assertEquals("someCore", params.get(CORE)); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/RequestSyncShardAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestSyncShardAPITest.java new file mode 100644 index 000000000000..d7e0ab233020 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/RequestSyncShardAPITest.java @@ -0,0 +1,88 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CORE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrException; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link RequestSyncShard} */ +public class RequestSyncShardAPITest extends SolrTestCaseJ4 { + + private RequestSyncShard api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private CoreAdminHandler.CoreAdminAsyncTracker asyncTracker; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + asyncTracker = new CoreAdminHandler.CoreAdminAsyncTracker(); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new RequestSyncShard(mockCoreContainer, asyncTracker, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCoreNameMissing() { + final SolrException thrown = + expectThrows(SolrException.class, () -> api.requestSyncShard(null)); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: core", thrown.getMessage()); + } + + @Test + public void testPassesParamsCorrectly() throws Exception { + api.requestSyncShard("someCore"); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("requestsyncshard", params.get(ACTION)); + assertEquals("someCore", params.get(CORE)); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/SplitCoreAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/SplitCoreAPITest.java new file mode 100644 index 000000000000..0899cd65c44b --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/SplitCoreAPITest.java @@ -0,0 +1,109 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonAdminParams.SPLIT_KEY; +import static org.apache.solr.common.params.CommonParams.PATH; +import static org.apache.solr.common.params.CoreAdminParams.ACTION; +import static org.apache.solr.common.params.CoreAdminParams.CORE; +import static org.apache.solr.common.params.CoreAdminParams.TARGET_CORE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.SplitCoreRequestBody; +import org.apache.solr.common.SolrException; +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.admin.CoreAdminHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link SplitCore} */ +public class SplitCoreAPITest extends SolrTestCaseJ4 { + + private SplitCore api; + private CoreContainer mockCoreContainer; + private CoreAdminHandler mockCoreAdminHandler; + private CoreAdminHandler.CoreAdminAsyncTracker asyncTracker; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCoreAdminHandler = mock(CoreAdminHandler.class); + when(mockCoreContainer.getMultiCoreHandler()).thenReturn(mockCoreAdminHandler); + asyncTracker = new CoreAdminHandler.CoreAdminAsyncTracker(); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new SplitCore(mockCoreContainer, asyncTracker, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCoreNameMissing() { + final SolrException thrown = + expectThrows(SolrException.class, () -> api.splitCore(null, new SplitCoreRequestBody())); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: core", thrown.getMessage()); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new SplitCoreRequestBody(); + requestBody.path = List.of("path1", "path2"); + requestBody.targetCore = List.of("core1", "core2"); + requestBody.splitKey = "splitKey1"; + requestBody.splitMethod = "rewrite"; + requestBody.getRanges = true; + requestBody.ranges = "range1,range2"; + requestBody.async = "asyncId"; + + api.splitCore("coreName", requestBody); + + verify(mockCoreAdminHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("split", params.get(ACTION)); + assertEquals("coreName", params.get(CORE)); + assertArrayEquals(new String[] {"path1", "path2"}, params.getParams(PATH)); + assertArrayEquals(new String[] {"core1", "core2"}, params.getParams(TARGET_CORE)); + assertEquals("splitKey1", params.get(SPLIT_KEY)); + assertEquals("rewrite", params.get("splitMethod")); + assertEquals("true", params.get("getRanges")); + assertEquals("range1,range2", params.get("ranges")); + assertEquals("asyncId", params.get("async")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/SplitShardAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/SplitShardAPITest.java new file mode 100644 index 000000000000..12f8254fa817 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/SplitShardAPITest.java @@ -0,0 +1,114 @@ +/* + * 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.api; + +import static org.apache.solr.common.params.CommonAdminParams.SPLIT_KEY; +import static org.apache.solr.common.params.CommonParams.ACTION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.api.model.SplitShardRequestBody; +import org.apache.solr.common.SolrException; +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.admin.CollectionsHandler; +import org.apache.solr.request.SolrQueryRequest; +import org.apache.solr.request.SolrQueryRequestBase; +import org.apache.solr.response.SolrQueryResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link SplitShard} */ +public class SplitShardAPITest extends SolrTestCaseJ4 { + + private SplitShard api; + private CoreContainer mockCoreContainer; + private CollectionsHandler mockCollectionsHandler; + private ArgumentCaptor requestCaptor; + private SolrQueryRequest realRequest; + private SolrQueryResponse queryResponse; + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mockCoreContainer = mock(CoreContainer.class); + mockCollectionsHandler = mock(CollectionsHandler.class); + when(mockCoreContainer.getCollectionsHandler()).thenReturn(mockCollectionsHandler); + requestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); + realRequest = new SolrQueryRequestBase(null, new ModifiableSolrParams()) {}; + queryResponse = new SolrQueryResponse(); + api = new SplitShard(mockCoreContainer, realRequest, queryResponse); + } + + @Test + public void testReportsErrorIfCollectionNameMissing() { + final SolrException thrown = + expectThrows(SolrException.class, () -> api.splitShard(null, new SplitShardRequestBody())); + assertEquals(400, thrown.code()); + assertEquals("Missing required parameter: collection", thrown.getMessage()); + } + + @Test + public void testAllParamsPassedCorrectly() throws Exception { + final var requestBody = new SplitShardRequestBody(); + requestBody.shard = "shard1"; + requestBody.ranges = "someRanges"; + requestBody.splitKey = "someSplitKey"; + requestBody.numSubShards = 3; + requestBody.splitFuzz = "0.1"; + requestBody.timing = true; + requestBody.splitByPrefix = true; + requestBody.followAliases = true; + requestBody.splitMethod = "rewrite"; + requestBody.async = "someAsyncId"; + requestBody.waitForFinalState = true; + requestBody.coreProperties = Map.of("prop1", "val1"); + + api.splitShard("collName", requestBody); + + verify(mockCollectionsHandler).handleRequestBody(requestCaptor.capture(), any()); + SolrParams params = requestCaptor.getValue().getParams(); + + assertEquals("splitshard", params.get(ACTION)); + assertEquals("collName", params.get("collection")); + assertEquals("shard1", params.get("shard")); + assertEquals("someRanges", params.get("ranges")); + assertEquals("someSplitKey", params.get(SPLIT_KEY)); + assertEquals("3", params.get("numSubShards")); + assertEquals("0.1", params.get("splitFuzz")); + assertEquals("true", params.get("timing")); + assertEquals("true", params.get("splitByPrefix")); + assertEquals("true", params.get("followAliases")); + assertEquals("rewrite", params.get("splitMethod")); + assertEquals("someAsyncId", params.get("async")); + assertEquals("true", params.get("waitForFinalState")); + assertEquals("val1", params.get("property.prop1")); + } +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java deleted file mode 100644 index 1a61b6516fdb..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; -import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF; -import static org.apache.solr.common.params.CommonAdminParams.ASYNC; -import static org.apache.solr.common.params.CommonParams.ACTION; - -import org.apache.solr.common.cloud.ZkStateReader; -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.common.params.SolrParams; -import org.apache.solr.handler.admin.CollectionsHandler; -import org.apache.solr.handler.admin.TestCollectionAPIs; -import org.apache.solr.handler.admin.V2ApiMappingTest; -import org.junit.Test; - -/** - * Unit tests for the V2 APIs found in {@link org.apache.solr.handler.admin.api} that use the - * /c/{collection} path. - * - *

This test bears many similarities to {@link TestCollectionAPIs} which appears to test the - * mappings indirectly by checking message sent to the ZK overseer (which is similar, but not - * identical to the v1 param list). If there's no particular benefit to testing the mappings in this - * way (there very well may be), then we should combine these two test classes at some point in the - * future using the simpler approach here. - * - *

Note that the V2 requests made by these tests are not necessarily semantically valid. They - * shouldn't be taken as examples. In several instances, mutually exclusive JSON parameters are - * provided. This is done to exercise conversion of all parameters, even if particular combinations - * are never expected in the same request. - */ -public class V2CollectionAPIMappingTest extends V2ApiMappingTest { - - @Override - public void populateApiBag() { - final CollectionsHandler collectionsHandler = getRequestHandler(); - apiBag.registerObject(new MigrateDocsAPI(collectionsHandler)); - apiBag.registerObject(new ModifyCollectionAPI(collectionsHandler)); - apiBag.registerObject(new MoveReplicaAPI(collectionsHandler)); - apiBag.registerObject(new RebalanceLeadersAPI(collectionsHandler)); - } - - @Override - public CollectionsHandler createUnderlyingRequestHandler() { - return createMock(CollectionsHandler.class); - } - - @Override - public boolean isCoreSpecific() { - return false; - } - - @Test - public void testModifyCollectionAllProperties() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/collections/collName", - "POST", - "{ 'modify': {" - + "'replicationFactor': 123, " - + "'readOnly': true, " - + "'config': 'techproducts_config', " - + "'async': 'requestTrackingId', " - + "'properties': {" - + " 'foo': 'bar', " - + " 'baz': 456 " - + "}" - + "}}"); - - assertEquals( - CollectionParams.CollectionAction.MODIFYCOLLECTION.lowerName, v1Params.get(ACTION)); - assertEquals("collName", v1Params.get(COLLECTION)); - assertEquals(123, v1Params.getPrimitiveInt(ZkStateReader.REPLICATION_FACTOR)); - assertTrue(v1Params.getPrimitiveBool(ZkStateReader.READ_ONLY)); - assertEquals("techproducts_config", v1Params.get(COLL_CONF)); - assertEquals("requestTrackingId", v1Params.get(ASYNC)); - assertEquals("bar", v1Params.get("property.foo")); - assertEquals(456, v1Params.getPrimitiveInt("property.baz")); - } - - @Test - public void testMoveReplicaAllProperties() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/collections/collName", - "POST", - "{ 'move-replica': {" - + "'sourceNode': 'someSourceNode', " - + "'targetNode': 'someTargetNode', " - + "'replica': 'someReplica', " - + "'shard': 'someShard', " - + "'waitForFinalState': true, " - + "'timeout': 123, " - + "'inPlaceMove': true, " - + "'followAliases': true " - + "}}"); - - assertEquals(CollectionParams.CollectionAction.MOVEREPLICA.lowerName, v1Params.get(ACTION)); - assertEquals("collName", v1Params.get(COLLECTION)); - assertEquals("someSourceNode", v1Params.get("sourceNode")); - assertEquals("someTargetNode", v1Params.get("targetNode")); - assertEquals("someReplica", v1Params.get("replica")); - assertEquals("someShard", v1Params.get("shard")); - assertTrue(v1Params.getPrimitiveBool("waitForFinalState")); - assertEquals(123, v1Params.getPrimitiveInt("timeout")); - assertTrue(v1Params.getPrimitiveBool("inPlaceMove")); - assertTrue(v1Params.getPrimitiveBool("followAliases")); - } - - @Test - public void testMigrateDocsAllProperties() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/collections/collName", - "POST", - "{ 'migrate-docs': {" - + "'target': 'someTargetCollection', " - + "'splitKey': 'someSplitKey', " - + "'forwardTimeout': 123, " - + "'followAliases': true, " - + "'async': 'requestTrackingId' " - + "}}"); - - assertEquals(CollectionParams.CollectionAction.MIGRATE.lowerName, v1Params.get(ACTION)); - assertEquals("collName", v1Params.get(COLLECTION)); - assertEquals("someTargetCollection", v1Params.get("target.collection")); - assertEquals("someSplitKey", v1Params.get("split.key")); - assertEquals(123, v1Params.getPrimitiveInt("forward.timeout")); - assertTrue(v1Params.getPrimitiveBool("followAliases")); - assertEquals("requestTrackingId", v1Params.get(ASYNC)); - } - - @Test - public void testRebalanceLeadersAllProperties() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/collections/collName", - "POST", - "{ 'rebalance-leaders': {" + "'maxAtOnce': 123, " + "'maxWaitSeconds': 456" + "}}"); - - assertEquals( - CollectionParams.CollectionAction.REBALANCELEADERS.lowerName, v1Params.get(ACTION)); - assertEquals("collName", v1Params.get(COLLECTION)); - assertEquals(123, v1Params.getPrimitiveInt("maxAtOnce")); - assertEquals(456, v1Params.getPrimitiveInt("maxWaitSeconds")); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CoreAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CoreAPIMappingTest.java deleted file mode 100644 index bd3171cd1c39..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CoreAPIMappingTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.common.params.CommonAdminParams.ASYNC; -import static org.apache.solr.common.params.CommonAdminParams.SPLIT_KEY; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.apache.solr.common.params.CommonParams.PATH; -import static org.apache.solr.common.params.CoreAdminParams.CORE; -import static org.apache.solr.common.params.CoreAdminParams.CORE_NODE_NAME; -import static org.apache.solr.common.params.CoreAdminParams.NAME; -import static org.apache.solr.common.params.CoreAdminParams.OTHER; -import static org.apache.solr.common.params.CoreAdminParams.RANGES; -import static org.apache.solr.common.params.CoreAdminParams.TARGET_CORE; - -import java.util.Arrays; -import java.util.List; -import org.apache.solr.common.cloud.ZkStateReader; -import org.apache.solr.common.params.SolrParams; -import org.apache.solr.handler.admin.CoreAdminHandler; -import org.apache.solr.handler.admin.V2ApiMappingTest; -import org.junit.Test; - -/** - * Unit tests for the V2 APIs found in {@link org.apache.solr.handler.admin.api} that use the - * /cores/{core} path. - * - *

Note that the V2 requests made by these tests are not necessarily semantically valid. They - * shouldn't be taken as examples. In several instances, mutually exclusive JSON parameters are - * provided. This is done to exercise conversion of all parameters, even if particular combinations - * are never expected in the same request. - */ -public class V2CoreAPIMappingTest extends V2ApiMappingTest { - - private static final String NO_BODY = null; - - @Override - public CoreAdminHandler createUnderlyingRequestHandler() { - return createMock(CoreAdminHandler.class); - } - - @Override - public boolean isCoreSpecific() { - return false; - } - - @Override - public void populateApiBag() { - final CoreAdminHandler handler = getRequestHandler(); - apiBag.registerObject(new RenameCoreAPI(handler)); - apiBag.registerObject(new SplitCoreAPI(handler)); - apiBag.registerObject(new RequestCoreRecoveryAPI(handler)); - apiBag.registerObject(new PrepareCoreRecoveryAPI(handler)); - apiBag.registerObject(new RequestApplyCoreUpdatesAPI(handler)); - apiBag.registerObject(new RequestSyncShardAPI(handler)); - apiBag.registerObject(new RequestBufferUpdatesAPI(handler)); - } - - @Test - public void testRenameCoreAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/cores/coreName", "POST", "{\"rename\": {\"to\": \"otherCore\"}}"); - - assertEquals("rename", v1Params.get(ACTION)); - assertEquals("coreName", v1Params.get(CORE)); - assertEquals("otherCore", v1Params.get(OTHER)); - } - - @Test - public void testSplitCoreAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/cores/coreName", - "POST", - "{" - + "\"split\": {" - + "\"path\": [\"path1\", \"path2\"], " - + "\"targetCore\": [\"core1\", \"core2\"], " - + "\"splitKey\": \"someSplitKey\", " - + "\"getRanges\": true, " - + "\"ranges\": \"range1,range2\", " - + "\"async\": \"someRequestId\"}}"); - - assertEquals("split", v1Params.get(ACTION)); - assertEquals("coreName", v1Params.get(CORE)); - assertEquals("someSplitKey", v1Params.get(SPLIT_KEY)); - assertEquals("range1,range2", v1Params.get(RANGES)); - assertEquals("someRequestId", v1Params.get(ASYNC)); - final List pathEntries = Arrays.asList(v1Params.getParams(PATH)); - assertEquals(2, pathEntries.size()); - assertTrue(pathEntries.containsAll(List.of("path1", "path2"))); - final List targetCoreEntries = Arrays.asList(v1Params.getParams(TARGET_CORE)); - assertEquals(2, targetCoreEntries.size()); - assertTrue(targetCoreEntries.containsAll(List.of("core1", "core2"))); - } - - @Test - public void testRequestCoreRecoveryAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params("/cores/coreName", "POST", "{\"request-recovery\": {}}"); - - assertEquals("requestrecovery", v1Params.get(ACTION)); - assertEquals("coreName", v1Params.get(CORE)); - } - - @Test - public void testPrepareCoreRecoveryAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/cores/coreName", - "POST", - "{\"prep-recovery\": {" - + "\"nodeName\": \"someNodeName\", " - + "\"coreNodeName\": \"someCoreNodeName\", " - + "\"state\": \"someState\", " - + "\"checkLive\": true, " - + "\"onlyIfLeader\": true" - + "\"onlyIfLeaderActive\": true " - + "}}"); - - assertEquals("preprecovery", v1Params.get(ACTION)); - assertEquals("coreName", v1Params.get(CORE)); - assertEquals("someNodeName", v1Params.get("nodeName")); - assertEquals("someCoreNodeName", v1Params.get(CORE_NODE_NAME)); - assertEquals("someState", v1Params.get(ZkStateReader.STATE_PROP)); - assertTrue(v1Params.getPrimitiveBool("checkLive")); - assertTrue(v1Params.getPrimitiveBool("onlyIfLeader")); - assertTrue(v1Params.getPrimitiveBool("onlyIfLeaderActive")); - } - - @Test - public void testApplyCoreUpdatesAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params("/cores/coreName", "POST", "{\"request-apply-updates\": {}}"); - - assertEquals("requestapplyupdates", v1Params.get(ACTION)); - assertEquals("coreName", v1Params.get(NAME)); - } - - @Test - public void testSyncShardAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params("/cores/coreName", "POST", "{\"request-sync-shard\": {}}"); - - assertEquals("requestsyncshard", v1Params.get(ACTION)); - assertEquals("coreName", v1Params.get(CORE)); - } - - @Test - public void testRequestBufferUpdatesAllParams() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params("/cores/coreName", "POST", "{\"request-buffer-updates\": {}}"); - - assertEquals("requestbufferupdates", v1Params.get(ACTION)); - assertEquals("coreName", v1Params.get(NAME)); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java deleted file mode 100644 index c0ca1bff0c00..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2NodeAPIMappingTest.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import org.apache.solr.SolrTestCaseJ4; -import org.apache.solr.api.Api; -import org.apache.solr.api.ApiBag; -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.ContentStreamBase; -import org.apache.solr.handler.RequestHandlerBase; -import org.apache.solr.handler.admin.CoreAdminHandler; -import org.apache.solr.handler.admin.HealthCheckHandler; -import org.apache.solr.handler.admin.InfoHandler; -import org.apache.solr.handler.admin.LoggingHandler; -import org.apache.solr.handler.admin.PropertiesRequestHandler; -import org.apache.solr.handler.admin.SystemInfoHandler; -import org.apache.solr.handler.admin.ThreadDumpHandler; -import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.request.SolrQueryRequestBase; -import org.apache.solr.response.SolrQueryResponse; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -/** Unit tests for the v2 to v1 mapping for /node/ APIs. */ -public class V2NodeAPIMappingTest extends SolrTestCaseJ4 { - private ApiBag apiBag; - private ArgumentCaptor queryRequestCaptor; - private CoreAdminHandler mockCoresHandler; - private InfoHandler infoHandler; - private SystemInfoHandler mockSystemInfoHandler; - private LoggingHandler mockLoggingHandler; - private PropertiesRequestHandler mockPropertiesHandler; - private HealthCheckHandler mockHealthCheckHandler; - private ThreadDumpHandler mockThreadDumpHandler; - - @BeforeClass - public static void ensureWorkingMockito() { - assumeWorkingMockito(); - } - - @Before - public void setupApiBag() { - mockCoresHandler = mock(CoreAdminHandler.class); - infoHandler = mock(InfoHandler.class); - mockSystemInfoHandler = mock(SystemInfoHandler.class); - mockLoggingHandler = mock(LoggingHandler.class); - mockPropertiesHandler = mock(PropertiesRequestHandler.class); - mockHealthCheckHandler = mock(HealthCheckHandler.class); - mockThreadDumpHandler = mock(ThreadDumpHandler.class); - queryRequestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class); - - when(infoHandler.getSystemInfoHandler()).thenReturn(mockSystemInfoHandler); - when(infoHandler.getLoggingHandler()).thenReturn(mockLoggingHandler); - when(infoHandler.getPropertiesHandler()).thenReturn(mockPropertiesHandler); - when(infoHandler.getHealthCheckHandler()).thenReturn(mockHealthCheckHandler); - when(infoHandler.getThreadDumpHandler()).thenReturn(mockThreadDumpHandler); - - apiBag = new ApiBag(false); - registerAllNodeApis(apiBag, mockCoresHandler, infoHandler); - } - - @Test - public void testOverseerOpApiAllProperties() throws Exception { - final SolrParams v1Params = - captureConvertedCoreV1Params( - "/node", - "POST", - "{" - + "\"overseer-op\": {" - + "\"op\": \"asdf\", " - + "\"electionNode\": \"someNodeName\"" - + "}}"); - - assertEquals("overseerop", v1Params.get(ACTION)); - assertEquals("asdf", v1Params.get("op")); - assertEquals("someNodeName", v1Params.get("electionNode")); - } - - @Test - public void testRejoinLeaderElectionApiAllProperties() throws Exception { - final SolrParams v1Params = - captureConvertedCoreV1Params( - "/node", - "POST", - "{" - + "\"rejoin-leader-election\": {" - + "\"collection\": \"someCollection\", " - + "\"coreNodeName\": \"someNodeName\"," - + "\"core\": \"someCore\"," - + "\"rejoinAtHead\": true" - + "}}"); - - assertEquals("rejoinleaderelection", v1Params.get(ACTION)); - assertEquals("someCollection", v1Params.get("collection")); - assertEquals("someNodeName", v1Params.get("core_node_name")); - assertEquals("someCore", v1Params.get("core")); - assertEquals("true", v1Params.get("rejoinAtHead")); - } - - @Test - public void testSystemPropsApiAllProperties() throws Exception { - final ModifiableSolrParams solrParams = new ModifiableSolrParams(); - solrParams.add("name", "specificPropertyName"); - final SolrParams v1Params = - captureConvertedPropertiesV1Params("/node/properties", "GET", solrParams); - - assertEquals("specificPropertyName", v1Params.get("name")); - } - - @Test - public void testThreadDumpApiAllProperties() throws Exception { - final ModifiableSolrParams solrParams = new ModifiableSolrParams(); - solrParams.add("anyParamName", "anyParamValue"); - final SolrParams v1Params = - captureConvertedThreadDumpV1Params("/node/threads", "GET", solrParams); - - // All parameters are passed through to v1 API as-is - assertEquals("anyParamValue", v1Params.get("anyParamName")); - } - - @Test - public void testSystemInfoApiAllProperties() throws Exception { - final ModifiableSolrParams solrParams = new ModifiableSolrParams(); - solrParams.add("anyParamName", "anyParamValue"); - final SolrParams v1Params = captureConvertedSystemV1Params("/node/system", "GET", solrParams); - - // All parameters are passed through to v1 API as-is. - assertEquals("anyParamValue", v1Params.get("anyParamName")); - } - - @Test - public void testHealthCheckApiAllProperties() throws Exception { - final ModifiableSolrParams solrParams = new ModifiableSolrParams(); - solrParams.add("requireHealthyCores", "true"); - solrParams.add("maxGenerationLag", "123"); - final SolrParams v1Params = - captureConvertedHealthCheckV1Params("/node/health", "GET", solrParams); - - // All parameters are passed through to v1 API as-is. - assertEquals(true, v1Params.getBool("requireHealthyCores")); - assertEquals(123, v1Params.getPrimitiveInt("maxGenerationLag")); - } - - private SolrParams captureConvertedCoreV1Params(String path, String method, String v2RequestBody) - throws Exception { - return doCaptureParams( - path, method, new ModifiableSolrParams(), v2RequestBody, mockCoresHandler); - } - - private SolrParams captureConvertedSystemV1Params( - String path, String method, SolrParams inputParams) throws Exception { - return doCaptureParams(path, method, inputParams, null, mockSystemInfoHandler); - } - - private SolrParams captureConvertedPropertiesV1Params( - String path, String method, SolrParams inputParams) throws Exception { - return doCaptureParams(path, method, inputParams, null, mockPropertiesHandler); - } - - private SolrParams captureConvertedHealthCheckV1Params( - String path, String method, SolrParams inputParams) throws Exception { - return doCaptureParams(path, method, inputParams, null, mockHealthCheckHandler); - } - - private SolrParams captureConvertedThreadDumpV1Params( - String path, String method, SolrParams inputParams) throws Exception { - return doCaptureParams(path, method, inputParams, null, mockThreadDumpHandler); - } - - private SolrParams doCaptureParams( - String path, - String method, - SolrParams inputParams, - String v2RequestBody, - RequestHandlerBase mockHandler) - throws Exception { - final HashMap parts = new HashMap<>(); - ModifiableSolrParams solrParams = new ModifiableSolrParams(); - inputParams.stream() - .forEach( - e -> { - solrParams.add(e.getKey(), e.getValue()); - }); - final Api api = apiBag.lookup(path, method, parts); - final SolrQueryResponse rsp = new SolrQueryResponse(); - final SolrQueryRequestBase req = - new SolrQueryRequestBase(null, solrParams) { - @Override - public List getCommands(boolean validateInput) { - if (v2RequestBody == null) return Collections.emptyList(); - return ApiBag.getCommandOperations( - new ContentStreamBase.StringStream(v2RequestBody), api.getCommandSchema(), true); - } - }; - - api.call(req, rsp); - verify(mockHandler).handleRequestBody(queryRequestCaptor.capture(), any()); - return queryRequestCaptor.getValue().getParams(); - } - - private static void registerAllNodeApis( - ApiBag apiBag, CoreAdminHandler coreHandler, InfoHandler infoHandler) { - apiBag.registerObject(new OverseerOperationAPI(coreHandler)); - apiBag.registerObject(new RejoinLeaderElectionAPI(coreHandler)); - apiBag.registerObject(new NodePropertiesAPI(infoHandler.getPropertiesHandler())); - apiBag.registerObject(new NodeThreadsAPI(infoHandler.getThreadDumpHandler())); - apiBag.registerObject(new NodeSystemInfoAPI(infoHandler.getSystemInfoHandler())); - apiBag.registerObject(new NodeHealthAPI(infoHandler.getHealthCheckHandler())); - } -} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java deleted file mode 100644 index 9731922de461..000000000000 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.api; - -import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; -import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; -import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; -import static org.apache.solr.common.params.CommonAdminParams.ASYNC; -import static org.apache.solr.common.params.CommonAdminParams.NUM_SUB_SHARDS; -import static org.apache.solr.common.params.CommonAdminParams.SPLIT_BY_PREFIX; -import static org.apache.solr.common.params.CommonAdminParams.SPLIT_FUZZ; -import static org.apache.solr.common.params.CommonAdminParams.SPLIT_KEY; -import static org.apache.solr.common.params.CommonAdminParams.SPLIT_METHOD; -import static org.apache.solr.common.params.CommonAdminParams.WAIT_FOR_FINAL_STATE; -import static org.apache.solr.common.params.CommonParams.ACTION; -import static org.apache.solr.common.params.CommonParams.TIMING; - -import org.apache.solr.common.params.CollectionParams; -import org.apache.solr.common.params.CoreAdminParams; -import org.apache.solr.common.params.SolrParams; -import org.apache.solr.handler.admin.CollectionsHandler; -import org.apache.solr.handler.admin.V2ApiMappingTest; -import org.junit.Test; - -/** - * Unit tests for the V2 APIs that use the /c/{collection}/shards or /c/{collection}/shards/{shard} - * paths. - * - *

Note that the V2 requests made by these tests are not necessarily semantically valid. They - * shouldn't be taken as examples. In several instances, mutually exclusive JSON parameters are - * provided. This is done to exercise conversion of all parameters, even if particular combinations - * are never expected in the same request. - */ -public class V2ShardsAPIMappingTest extends V2ApiMappingTest { - - @Override - public void populateApiBag() { - final CollectionsHandler collectionsHandler = getRequestHandler(); - apiBag.registerObject(new SplitShardAPI(collectionsHandler)); - } - - @Override - public CollectionsHandler createUnderlyingRequestHandler() { - return createMock(CollectionsHandler.class); - } - - @Override - public boolean isCoreSpecific() { - return false; - } - - @Test - public void testSplitShardAllProperties() throws Exception { - final SolrParams v1Params = - captureConvertedV1Params( - "/collections/collName/shards", - "POST", - "{ 'split': {" - + "'shard': 'shard1', " - + "'ranges': 'someRangeValues', " - + "'splitKey': 'someSplitKey', " - + "'numSubShards': 123, " - + "'splitFuzz': 'some_fuzz_value', " - + "'timing': true, " - + "'splitByPrefix': true, " - + "'followAliases': true, " - + "'splitMethod': 'rewrite', " - + "'async': 'some_async_id', " - + "'waitForFinalState': true, " - + "'coreProperties': {" - + " 'foo': 'foo1', " - + " 'bar': 'bar1', " - + "}}}"); - - assertEquals(CollectionParams.CollectionAction.SPLITSHARD.lowerName, v1Params.get(ACTION)); - assertEquals("collName", v1Params.get(COLLECTION)); - assertEquals("shard1", v1Params.get(SHARD_ID_PROP)); - assertEquals("someRangeValues", v1Params.get(CoreAdminParams.RANGES)); - assertEquals("someSplitKey", v1Params.get(SPLIT_KEY)); - assertEquals(123, v1Params.getPrimitiveInt(NUM_SUB_SHARDS)); - assertEquals("some_fuzz_value", v1Params.get(SPLIT_FUZZ)); - assertTrue(v1Params.getPrimitiveBool(TIMING)); - assertTrue(v1Params.getPrimitiveBool(SPLIT_BY_PREFIX)); - assertTrue(v1Params.getPrimitiveBool(FOLLOW_ALIASES)); - assertEquals("rewrite", v1Params.get(SPLIT_METHOD)); - assertEquals("some_async_id", v1Params.get(ASYNC)); - assertTrue(v1Params.getPrimitiveBool(WAIT_FOR_FINAL_STATE)); - assertEquals("foo1", v1Params.get("property.foo")); - assertEquals("bar1", v1Params.get("property.bar")); - } -} diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc index 0ed88262b49f..46315188aabe 100644 --- a/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc +++ b/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc @@ -65,12 +65,10 @@ V2 API:: ==== [source,bash] ---- -curl -X POST http://localhost:8983/api/collections/collection1/shards -H 'Content-Type: application/json' -d ' +curl -X POST http://localhost:8983/api/collections/collection1/shards/split -H 'Content-Type: application/json' -d ' { - "split": { - "shard": "shard1", - "async": "1000" - } + "shard": "shard1", + "async": "1000" } ' ---- diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc index a9e1e8cff3c1..123bbb01a655 100644 --- a/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc +++ b/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc @@ -105,33 +105,25 @@ Description of some of the keys in the above example: * `**spec/url/params**`: List of supported URL request params * `**availableSubPaths**`: List of valid URL subpaths and the HTTP method(s) each supports -Example of introspect for a POST API: `\http://localhost:8983/api/c/gettingstarted/_introspect?method=POST&command=modify` +Example of introspect for a POST API that uses command-based dispatch: `\http://localhost:8983/api/cluster/plugin/_introspect?method=POST&command=add` [source,json] ---- { "spec":[{ - "documentation":"https://solr.apache.org/guide/solr/latest/configuration-guide/collections-api.html", - "description":"Several collection-level operations are supported with this endpoint: modify collection attributes; reload a collection; migrate documents to a different collection; rebalance collection leaders; balance properties across shards; and add or delete a replica property.", + "description":"Add a plugin.", "methods":["POST"], - "url":{"paths":["/collections/{collection}", - "/c/{collection}"]}, - "commands":{"modify":{ - "documentation":"https://solr.apache.org/guide/solr/latest/deployment-guide/collection-management.html#modifycollection", - "description":"Modifies specific attributes of a collection. Multiple attributes can be changed at one time.", + "url":{"paths":["/cluster/plugin"]}, + "commands":{"add":{ "type":"object", "properties":{ - "replicationFactor":{ + "name":{ "type":"string", - "description":"The number of replicas to be created for each shard. Replicas are physical copies of each shard, acting as failover for the shard. Note that changing this value on an existing collection does not automatically add more replicas to the collection. However, it will allow add-replica commands to succeed."}}}}}], - "WARNING":"This response format is experimental. It is likely to change in the future.", - "availableSubPaths":{ - "/c/gettingstarted/select":["POST", "GET"], - "/c/gettingstarted/config":["POST", "GET"], - "/c/gettingstarted/schema":["POST", "GET"], - "/c/gettingstarted/export":["POST", "GET"], - "/c/gettingstarted/admin/ping":["POST", "GET"], - "/c/gettingstarted/update":["POST"]} + "description":"A unique name for the plugin."}, + "class":{ + "type":"string", + "description":"The implementation class of the plugin. If the class is in a package, use the syntax 'package:class'."}}}}}], + "WARNING":"This response format is experimental. It is likely to change in the future." } ---- @@ -140,12 +132,12 @@ The key is the command name and the value is a JSON object describing the comman == Invocation Examples -For the "gettingstarted" collection, set the replication factor and whether to automatically add replicas (see above for the introspect output for the `"modify"` command used here): +For the "gettingstarted" collection, set the replication factor using the new JAX-RS path: [source,bash] ---- -$ curl http://localhost:8983/api/c/gettingstarted -H 'Content-type:application/json' -d ' -{ modify: { replicationFactor: "3" } }' +$ curl -X POST http://localhost:8983/api/collections/gettingstarted/modify -H 'Content-type:application/json' -d ' +{ "replicationFactor": "3" }' {"responseHeader":{"status":0,"QTime":842}} ---- diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc index 46452619f7c7..dcadf8dd388b 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc @@ -395,15 +395,11 @@ http://localhost:8983/solr/admin/collections?action=modifycollection&collection= V2 API:: + ==== -With the v2 API, the `modify` command is provided as part of the JSON data that contains the required parameters: - [source,bash] ---- -curl -X POST http://localhost:8983/api/collections/techproducts_v2 -H 'Content-Type: application/json' -d ' +curl -X POST http://localhost:8983/api/collections/techproducts_v2/modify -H 'Content-Type: application/json' -d ' { - "modify": { - "replicationFactor": 2 - } + "replicationFactor": 2 } ' ---- @@ -825,12 +821,10 @@ V2 API:: ==== [source,bash] ---- -curl -X POST http://localhost:8983/api/collections/techproducts_v2 -H 'Content-Type: application/json' -d ' +curl -X POST http://localhost:8983/api/collections/techproducts_v2/migrate -H 'Content-Type: application/json' -d ' { - "migrate-docs": { - "target": "postMigrationCollection", - "splitKey": "key1!" - } + "target": "postMigrationCollection", + "splitKey": "key1!" } ' ---- @@ -2236,15 +2230,11 @@ http://localhost:8983/solr/admin/collections?action=REBALANCELEADERS&collection= V2 API:: + ==== -With the v2 API, the `rebalance-leaders` command is provided as part of the JSON data that contains the required parameters: - [source,bash] ---- -curl -X POST http://localhost:8983/api/collections/techproducts -H 'Content-Type: application/json' -d ' +curl -X POST http://localhost:8983/api/collections/techproducts/rebalance-leaders -H 'Content-Type: application/json' -d ' { - "rebalance-leaders": { - "maxAtOnce": 3 - } + "maxAtOnce": 3 } ' ---- diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc index 73c19a9db478..76a10db8db32 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/replica-management.adoc @@ -336,12 +336,10 @@ V2 API:: [source,bash] ---- -curl -X POST http://localhost:8983/api/collections/techproducts -H 'Content-Type: application/json' -d ' +curl -X POST http://localhost:8983/api/collections/techproducts/move-replica -H 'Content-Type: application/json' -d ' { - "move-replica":{ - "replica":"core_node6", - "targetNode": "localhost:8983_solr" - } + "replica":"core_node6", + "targetNode": "localhost:8983_solr" } ' ---- diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/shard-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/shard-management.adoc index e623424b6aa0..16e0e7ac9c33 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/shard-management.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/shard-management.adoc @@ -65,11 +65,9 @@ V2 API:: [source,bash] ---- -curl -X POST http://localhost:8983/api/collections/techproducts/shards -H 'Content-Type: application/json' -d ' +curl -X POST http://localhost:8983/api/collections/techproducts/shards/split -H 'Content-Type: application/json' -d ' { - "split":{ - "shard":"shard1" - } + "shard":"shard1" } ' ---- diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java index 345c06502566..934b17021b4c 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/SystemInfoRequest.java @@ -77,7 +77,7 @@ public ApiVersion getApiVersion() { // (/solr) /admin/info/system return ApiVersion.V1; } - // Ref. org.apache.solr.handler.admin.api.NodeSystemInfoAPI : /node/system + // Ref. org.apache.solr.handler.admin.api.NodeSystemInfo : /node/system return ApiVersion.V2; } }