This is an automated email from the ASF dual-hosted git repository.
broustant pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-sandbox.git
The following commit(s) were added to refs/heads/main by this push:
new 1de1ce5 Shard split supports encryption. (#103)
1de1ce5 is described below
commit 1de1ce559338ae111e5c7de8fc83b01b2b7c3cd1
Author: Bruno Roustant <[email protected]>
AuthorDate: Fri Apr 26 16:04:54 2024 +0200
Shard split supports encryption. (#103)
---
.../solr/encryption/EncryptionUpdateHandler.java | 31 +++--
.../encryption/EncryptionRequestHandlerTest.java | 30 ++---
.../apache/solr/encryption/EncryptionTestUtil.java | 18 ++-
.../EncryptionUpdateHandlerSplitTest.java | 136 +++++++++++++++++++++
.../resources/configs/collection1/solrconfig.xml | 2 +-
5 files changed, 183 insertions(+), 34 deletions(-)
diff --git
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateHandler.java
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateHandler.java
index 8b86d5f..d9cee7a 100644
---
a/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateHandler.java
+++
b/encryption/src/main/java/org/apache/solr/encryption/EncryptionUpdateHandler.java
@@ -20,6 +20,7 @@ import org.apache.lucene.index.IndexWriter;
import org.apache.solr.core.SolrCore;
import org.apache.solr.update.CommitUpdateCommand;
import org.apache.solr.update.DirectUpdateHandler2;
+import org.apache.solr.update.SplitIndexCommand;
import org.apache.solr.update.UpdateHandler;
import java.io.IOException;
@@ -57,25 +58,37 @@ public class EncryptionUpdateHandler extends
DirectUpdateHandler2 {
if (!super.shouldCommit(cmd, writer)) {
return false;
}
+ cmd.commitData = transferCommitData(cmd.commitData);
+ return true;
+ }
+
+ private Map<String, String> transferCommitData(Map<String, String>
commitData) throws IOException {
// Two cases:
- // - If cmd.commitData is null, then transfer all the latest commit
transferable
+ // - If commitData is null, then transfer all the latest commit
transferable
// data to the current commit.
- // - If cmd.commitData is not null, nothing is transferred. It is the
caller
+ // - If commitData is not null, nothing is transferred. It is the caller
// responsibility to include all the required user data from the latest
commit.
// That way, the caller can remove some entries.
- if (cmd.commitData == null) {
+ if (commitData == null) {
Map<String, String> latestCommitData =
readLatestCommit(core).getUserData();
- Map<String, String> commitData = null;
+ Map<String, String> newCommitData = null;
for (Map.Entry<String, String> latestCommitEntry :
latestCommitData.entrySet()) {
if (latestCommitEntry.getKey().startsWith(TRANSFERABLE_COMMIT_DATA)) {
- if (commitData == null) {
- commitData = new HashMap<>();
+ if (newCommitData == null) {
+ newCommitData = new HashMap<>();
}
- commitData.put(latestCommitEntry.getKey(),
latestCommitEntry.getValue());
+ newCommitData.put(latestCommitEntry.getKey(),
latestCommitEntry.getValue());
}
}
- cmd.commitData = commitData;
+ return newCommitData;
}
- return true;
+ return commitData;
+ }
+
+ @Override
+ public void split(SplitIndexCommand cmd) throws IOException {
+ // Transfer the parent core commit data to the sub-shard cores.
+ cmd.commitData = transferCommitData(cmd.commitData);
+ super.split(cmd);
}
}
diff --git
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
index f2515af..a943bae 100644
---
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
+++
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionRequestHandlerTest.java
@@ -34,7 +34,6 @@ import java.util.UUID;
import static
org.apache.solr.encryption.EncryptionDirectoryFactory.PROPERTY_INNER_ENCRYPTION_DIRECTORY_FACTORY;
import static org.apache.solr.encryption.EncryptionRequestHandler.NO_KEY_ID;
-import static org.apache.solr.encryption.EncryptionTestUtil.EncryptionStatus;
import static org.apache.solr.encryption.EncryptionUtil.getKeyIdFromCommit;
import static org.apache.solr.encryption.TestingKeySupplier.KEY_ID_1;
import static org.apache.solr.encryption.TestingKeySupplier.KEY_ID_2;
@@ -88,7 +87,7 @@ public class EncryptionRequestHandlerTest extends
SolrCloudTestCase {
@Test
public void testEncryptionFromNoKeysToOneKey_NoIndex() throws Exception {
// Send an encrypt request with a key id on an empty index.
- encryptAndExpectCompletion(KEY_ID_1);
+ testUtil.encryptAndExpectCompletion(KEY_ID_1);
// Index some documents to create a first segment.
testUtil.indexDocsAndCommit("weather broadcast");
@@ -104,10 +103,10 @@ public class EncryptionRequestHandlerTest extends
SolrCloudTestCase {
@Test
public void testEncryptionFromNoKeysToOneKeyToNoKeys_NoIndex() throws
Exception {
// Send an encrypt request with a key id on an empty index.
- encryptAndExpectCompletion(KEY_ID_1);
+ testUtil.encryptAndExpectCompletion(KEY_ID_1);
// Send another encrypt request with no key id, still on the empty index.
- encryptAndExpectCompletion(NO_KEY_ID);
+ testUtil.encryptAndExpectCompletion(NO_KEY_ID);
// Index some documents to create a first segment.
testUtil.indexDocsAndCommit("weather broadcast");
@@ -134,7 +133,7 @@ public class EncryptionRequestHandlerTest extends
SolrCloudTestCase {
forceClearText = false;
// Send an encrypt request with a key id.
- encryptAndWaitForCompletion(KEY_ID_1);
+ testUtil.encryptAndWaitForCompletion(KEY_ID_1);
// Verify that the segment is encrypted.
forceClearText = true;
@@ -154,7 +153,7 @@ public class EncryptionRequestHandlerTest extends
SolrCloudTestCase {
testUtil.indexDocsAndCommit("foggy weather");
// Send an encrypt request with another key id.
- encryptAndWaitForCompletion(KEY_ID_2);
+ testUtil.encryptAndWaitForCompletion(KEY_ID_2);
// Verify that the segment is encrypted.
forceClearText = true;
@@ -173,7 +172,7 @@ public class EncryptionRequestHandlerTest extends
SolrCloudTestCase {
testUtil.indexDocsAndCommit("foggy weather");
// Send an encrypt request with no key id.
- encryptAndWaitForCompletion(NO_KEY_ID);
+ testUtil.encryptAndWaitForCompletion(NO_KEY_ID);
// Verify that the segment is cleartext.
forceClearText = true;
@@ -185,7 +184,7 @@ public class EncryptionRequestHandlerTest extends
SolrCloudTestCase {
testUtil.indexDocsAndCommit("cloudy weather");
// Send an encrypt request with another key id.
- encryptAndWaitForCompletion(KEY_ID_2);
+ testUtil.encryptAndWaitForCompletion(KEY_ID_2);
// Verify that the segment is encrypted.
forceClearText = true;
@@ -196,21 +195,6 @@ public class EncryptionRequestHandlerTest extends
SolrCloudTestCase {
testUtil.assertQueryReturns("weather", 4);
}
- private void encryptAndExpectCompletion(String keyId) {
- encrypt(keyId, true);
- }
-
- private void encryptAndWaitForCompletion(String keyId) throws
InterruptedException {
- encrypt(keyId, false);
- testUtil.waitUntilEncryptionIsComplete(keyId);
- }
-
- private void encrypt(String keyId, boolean expectComplete) {
- EncryptionStatus encryptionStatus = testUtil.encrypt(keyId);
- assertTrue(encryptionStatus.isSuccess());
- assertEquals(expectComplete, encryptionStatus.isComplete());
- }
-
private static void clearMockValues() {
forceClearText = false;
soleKeyIdAllowed = null;
diff --git
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionTestUtil.java
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionTestUtil.java
index 61d36c4..ebb9538 100644
---
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionTestUtil.java
+++
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionTestUtil.java
@@ -45,6 +45,7 @@ import static
org.apache.solr.encryption.EncryptionRequestHandler.PARAM_KEY_ID;
import static
org.apache.solr.encryption.EncryptionRequestHandler.STATE_COMPLETE;
import static org.apache.solr.encryption.EncryptionRequestHandler.STATUS;
import static
org.apache.solr.encryption.EncryptionRequestHandler.STATUS_SUCCESS;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -100,7 +101,7 @@ public class EncryptionTestUtil {
*/
public void assertQueryReturns(String query, int expectedNumResults) throws
Exception {
QueryResponse response = cloudSolrClient.query(collectionName, new
SolrQuery(query));
- Assert.assertEquals(expectedNumResults, response.getResults().size());
+ assertEquals(expectedNumResults, response.getResults().size());
}
public EncryptionStatus encrypt(String keyId) {
@@ -116,6 +117,21 @@ public class EncryptionTestUtil {
return encryptionStatus;
}
+ public void encryptAndExpectCompletion(String keyId) {
+ encryptAndCheck(keyId, true);
+ }
+
+ public void encryptAndWaitForCompletion(String keyId) throws
InterruptedException {
+ encryptAndCheck(keyId, false);
+ waitUntilEncryptionIsComplete(keyId);
+ }
+
+ private void encryptAndCheck(String keyId, boolean expectComplete) {
+ EncryptionStatus encryptionStatus = encrypt(keyId);
+ assertTrue(encryptionStatus.isSuccess());
+ assertEquals(expectComplete, encryptionStatus.isComplete());
+ }
+
public void waitUntilEncryptionIsComplete(String keyId) throws
InterruptedException {
RetryUtil.retryUntil("Timeout waiting for encryption completion",
50,
diff --git
a/encryption/src/test/java/org/apache/solr/encryption/EncryptionUpdateHandlerSplitTest.java
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionUpdateHandlerSplitTest.java
new file mode 100644
index 0000000..4e40031
--- /dev/null
+++
b/encryption/src/test/java/org/apache/solr/encryption/EncryptionUpdateHandlerSplitTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.encryption;
+
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.MiniSolrCloudCluster;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.update.SolrIndexSplitter;
+import org.apache.solr.update.SplitIndexCommand;
+import org.apache.solr.update.UpdateHandler;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.apache.solr.encryption.CommitUtil.readLatestCommit;
+import static org.apache.solr.encryption.EncryptionUtil.getKeyIdFromCommit;
+import static org.apache.solr.encryption.TestingKeySupplier.KEY_ID_1;
+
+/**
+ * Tests {@link EncryptionUpdateHandler} commit data transfer during shard
split.
+ */
+public class EncryptionUpdateHandlerSplitTest extends SolrCloudTestCase {
+
+ private static final String COLLECTION_PREFIX =
EncryptionUpdateHandlerSplitTest.class.getSimpleName() + "-collection-";
+
+ private String collectionName;
+ private EncryptionTestUtil testUtil;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ System.setProperty("solr.updateHandler",
TestEncryptionUpdateHandler.class.getName());
+ EncryptionTestUtil.setInstallDirProperty();
+ cluster = new MiniSolrCloudCluster.Builder(2, createTempDir())
+ .addConfig("config", EncryptionTestUtil.getConfigPath("collection1"))
+ .configure();
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ cluster.shutdown();
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ collectionName = COLLECTION_PREFIX + UUID.randomUUID();
+ CollectionAdminRequest
+ .createCollection(collectionName, 2, 1)
+ .process(cluster.getSolrClient());
+ cluster.waitForActiveCollection(collectionName, 2, 2);
+ testUtil = new EncryptionTestUtil(cluster.getSolrClient(), collectionName);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+
CollectionAdminRequest.deleteCollection(collectionName).process(cluster.getSolrClient());
+ super.tearDown();
+ }
+
+ @Test
+ public void testSplitTransfersCommitData() throws Exception {
+ // Index some documents to create a first encrypted segment.
+ testUtil.encryptAndExpectCompletion(KEY_ID_1);
+ testUtil.indexDocsAndCommit("weather broadcast");
+
+ SolrIndexSplitter.SplitMethod splitMethod = random().nextBoolean() ?
+ SolrIndexSplitter.SplitMethod.LINK :
SolrIndexSplitter.SplitMethod.REWRITE;
+ CollectionAdminRequest.SplitShard splitShard =
CollectionAdminRequest.splitShard(collectionName)
+ .setNumSubShards(2)
+ .setShardName("shard1")
+ .setSplitMethod(splitMethod.toLower());
+ splitShard.process(cluster.getSolrClient());
+ waitForState("Timed out waiting for sub-shards to be active",
+ collectionName,
+ SolrCloudTestCase.activeClusterShape(3, 4));
+
+ assertNotNull(TestEncryptionUpdateHandler.cmd);
+
+ if (TestEncryptionUpdateHandler.cmd.cores != null) {
+ // Check the sub-shard cores if we have them (LINK split method).
+ for (SolrCore subCore : TestEncryptionUpdateHandler.cmd.cores) {
+ assertEquals(KEY_ID_1, getKeyIdFromCommitData(subCore));
+ }
+ }
+ }
+
+ private static String getKeyIdFromCommitData(SolrCore core) throws
IOException {
+ Map<String, String> commitData = readLatestCommit(core).getUserData();
+ assertNotNull(commitData);
+ return getKeyIdFromCommit("0", commitData);
+ }
+
+ /**
+ * Captures the {@link SplitIndexCommand} for validation.
+ */
+ public static class TestEncryptionUpdateHandler extends
EncryptionUpdateHandler {
+
+ static volatile SplitIndexCommand cmd;
+
+ public TestEncryptionUpdateHandler(SolrCore core) {
+ super(core);
+ }
+
+ public TestEncryptionUpdateHandler(SolrCore core, UpdateHandler
updateHandler) {
+ super(core, updateHandler);
+ }
+
+ @Override
+ public void split(SplitIndexCommand cmd) throws IOException {
+ TestEncryptionUpdateHandler.cmd = cmd;
+ // Check the parent core commit data here while it is still active.
+ SolrCore parentCore = cmd.getReq().getSearcher().getCore();
+ assertEquals(KEY_ID_1, getKeyIdFromCommitData(parentCore));
+ super.split(cmd);
+ }
+ }
+}
diff --git a/encryption/src/test/resources/configs/collection1/solrconfig.xml
b/encryption/src/test/resources/configs/collection1/solrconfig.xml
index d9a7bb8..0062cf5 100644
--- a/encryption/src/test/resources/configs/collection1/solrconfig.xml
+++ b/encryption/src/test/resources/configs/collection1/solrconfig.xml
@@ -45,7 +45,7 @@
</directoryFactory>
<!-- EncryptionUpdateHandler transfers the encryption key ids from a commit
to the next. -->
- <updateHandler class="org.apache.solr.encryption.EncryptionUpdateHandler">
+ <updateHandler
class="${solr.updateHandler:org.apache.solr.encryption.EncryptionUpdateHandler}">
<!-- EncryptionUpdateLog encrypts transaction logs if the index is
encrypted. -->
<updateLog
class="${solr.updateLog:org.apache.solr.encryption.EncryptionUpdateLog}"/>
</updateHandler>