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

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pulsar.git


The following commit(s) were added to refs/heads/master by this push:
     new 06f9e2d  CLI for offload (#1865)
06f9e2d is described below

commit 06f9e2d126eba015bc9b0461014aa3b74794f546
Author: Ivan Kelly <[email protected]>
AuthorDate: Thu May 31 18:53:09 2018 +0200

    CLI for offload (#1865)
    
    * CLI for offload
    
    This patch adds two CLI commands for offload, one to trigger offload,
    and another to check offload status.
    
    Triggering offload requires a size-threshold argument, which is the
    maximum number of bytes which should be retained locally. The prefix
    of ledgers over this threshold will be offloaded.
    
    ```
    pulsar-admin topics offload --size-threshold 104857600 
persistent://public/default/topic1
    pulsar-admin topics offload-status -w persistent://public/default/topic1
    ```
    
    Master Issue: #1511
    
    * luc's review comments
---
 .../org/apache/pulsar/admin/cli/CmdTopics.java     | 94 +++++++++++++++++++++-
 .../org/apache/pulsar/admin/cli/TestCmdTopics.java | 58 +++++++++++++
 .../pulsar/tests/integration/TestS3Offload.java    | 42 +++++-----
 3 files changed, 175 insertions(+), 19 deletions(-)

diff --git 
a/pulsar-client-tools/src/main/java/org/apache/pulsar/admin/cli/CmdTopics.java 
b/pulsar-client-tools/src/main/java/org/apache/pulsar/admin/cli/CmdTopics.java
index b8f93f7..4c14310 100644
--- 
a/pulsar-client-tools/src/main/java/org/apache/pulsar/admin/cli/CmdTopics.java
+++ 
b/pulsar-client-tools/src/main/java/org/apache/pulsar/admin/cli/CmdTopics.java
@@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
 import com.beust.jcommander.Parameter;
 import com.beust.jcommander.Parameters;
 import com.beust.jcommander.converters.CommaParameterSplitter;
+import com.google.common.collect.Lists;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonObject;
@@ -32,6 +33,7 @@ import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufUtil;
 import io.netty.buffer.Unpooled;
 
+import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
@@ -44,7 +46,7 @@ import org.apache.pulsar.client.api.Message;
 import org.apache.pulsar.client.api.MessageId;
 import org.apache.pulsar.client.impl.BatchMessageIdImpl;
 import org.apache.pulsar.client.impl.MessageIdImpl;
-
+import org.apache.pulsar.common.policies.data.PersistentTopicInternalStats;
 
 @Parameters(commandDescription = "Operations on persistent topics")
 public class CmdTopics extends CmdBase {
@@ -89,6 +91,8 @@ public class CmdTopics extends CmdBase {
         jcommander.addCommand("terminate", new Terminate());
         jcommander.addCommand("compact", new Compact());
         jcommander.addCommand("compaction-status", new CompactionStatusCmd());
+        jcommander.addCommand("offload", new Offload());
+        jcommander.addCommand("offload-status", new OffloadStatusCmd());
     }
 
     @Parameters(commandDescription = "Get the list of topics under a 
namespace.")
@@ -614,6 +618,94 @@ public class CmdTopics extends CmdBase {
         }
     }
 
+    static MessageId 
findFirstLedgerWithinThreshold(List<PersistentTopicInternalStats.LedgerInfo> 
ledgers,
+                                                    long sizeThreshold) {
+        long suffixSize = 0L;
+
+        ledgers = Lists.reverse(ledgers);
+        for (PersistentTopicInternalStats.LedgerInfo l : ledgers) {
+            suffixSize += l.size;
+            if (suffixSize >= sizeThreshold) {
+                return new MessageIdImpl(l.ledgerId, 0L, -1);
+            }
+        }
+        return null;
+    }
+
+    @Parameters(commandDescription = "Trigger offload of data from a topic to 
long-term storage (e.g. Amazon S3)")
+    private class Offload extends CliCommand {
+        @Parameter(names = { "-s", "--size-threshold" },
+                   description = "Maximum amount of data to keep in BookKeeper 
for the specified topic",
+                   required = true)
+        private Long sizeThreshold;
+
+        @Parameter(description = "persistent://tenant/namespace/topic", 
required = true)
+        private java.util.List<String> params;
+
+        @Override
+        void run() throws PulsarAdminException {
+            String persistentTopic = validatePersistentTopic(params);
+
+            PersistentTopicInternalStats stats = 
topics.getInternalStats(persistentTopic);
+            if (stats.ledgers.size() < 1) {
+                throw new PulsarAdminException("Topic doesn't have any data");
+            }
+
+            LinkedList<PersistentTopicInternalStats.LedgerInfo> ledgers = new 
LinkedList(stats.ledgers);
+            ledgers.get(ledgers.size()-1).size = stats.currentLedgerSize; // 
doesn't get filled in now it seems
+            MessageId messageId = findFirstLedgerWithinThreshold(ledgers, 
sizeThreshold);
+
+            if (messageId == null) {
+                System.out.println("Nothing to offload");
+                return;
+            }
+
+            topics.triggerOffload(persistentTopic, messageId);
+            System.out.println("Offload triggered for " + persistentTopic + " 
for messages before " + messageId);
+        }
+    }
+
+    @Parameters(commandDescription = "Check the status of data offloading from 
a topic to long-term storage")
+    private class OffloadStatusCmd extends CliCommand {
+        @Parameter(description = "persistent://tenant/namespace/topic", 
required = true)
+        private java.util.List<String> params;
+
+        @Parameter(names = { "-w", "--wait-complete" },
+                   description = "Wait for offloading to complete", required = 
false)
+        private boolean wait = false;
+
+        @Override
+        void run() throws PulsarAdminException {
+            String persistentTopic = validatePersistentTopic(params);
+
+            try {
+                LongRunningProcessStatus status = 
topics.offloadStatus(persistentTopic);
+                while (wait && status.status == 
LongRunningProcessStatus.Status.RUNNING) {
+                    Thread.sleep(1000);
+                    status = topics.offloadStatus(persistentTopic);
+                }
+
+                switch (status.status) {
+                case NOT_RUN:
+                    System.out.println("Offload has not been run for " + 
persistentTopic
+                                       + " since broker startup");
+                    break;
+                case RUNNING:
+                    System.out.println("Offload is currently running");
+                    break;
+                case SUCCESS:
+                    System.out.println("Offload was a success");
+                    break;
+                case ERROR:
+                    System.out.println("Error in offload");
+                    throw new PulsarAdminException("Error offloading: " + 
status.lastError);
+                }
+            } catch (InterruptedException e) {
+                throw new PulsarAdminException(e);
+            }
+        }
+    }
+
     private static int validateTimeString(String s) {
         char last = s.charAt(s.length() - 1);
         String subStr = s.substring(0, s.length() - 1);
diff --git 
a/pulsar-client-tools/src/test/java/org/apache/pulsar/admin/cli/TestCmdTopics.java
 
b/pulsar-client-tools/src/test/java/org/apache/pulsar/admin/cli/TestCmdTopics.java
new file mode 100644
index 0000000..df2d14c
--- /dev/null
+++ 
b/pulsar-client-tools/src/test/java/org/apache/pulsar/admin/cli/TestCmdTopics.java
@@ -0,0 +1,58 @@
+/**
+ * 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.pulsar.admin.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.pulsar.client.impl.MessageIdImpl;
+import 
org.apache.pulsar.common.policies.data.PersistentTopicInternalStats.LedgerInfo;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestCmdTopics {
+    private static LedgerInfo newLedger(long id, long entries, long size) {
+        LedgerInfo l = new LedgerInfo();
+        l.ledgerId = id;
+        l.entries = entries;
+        l.size = size;
+        return l;
+    }
+
+    @Test
+    public void testFindFirstLedgerWithinThreshold() throws Exception {
+        List<LedgerInfo> ledgers = new ArrayList<>();
+        ledgers.add(newLedger(0, 10, 1000));
+        ledgers.add(newLedger(1, 10, 2000));
+        ledgers.add(newLedger(2, 10, 3000));
+
+        // test huge threshold
+        Assert.assertNull(CmdTopics.findFirstLedgerWithinThreshold(ledgers, 
Long.MAX_VALUE));
+
+        // test small threshold
+        Assert.assertEquals(CmdTopics.findFirstLedgerWithinThreshold(ledgers, 
0),
+                            new MessageIdImpl(2, 0, -1));
+
+        // test middling thresholds
+        Assert.assertEquals(CmdTopics.findFirstLedgerWithinThreshold(ledgers, 
1000),
+                            new MessageIdImpl(2, 0, -1));
+        Assert.assertEquals(CmdTopics.findFirstLedgerWithinThreshold(ledgers, 
5000),
+                            new MessageIdImpl(1, 0, -1));
+    }
+}
diff --git 
a/tests/integration/s3-offload/src/test/java/org/apache/pulsar/tests/integration/TestS3Offload.java
 
b/tests/integration/s3-offload/src/test/java/org/apache/pulsar/tests/integration/TestS3Offload.java
index c5b27f0..e3ee495 100644
--- 
a/tests/integration/s3-offload/src/test/java/org/apache/pulsar/tests/integration/TestS3Offload.java
+++ 
b/tests/integration/s3-offload/src/test/java/org/apache/pulsar/tests/integration/TestS3Offload.java
@@ -93,7 +93,7 @@ public class TestS3Offload extends Arquillian {
     }
 
     @Test
-    public void testPublishOffloadAndConsumeViaAdmin() throws Exception {
+    public void testPublishOffloadAndConsumeViaCLI() throws Exception {
         PulsarClusterUtils.runOnAnyBroker(docker, CLUSTER_NAME,
                 "/pulsar/bin/pulsar-admin", "tenants",
                 "create", "--allowed-clusters", CLUSTER_NAME,
@@ -102,8 +102,8 @@ public class TestS3Offload extends Arquillian {
                 "/pulsar/bin/pulsar-admin", "namespaces",
                 "create", "--clusters", CLUSTER_NAME, "s3-offload-test/ns1");
 
-        String brokerIp = PulsarClusterUtils.brokerSet(docker, CLUSTER_NAME)
-            .stream().map((c) -> DockerUtils.getContainerIP(docker, 
c)).findFirst().get();
+        String broker = PulsarClusterUtils.brokerSet(docker, 
CLUSTER_NAME).stream().findAny().get();
+        String brokerIp = DockerUtils.getContainerIP(docker, broker);
         String proxyIp  = PulsarClusterUtils.proxySet(docker, CLUSTER_NAME)
             .stream().map((c) -> DockerUtils.getContainerIP(docker, 
c)).findFirst().get();
         String serviceUrl = "pulsar://" + proxyIp + ":6650";
@@ -131,21 +131,27 @@ public class TestS3Offload extends Arquillian {
 
             firstLedger = info.ledgers.get(0).ledgerId;
 
-            // trigger offload
-            try (PulsarAdmin admin = new PulsarAdmin(new URL(adminUrl), "", 
"")) {
-                log.info("Trigger offload");
-
-                admin.topics().triggerOffload(topic, latestMessage);
-
-                LongRunningProcessStatus status = 
admin.topics().offloadStatus(topic);
-                while (status.status == 
LongRunningProcessStatus.Status.RUNNING) {
-                    Thread.sleep(100);
-                    status = admin.topics().offloadStatus(topic);
-                }
-                Assert.assertEquals(status.status, 
LongRunningProcessStatus.Status.SUCCESS);
-
-                log.info("Offload complete");
-            }
+            // first offload with a high threshold, nothing should offload
+            String output = DockerUtils.runCommand(docker, broker,
+                    "/pulsar/bin/pulsar-admin", "topics",
+                    "offload", "--size-threshold", 
String.valueOf(Long.MAX_VALUE),
+                    topic);
+            Assert.assertTrue(output.contains("Nothing to offload"));
+
+            output = DockerUtils.runCommand(docker, broker,
+                    "/pulsar/bin/pulsar-admin", "topics", "offload-status", 
topic);
+            Assert.assertTrue(output.contains("Offload has not been run"));
+
+            // offload with a low threshold
+            output = DockerUtils.runCommand(docker, broker,
+                    "/pulsar/bin/pulsar-admin", "topics",
+                    "offload", "--size-threshold", "0",
+                    topic);
+            Assert.assertTrue(output.contains("Offload triggered"));
+
+            output = DockerUtils.runCommand(docker, broker,
+                    "/pulsar/bin/pulsar-admin", "topics", "offload-status", 
"-w", topic);
+            Assert.assertTrue(output.contains("Offload was a success"));
         }
 
         log.info("Kill ledger");

-- 
To stop receiving notification emails like this one, please contact
[email protected].

Reply via email to