gerlowskija commented on code in PR #2128:
URL: https://github.com/apache/solr/pull/2128#discussion_r1439701751


##########
solr/core/src/java/org/apache/solr/handler/admin/api/MergeIndexes.java:
##########
@@ -0,0 +1,207 @@
+/*
+ * 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 java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.inject.Inject;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.IOUtils;
+import org.apache.solr.client.api.endpoint.MergeIndexesApi;
+import org.apache.solr.client.api.model.MergeIndexesRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.CachingDirectoryFactory;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.DirectoryFactory;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.security.PermissionNameProvider;
+import org.apache.solr.update.MergeIndexesCommand;
+import org.apache.solr.update.processor.UpdateRequestProcessor;
+import org.apache.solr.update.processor.UpdateRequestProcessorChain;
+import org.apache.solr.util.RefCounted;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of V2 API interface {@link MergeIndexesApi} for merging one 
or more indexes to
+ * another index.
+ *
+ * @see MergeIndexesApi
+ * @see MergeIndexesRequestBody
+ * @see CoreAdminAPIBase
+ */
+public class MergeIndexes extends CoreAdminAPIBase implements MergeIndexesApi {
+
+  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Inject
+  public MergeIndexes(
+      CoreContainer coreContainer,
+      CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker,
+      SolrQueryRequest req,
+      SolrQueryResponse rsp) {
+    super(coreContainer, coreAdminAsyncTracker, req, rsp);
+  }
+
+  @Override
+  @PermissionName(PermissionNameProvider.Name.CORE_EDIT_PERM)
+  public SolrJerseyResponse mergeIndexes(String coreName, 
MergeIndexesRequestBody requestBody)
+      throws Exception {
+    ensureRequiredParameterProvided("coreName", coreName);
+    SolrJerseyResponse response = 
instantiateJerseyResponse(SolrJerseyResponse.class);
+
+    return handlePotentiallyAsynchronousTask(
+        response,
+        coreName,
+        requestBody.async,
+        "merge-indices",
+        () -> {
+          try {
+            SolrCore core = coreContainer.getCore(coreName);
+            SolrQueryRequest wrappedReq = null;
+            if (core == null) return response;
+
+            List<SolrCore> sourceCores = new ArrayList<>();
+            List<RefCounted<SolrIndexSearcher>> searchers = new ArrayList<>();
+            // stores readers created from indexDir param values
+            List<DirectoryReader> readersToBeClosed = new ArrayList<>();
+            Map<Directory, Boolean> dirsToBeReleased = new HashMap<>();
+
+            try {
+              var dirNames =
+                  Optional.ofNullable(requestBody.indexDir).orElseGet(() -> 
new ArrayList<>());
+              // requestBody.indexDir.toArray(new String[0]);
+              if (dirNames.size() == 0) {
+                var sources =
+                    Optional.ofNullable(requestBody.srcCore).orElseGet(() -> 
new ArrayList<>());
+                if (sources.size() == 0)

Review Comment:
   [0] I have a slight light preference for `List.isEmpty` for readability 
reasons, but that might just be personal preference 🤷  



##########
solr/api/src/java/org/apache/solr/client/api/endpoint/MergeIndexesApi.java:
##########
@@ -0,0 +1,50 @@
+/*
+ * 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 javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.MergeIndexesRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/**
+ * V2 API for merging one or more indexes(either from multiple Solr cores or 
multiple index
+ * directories) to another index.
+ *
+ * <p>The new API (POST /api/cores/coreName/merge-indices {...}) is equivalent 
to the v1
+ * /admin/cores?action=mergeindexes command.
+ *
+ * @see MergeIndexesRequestBody
+ */
+@Path("/cores/{coreName}/merge-indices")
+public interface MergeIndexesApi {
+  @POST
+  @Operation(
+      summary = "The MERGEINDEXES action merges one or more indexes to another 
index.",
+      tags = {"cores"})
+  SolrJerseyResponse mergeIndexes(
+      @Parameter(description = "The name of the target core.", required = true)

Review Comment:
   [0] The first time I read this, it wasn't clear to me what "target" meant in 
the context of merging data.  Maybe that's just me.  But maybe something like 
the following would be clearer:
   
   ```
   description = "The core that the specified indices are merged into,", 
required = true)
   ```



##########
solr/core/src/test/org/apache/solr/handler/admin/api/MergeIndexesTest.java:
##########
@@ -0,0 +1,112 @@
+/*
+ * 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 java.nio.file.Paths;
+import java.util.Arrays;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.endpoint.MergeIndexesApi;
+import org.apache.solr.client.api.model.MergeIndexesRequestBody;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class MergeIndexesTest extends SolrTestCaseJ4 {
+  private MergeIndexesApi mergeIndexesApi;
+  private CoreContainer coreContainer;
+
+  @BeforeClass
+  public static void initializeCoreAndRequestFactory() throws Exception {
+    initCore("solrconfig.xml", "schema.xml");
+    lrf = h.getRequestFactory("/api", 0, 10);
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    SolrQueryRequest solrQueryRequest = req();
+    SolrQueryResponse solrQueryResponse = new SolrQueryResponse();
+    coreContainer = h.getCoreContainer();
+
+    CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker =
+        new CoreAdminHandler.CoreAdminAsyncTracker();
+    mergeIndexesApi =
+        new MergeIndexes(coreContainer, coreAdminAsyncTracker, 
solrQueryRequest, solrQueryResponse);
+    assumeWorkingMockito();
+  }
+
+  @Test
+  public void testReportsErrorIfBothIndexDirAndSrcCoreAreEmpty() throws 
Exception {
+    var requestBody = new MergeIndexesRequestBody();
+    requestBody.indexDir = null;
+    requestBody.srcCore = null;
+    var ex =
+        assertThrows(
+            SolrException.class, () -> mergeIndexesApi.mergeIndexes(coreName, 
requestBody));
+    assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code());
+    assertTrue(ex.getMessage().contains("At least one indexDir or srcCore must 
be specified"));
+  }
+
+  @Test
+  public void testReportsErrorIfSrcCoreMissing() throws Exception {
+    final var INVALID_CORE = "INVALID_CORE";
+    var requestBody = new MergeIndexesRequestBody();
+    requestBody.srcCore = Arrays.asList(INVALID_CORE);
+    var ex =
+        assertThrows(
+            SolrException.class, () -> mergeIndexesApi.mergeIndexes(coreName, 
requestBody));
+    assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code());
+    assertTrue(ex.getMessage().contains("Core: " + INVALID_CORE + " does not 
exist"));
+  }
+
+  @Test
+  public void testReportsErrorIfPathNotAllowed() throws Exception {
+    CoreContainer mockCoreContainer = Mockito.mock(CoreContainer.class);
+    CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker =
+        Mockito.mock(CoreAdminHandler.CoreAdminAsyncTracker.class);
+    SolrCore mockSolrCore = Mockito.mock(SolrCore.class);
+
+    final var requestBody = new MergeIndexesRequestBody();
+    final var path_not_allowed = "INVALID_PATH";
+    requestBody.indexDir = Arrays.asList(path_not_allowed);
+
+    var mergeIndexes =
+        new MergeIndexes(mockCoreContainer, coreAdminAsyncTracker, req(), new 
SolrQueryResponse());
+
+    Mockito.when(mockCoreContainer.getCore(coreName)).thenReturn(mockSolrCore);

Review Comment:
   [0] Personal preference, but I find these when/thenReturn calls to be more 
readable if the methods are all statically imported.



##########
solr/core/src/test/org/apache/solr/handler/admin/api/MergeIndexesTest.java:
##########
@@ -0,0 +1,112 @@
+/*
+ * 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 java.nio.file.Paths;
+import java.util.Arrays;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.endpoint.MergeIndexesApi;
+import org.apache.solr.client.api.model.MergeIndexesRequestBody;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class MergeIndexesTest extends SolrTestCaseJ4 {

Review Comment:
   [+1] Thanks for adding unit tests!



##########
solr/core/src/java/org/apache/solr/handler/admin/api/MergeIndexes.java:
##########
@@ -0,0 +1,207 @@
+/*
+ * 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 java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.inject.Inject;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.IOUtils;
+import org.apache.solr.client.api.endpoint.MergeIndexesApi;
+import org.apache.solr.client.api.model.MergeIndexesRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.CachingDirectoryFactory;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.DirectoryFactory;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.security.PermissionNameProvider;
+import org.apache.solr.update.MergeIndexesCommand;
+import org.apache.solr.update.processor.UpdateRequestProcessor;
+import org.apache.solr.update.processor.UpdateRequestProcessorChain;
+import org.apache.solr.util.RefCounted;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of V2 API interface {@link MergeIndexesApi} for merging one 
or more indexes to
+ * another index.
+ *
+ * @see MergeIndexesApi
+ * @see MergeIndexesRequestBody
+ * @see CoreAdminAPIBase
+ */
+public class MergeIndexes extends CoreAdminAPIBase implements MergeIndexesApi {
+
+  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Inject
+  public MergeIndexes(
+      CoreContainer coreContainer,
+      CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker,
+      SolrQueryRequest req,
+      SolrQueryResponse rsp) {
+    super(coreContainer, coreAdminAsyncTracker, req, rsp);
+  }
+
+  @Override
+  @PermissionName(PermissionNameProvider.Name.CORE_EDIT_PERM)
+  public SolrJerseyResponse mergeIndexes(String coreName, 
MergeIndexesRequestBody requestBody)
+      throws Exception {
+    ensureRequiredParameterProvided("coreName", coreName);
+    SolrJerseyResponse response = 
instantiateJerseyResponse(SolrJerseyResponse.class);
+
+    return handlePotentiallyAsynchronousTask(
+        response,
+        coreName,
+        requestBody.async,
+        "merge-indices",
+        () -> {
+          try {
+            SolrCore core = coreContainer.getCore(coreName);
+            SolrQueryRequest wrappedReq = null;
+            if (core == null) return response;
+
+            List<SolrCore> sourceCores = new ArrayList<>();
+            List<RefCounted<SolrIndexSearcher>> searchers = new ArrayList<>();
+            // stores readers created from indexDir param values
+            List<DirectoryReader> readersToBeClosed = new ArrayList<>();
+            Map<Directory, Boolean> dirsToBeReleased = new HashMap<>();
+
+            try {
+              var dirNames =
+                  Optional.ofNullable(requestBody.indexDir).orElseGet(() -> 
new ArrayList<>());
+              // requestBody.indexDir.toArray(new String[0]);

Review Comment:
   [-0] This commented-out line should be removed, I'm guessing?



##########
solr/api/src/java/org/apache/solr/client/api/model/MergeIndexesRequestBody.java:
##########
@@ -0,0 +1,38 @@
+/*
+ * 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;
+import java.util.List;
+
+/** Request body for endpoints {@link 
org.apache.solr.client.api.endpoint.MergeIndexesApi} */
+public class MergeIndexesRequestBody {
+  @Schema(description = "Multi-valued, directories that would be merged.")
+  @JsonProperty
+  public List<String> indexDir;

Review Comment:
   [-0] Maybe the variable name (and, by connection, the JSON property name) 
should reflect the fact that this is meant to hold multiple directory paths. 
e.g. `indexDirs` or maybe `indexDirectories`
   
   (I guess the same comment also goes for the 'srcCore' name down on line 31.)



##########
solr/core/src/java/org/apache/solr/handler/admin/api/MergeIndexes.java:
##########
@@ -0,0 +1,196 @@
+/*
+ * 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 java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.inject.Inject;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.IOUtils;
+import org.apache.solr.client.api.endpoint.MergeIndexesApi;
+import org.apache.solr.client.api.model.MergeIndexesRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.core.CachingDirectoryFactory;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.DirectoryFactory;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.admin.CoreAdminHandler;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.update.MergeIndexesCommand;
+import org.apache.solr.update.processor.UpdateRequestProcessor;
+import org.apache.solr.update.processor.UpdateRequestProcessorChain;
+import org.apache.solr.util.RefCounted;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MergeIndexes extends CoreAdminAPIBase implements MergeIndexesApi {
+
+  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  @Inject
+  public MergeIndexes(
+      CoreContainer coreContainer,
+      CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker,
+      SolrQueryRequest req,
+      SolrQueryResponse rsp) {
+    super(coreContainer, coreAdminAsyncTracker, req, rsp);
+  }
+
+  @Override
+  public SolrJerseyResponse mergeIndexes(String coreName, 
MergeIndexesRequestBody requestBody)

Review Comment:
   👍 I think core-edit makes sense here.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to