This is an automated email from the ASF dual-hosted git repository.

jsweeney pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 0e7a05de565 Adding support for compression within the ZkCLI tool 
(#1348)
0e7a05de565 is described below

commit 0e7a05de5656e4d686eedde00863d88d4d820410
Author: Justin Sweeney <[email protected]>
AuthorDate: Tue Feb 28 07:52:17 2023 -0700

    Adding support for compression within the ZkCLI tool (#1348)
    
    This provides support for using the ZkCLI tool to get/put compressed 
state.json data, providing the ability to easily edit state.json even when 
compression is enabled.
---
 .../core/src/java/org/apache/solr/cloud/ZkCLI.java |  80 +++++++++++++--
 .../src/test/org/apache/solr/cloud/ZkCLITest.java  | 108 +++++++++++++++++++++
 solr/server/scripts/cloud-scripts/zkcli.sh         |   4 +-
 3 files changed, 181 insertions(+), 11 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkCLI.java 
b/solr/core/src/java/org/apache/solr/cloud/ZkCLI.java
index f8ae0e194de..9cac8e45661 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkCLI.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkCLI.java
@@ -25,7 +25,9 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Properties;
@@ -42,11 +44,17 @@ import org.apache.commons.cli.ParseException;
 import org.apache.commons.cli.PosixParser;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.StringUtils;
 import org.apache.solr.common.cloud.ClusterProperties;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkMaintenanceUtils;
+import org.apache.solr.common.util.Compressor;
+import org.apache.solr.common.util.ZLibCompressor;
 import org.apache.solr.core.ConfigSetService;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.util.CLIO;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
@@ -234,6 +242,9 @@ public class ZkCLI implements CLIO {
       // start up a tmp zk server first
       String zkServerAddress = line.getOptionValue(ZKHOST);
       String solrHome = line.getOptionValue(SOLRHOME);
+      if (StringUtils.isEmpty(solrHome)) {
+        solrHome = System.getProperty("solr.home");
+      }
 
       String solrPort = null;
       if (line.hasOption(RUNZK)) {
@@ -256,6 +267,38 @@ public class ZkCLI implements CLIO {
         zkServer.parseConfig();
         zkServer.start();
       }
+
+      int minStateByteLenForCompression = -1;
+      Compressor compressor = new ZLibCompressor();
+
+      if (solrHome != null) {
+        try {
+          Path solrHomePath = Paths.get(solrHome);
+          Properties props = new Properties();
+          props.put(SolrXmlConfig.ZK_HOST, zkServerAddress);
+          NodeConfig nodeConfig = NodeConfig.loadNodeConfig(solrHomePath, 
props);
+          minStateByteLenForCompression =
+              nodeConfig.getCloudConfig().getMinStateByteLenForCompression();
+          String stateCompressorClass = 
nodeConfig.getCloudConfig().getStateCompressorClass();
+          if (!StringUtils.isEmpty(stateCompressorClass)) {
+            Class<? extends Compressor> compressionClass =
+                
Class.forName(stateCompressorClass).asSubclass(Compressor.class);
+            compressor = 
compressionClass.getDeclaredConstructor().newInstance();
+          }
+        } catch (SolrException e) {
+          // Failed to load solr.xml
+          stdout.println(
+              "Failed to load solr.xml from ZK or SolrHome, put/get operations 
on compressed data will use data as is. If you intention is to read and 
de-compress data or compress and write data, then solr.xml must be 
accessible.");
+        } catch (ClassNotFoundException
+            | NoSuchMethodException
+            | InstantiationException
+            | IllegalAccessException
+            | InvocationTargetException e) {
+          stdout.println("Unable to find or instantiate compression class: " + 
e.getMessage());
+          System.exit(1);
+        }
+      }
+
       SolrZkClient zkClient = null;
       try {
         zkClient =
@@ -264,6 +307,7 @@ public class ZkCLI implements CLIO {
                 .withTimeout(30000, TimeUnit.MILLISECONDS)
                 .withConnTimeOut(30000, TimeUnit.MILLISECONDS)
                 .withReconnectListener(() -> {})
+                .withCompressor(compressor)
                 .build();
 
         if (line.getOptionValue(CMD).equalsIgnoreCase(BOOTSTRAP)) {
@@ -359,16 +403,16 @@ public class ZkCLI implements CLIO {
                 "-" + PUT + " requires two args - the path to create and the 
data string");
             System.exit(1);
           }
-          String path = arglist.get(0).toString();
+          String path = arglist.get(0);
+          byte[] data = arglist.get(1).getBytes(StandardCharsets.UTF_8);
+          if (shouldCompressData(data, path, minStateByteLenForCompression)) {
+            // state.json should be compressed before being put to ZK
+            data = compressor.compressBytes(data);
+          }
           if (zkClient.exists(path, true)) {
-            zkClient.setData(
-                path, 
arglist.get(1).toString().getBytes(StandardCharsets.UTF_8), true);
+            zkClient.setData(path, data, true);
           } else {
-            zkClient.create(
-                path,
-                arglist.get(1).toString().getBytes(StandardCharsets.UTF_8),
-                CreateMode.PERSISTENT,
-                true);
+            zkClient.create(path, data, CreateMode.PERSISTENT, true);
           }
         } else if (line.getOptionValue(CMD).equalsIgnoreCase(PUT_FILE)) {
           List<String> arglist = line.getArgList();
@@ -382,11 +426,16 @@ public class ZkCLI implements CLIO {
 
           String path = arglist.get(0).toString();
           InputStream is = new FileInputStream(arglist.get(1).toString());
+          byte[] data = IOUtils.toByteArray(is);
+          if (shouldCompressData(data, path, minStateByteLenForCompression)) {
+            // state.json should be compressed before being put to ZK
+            data = compressor.compressBytes(data);
+          }
           try {
             if (zkClient.exists(path, true)) {
-              zkClient.setData(path, IOUtils.toByteArray(is), true);
+              zkClient.setData(path, data, true);
             } else {
-              zkClient.create(path, IOUtils.toByteArray(is), 
CreateMode.PERSISTENT, true);
+              zkClient.create(path, data, CreateMode.PERSISTENT, true);
             }
           } finally {
             IOUtils.closeQuietly(is);
@@ -450,4 +499,15 @@ public class ZkCLI implements CLIO {
       stdout.println("Unexpected exception:" + exp.getMessage());
     }
   }
+
+  private static boolean shouldCompressData(
+      byte[] data, String path, int minStateByteLenForCompression) {
+    if (path.endsWith("state.json")
+        && minStateByteLenForCompression > -1
+        && data.length > minStateByteLenForCompression) {
+      // state.json should be compressed before being put to ZK
+      return true;
+    }
+    return false;
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java 
b/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java
index 58101f0c7c6..1ba4fde8caa 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ZkCLITest.java
@@ -32,6 +32,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.filefilter.RegexFileFilter;
 import org.apache.commons.io.filefilter.TrueFileFilter;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.solr.SolrJettyTestBase;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
@@ -40,6 +41,7 @@ import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.util.ZLibCompressor;
 import org.apache.solr.core.ConfigSetService;
 import org.apache.solr.util.ExternalPaths;
 import org.apache.zookeeper.CreateMode;
@@ -63,6 +65,8 @@ public class ZkCLITest extends SolrTestCaseJ4 {
 
   private SolrZkClient zkClient;
 
+  private PrintStream originalSystemOut;
+
   protected static final String SOLR_HOME = SolrTestCaseJ4.TEST_HOME();
 
   @BeforeClass
@@ -87,6 +91,8 @@ public class ZkCLITest extends SolrTestCaseJ4 {
     Path tmpDir = createTempDir();
     solrHome = exampleHome;
 
+    originalSystemOut = System.out;
+
     zkDir = tmpDir.resolve("zookeeper/server1/data");
     log.info("ZooKeeper dataDir:{}", zkDir);
     zkServer = new ZkTestServer(zkDir);
@@ -170,6 +176,28 @@ public class ZkCLITest extends SolrTestCaseJ4 {
         zkClient.getData("/data.txt", null, null, true), 
data.getBytes(StandardCharsets.UTF_8));
   }
 
+  @Test
+  public void testPutCompressed() throws Exception {
+    // test put compressed
+    System.setProperty("solr.home", solrHome);
+    System.setProperty("minStateByteLenForCompression", "0");
+
+    String data = "my data";
+    ZLibCompressor zLibCompressor = new ZLibCompressor();
+    byte[] expected = 
zLibCompressor.compressBytes(data.getBytes(StandardCharsets.UTF_8));
+    String[] args =
+        new String[] {"-zkhost", zkServer.getZkAddress(), "-cmd", "put", 
"/state.json", data};
+    ZkCLI.main(args);
+    assertArrayEquals(zkClient.getZooKeeper().getData("/state.json", null, 
null), expected);
+
+    // test re-put to existing
+    data = "my data deux";
+    expected = 
zLibCompressor.compressBytes(data.getBytes(StandardCharsets.UTF_8));
+    args = new String[] {"-zkhost", zkServer.getZkAddress(), "-cmd", "put", 
"/state.json", data};
+    ZkCLI.main(args);
+    assertArrayEquals(zkClient.getZooKeeper().getData("/state.json", null, 
null), expected);
+  }
+
   @Test
   public void testPutFile() throws Exception {
     // test put file
@@ -197,6 +225,37 @@ public class ZkCLITest extends SolrTestCaseJ4 {
     assertEquals("Should get back what we put in ZK", fromZk, fromLoc);
   }
 
+  @Test
+  public void testPutFileCompressed() throws Exception {
+    // test put file compressed
+    System.setProperty("solr.home", solrHome);
+    System.setProperty("minStateByteLenForCompression", "0");
+
+    String[] args =
+        new String[] {
+          "-zkhost",
+          zkServer.getZkAddress(),
+          "-cmd",
+          "putfile",
+          "/state.json",
+          SOLR_HOME + File.separator + "solr-stress-new.xml"
+        };
+    ZkCLI.main(args);
+
+    byte[] fromZk = zkClient.getZooKeeper().getData("/state.json", null, null);
+    File locFile = new File(SOLR_HOME + File.separator + 
"solr-stress-new.xml");
+    InputStream is = new FileInputStream(locFile);
+    byte[] fromLoc;
+    try {
+      fromLoc = IOUtils.toByteArray(is);
+      ZLibCompressor zLibCompressor = new ZLibCompressor();
+      fromLoc = zLibCompressor.compressBytes(fromLoc);
+    } finally {
+      IOUtils.closeQuietly(is);
+    }
+    assertArrayEquals("Should get back what we put in ZK", fromLoc, fromZk);
+  }
+
   @Test
   public void testPutFileNotExists() {
     // test put file
@@ -379,9 +438,34 @@ public class ZkCLITest extends SolrTestCaseJ4 {
   public void testGet() throws Exception {
     String getNode = "/getNode";
     byte[] data = "getNode-data".getBytes(StandardCharsets.UTF_8);
+    ByteArrayOutputStream systemOut = new ByteArrayOutputStream();
     this.zkClient.create(getNode, data, CreateMode.PERSISTENT, true);
     String[] args = new String[] {"-zkhost", zkServer.getZkAddress(), "-cmd", 
"get", getNode};
+    ZkCLI.setStdout(new PrintStream(systemOut, true, StandardCharsets.UTF_8));
     ZkCLI.main(args);
+    assertArrayEquals(
+        data,
+        StringUtils.removeEnd(systemOut.toString(StandardCharsets.UTF_8), "\n")
+            .getBytes(StandardCharsets.UTF_8));
+  }
+
+  @Test
+  public void testGetCompressed() throws Exception {
+    System.setProperty("solr.home", solrHome);
+    System.setProperty("minStateByteLenForCompression", "0");
+
+    String getNode = "/getNode";
+    byte[] data = "getNode-data".getBytes(StandardCharsets.UTF_8);
+    ZLibCompressor zLibCompressor = new ZLibCompressor();
+    ByteArrayOutputStream systemOut = new ByteArrayOutputStream();
+    this.zkClient.create(getNode, zLibCompressor.compressBytes(data), 
CreateMode.PERSISTENT, true);
+    String[] args = new String[] {"-zkhost", zkServer.getZkAddress(), "-cmd", 
"get", getNode};
+    ZkCLI.setStdout(new PrintStream(systemOut, true, StandardCharsets.UTF_8));
+    ZkCLI.main(args);
+    assertArrayEquals(
+        data,
+        StringUtils.removeEnd(systemOut.toString(StandardCharsets.UTF_8), "\n")
+            .getBytes(StandardCharsets.UTF_8));
   }
 
   @Test
@@ -404,6 +488,27 @@ public class ZkCLITest extends SolrTestCaseJ4 {
     assertArrayEquals(data, readData);
   }
 
+  @Test
+  public void testGetFileCompressed() throws Exception {
+    File tmpDir = createTempDir().toFile();
+
+    String getNode = "/getFileNode";
+    byte[] data = "getFileNode-data".getBytes(StandardCharsets.UTF_8);
+    ZLibCompressor zLibCompressor = new ZLibCompressor();
+    this.zkClient.create(getNode, zLibCompressor.compressBytes(data), 
CreateMode.PERSISTENT, true);
+
+    File file =
+        new File(tmpDir, "solrtest-getfile-" + this.getClass().getName() + "-" 
+ System.nanoTime());
+    String[] args =
+        new String[] {
+          "-zkhost", zkServer.getZkAddress(), "-cmd", "getfile", getNode, 
file.getAbsolutePath()
+        };
+    ZkCLI.main(args);
+
+    byte[] readData = FileUtils.readFileToByteArray(file);
+    assertArrayEquals(data, readData);
+  }
+
   @Test
   public void testGetFileNotExists() throws Exception {
     String getNode = "/getFileNotExistsNode";
@@ -502,6 +607,9 @@ public class ZkCLITest extends SolrTestCaseJ4 {
     if (zkServer != null) {
       zkServer.shutdown();
     }
+    System.clearProperty("solr.home");
+    System.clearProperty("minStateByteLenForCompression");
+    System.setOut(originalSystemOut);
     super.tearDown();
   }
 }
diff --git a/solr/server/scripts/cloud-scripts/zkcli.sh 
b/solr/server/scripts/cloud-scripts/zkcli.sh
index fa4e4556bfa..4477df48634 100755
--- a/solr/server/scripts/cloud-scripts/zkcli.sh
+++ b/solr/server/scripts/cloud-scripts/zkcli.sh
@@ -11,6 +11,8 @@ sdir="`dirname \"$0\"`"
 
 log4j_config="file:$sdir/../../resources/log4j2-console.xml"
 
+solr_home="$sdir/../../solr"
+
 # Settings for ZK ACL
 
#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.DigestZkACLProvider
 \
 #  
-DzkCredentialsProvider=org.apache.solr.common.cloud.DigestZkCredentialsProvider
 \
@@ -21,6 +23,6 @@ log4j_config="file:$sdir/../../resources/log4j2-console.xml"
 #...
 #   -DzkDigestCredentialsFile=/path/to/zkDigestCredentialsFile.properties
 #...
-PATH=$JAVA_HOME/bin:$PATH $JVM $SOLR_ZK_CREDS_AND_ACLS $ZKCLI_JVM_FLAGS 
-Dlog4j.configurationFile=$log4j_config \
+PATH=$JAVA_HOME/bin:$PATH $JVM $SOLR_ZK_CREDS_AND_ACLS $ZKCLI_JVM_FLAGS 
-Dlog4j.configurationFile=$log4j_config -DsolrHome=$solr_home \
 -classpath 
"$sdir/../../solr-webapp/webapp/WEB-INF/lib/*:$sdir/../../lib/ext/*:$sdir/../../lib/*"
 org.apache.solr.cloud.ZkCLI ${1+"$@"}
 

Reply via email to