/**
* Copyright 2016 Yahoo Inc.
*
* Licensed 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 com.yahoo.pulsar.admin.cli;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.converters.CommaParameterSplitter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.yahoo.pulsar.client.admin.PersistentTopics;
import com.yahoo.pulsar.client.admin.PulsarAdmin;
import com.yahoo.pulsar.client.admin.PulsarAdminException;
import com.yahoo.pulsar.client.api.Message;
import com.yahoo.pulsar.client.impl.MessageIdImpl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
@Parameters(commandDescription = "Operations on persistent topics")
public class CmdPersistentTopics extends CmdBase {
private final PersistentTopics persistentTopics;
public CmdPersistentTopics(PulsarAdmin admin) {
super("persistent", admin);
persistentTopics = admin.persistentTopics();
jcommander.addCommand("list", new ListCmd());
jcommander.addCommand("list-partitioned-topics", new PartitionedTopicListCmd());
jcommander.addCommand("permissions", new Permissions());
jcommander.addCommand("grant-permission", new GrantPermissions());
jcommander.addCommand("revoke-permission", new RevokePermissions());
jcommander.addCommand("lookup", new Lookup());
jcommander.addCommand("delete", new DeleteCmd());
jcommander.addCommand("subscriptions", new ListSubscriptions());
jcommander.addCommand("unsubscribe", new DeleteSubscription());
jcommander.addCommand("stats", new GetStats());
jcommander.addCommand("stats-internal", new GetInternalStats());
jcommander.addCommand("info-internal", new GetInternalInfo());
jcommander.addCommand("partitioned-stats", new GetPartitionedStats());
jcommander.addCommand("skip", new Skip());
jcommander.addCommand("skip-all", new SkipAll());
jcommander.addCommand("expire-messages", new ExpireMessages());
jcommander.addCommand("expire-messages-all-subscriptions", new ExpireMessagesForAllSubscriptions());
jcommander.addCommand("create-partitioned-topic", new CreatePartitionedCmd());
jcommander.addCommand("get-partitioned-topic-metadata", new GetPartitionedTopicMetadataCmd());
jcommander.addCommand("delete-partitioned-topic", new DeletePartitionedCmd());
jcommander.addCommand("peek-messages", new PeekMessages());
jcommander.addCommand("reset-cursor", new ResetCursor());
}
@Parameters(commandDescription = "Get the list of destinations under a namespace.")
private class ListCmd extends CliCommand {
@Parameter(description = "property/cluster/namespace\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String namespace = validateNamespace(params);
print(persistentTopics.getList(namespace));
}
}
@Parameters(commandDescription = "Get the list of partitioned topics under a namespace.")
private class PartitionedTopicListCmd extends CliCommand {
@Parameter(description = "property/cluster/namespace\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String namespace = validateNamespace(params);
print(persistentTopics.getPartitionedTopicList(namespace));
}
}
@Parameters(commandDescription = "Grant a new permission to a client role on a single destination.")
private class GrantPermissions extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = "--role", description = "Client role to which grant permissions", required = true)
private String role;
@Parameter(names = "--actions", description = "Actions to be granted (produce,consume)", required = true, splitter = CommaParameterSplitter.class)
private List<String> actions;
@Override
void run() throws PulsarAdminException {
String destination = validateDestination(params);
persistentTopics.grantPermission(destination, role, getAuthActions(actions));
}
}
@Parameters(commandDescription = "Revoke permissions on a destination \n "
+ "\t\t\t Revoke permissions to a client role on a single destination. If the permission \n"
+ "\t\t\t was not set at the destination level, but rather at the namespace level, this \n"
+ "\t\t\t operation will return an error (HTTP status code 412).")
private class RevokePermissions extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = "--role", description = "Client role to which revoke permissions", required = true)
private String role;
@Override
void run() throws PulsarAdminException {
String destination = validateDestination(params);
persistentTopics.revokePermissions(destination, role);
}
}
@Parameters(commandDescription = "Get the permissions on a destination\n"
+ "\t\t Retrieve the effective permissions for a destination. These permissions are defined \n"
+ "\t\t by the permissions set at the namespace level combined (union) with any eventual \n"
+ "\t\t specific permission set on the destination.")
private class Permissions extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String destination = validateDestination(params);
print(persistentTopics.getPermissions(destination));
}
}
@Parameters(commandDescription = "Lookup a destination from the current serving broker")
private class Lookup extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/topic\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String destination = validateDestination(params);
print(admin.lookups().lookupDestination(destination));
}
}
@Parameters(commandDescription = "Create a partitioned topic. \n"
+ "\t\tThe partitioned topic has to be created before creating a producer on it.")
private class CreatePartitionedCmd extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Parameter(names = { "-p",
"--partitions" }, description = "Number of partitions for the topic", required = true)
private int numPartitions;
@Override
void run() throws Exception {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.createPartitionedTopic(persistentTopic, numPartitions);
}
}
@Parameters(commandDescription = "Get the partitioned topic metadata. \n"
+ "\t\tIf the topic is not created or is a non-partitioned topic, it returns empty topic with 0 partitions")
private class GetPartitionedTopicMetadataCmd extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws Exception {
String persistentTopic = validatePersistentTopic(params);
print(persistentTopics.getPartitionedTopicMetadata(persistentTopic));
}
}
@Parameters(commandDescription = "Delete a partitioned topic. \n"
+ "\t\tIt will also delete all the partitions of the topic if it exists.")
private class DeletePartitionedCmd extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws Exception {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.deletePartitionedTopic(persistentTopic);
}
}
@Parameters(commandDescription = "Delete a topic. \n"
+ "\t\tThe topic cannot be deleted if there's any active subscription or producers connected to it.")
private class DeleteCmd extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.delete(persistentTopic);
}
}
@Parameters(commandDescription = "Get the list of subscriptions on the topic")
private class ListSubscriptions extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws Exception {
String persistentTopic = validatePersistentTopic(params);
print(persistentTopics.getSubscriptions(persistentTopic));
}
}
@Parameters(commandDescription = "Delete a durable subscriber from a topic. \n"
+ "\t\tThe subscription cannot be deleted if there are any active consumers attached to it \n")
private class DeleteSubscription extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = { "-s", "--subscription" }, description = "Subscription to be deleted", required = true)
private String subName;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.deleteSubscription(persistentTopic, subName);
}
}
@Parameters(commandDescription = "Get the stats for the topic and its connected producers and consumers. \n"
+ "\t All the rates are computed over a 1 minute window and are relative the last completed 1 minute period.")
private class GetStats extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
print(persistentTopics.getStats(persistentTopic));
}
}
@Parameters(commandDescription = "Get the internal stats for the topic")
private class GetInternalStats extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
print(persistentTopics.getInternalStats(persistentTopic));
}
}
@Parameters(commandDescription = "Get the internal metadata info for the topic")
private class GetInternalInfo extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
JsonObject result = persistentTopics.getInternalInfo(persistentTopic);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(result));
}
}
@Parameters(commandDescription = "Get the stats for the partitioned topic and its connected producers and consumers. \n"
+ "\t All the rates are computed over a 1 minute window and are relative the last completed 1 minute period.")
private class GetPartitionedStats extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination\n", required = true)
private java.util.List<String> params;
@Parameter(names = "--per-partition", description = "Get per partition stats")
private boolean perPartition = false;
@Override
void run() throws Exception {
String persistentTopic = validatePersistentTopic(params);
print(persistentTopics.getPartitionedStats(persistentTopic, perPartition));
}
}
@Parameters(commandDescription = "Skip all the messages for the subscription")
private class SkipAll extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = { "-s", "--subscription" }, description = "Subscription to be cleared", required = true)
private String subName;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.skipAllMessages(persistentTopic, subName);
}
}
@Parameters(commandDescription = "Skip some messages for the subscription")
private class Skip extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = { "-s",
"--subscription" }, description = "Subscription to be skip messages on", required = true)
private String subName;
@Parameter(names = { "-n", "--count" }, description = "Number of messages to skip", required = true)
private long numMessages;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.skipMessages(persistentTopic, subName, numMessages);
}
}
@Parameters(commandDescription = "Expire messages that older than given expiry time (in seconds) for the subscription")
private class ExpireMessages extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = { "-s",
"--subscription" }, description = "Subscription to be skip messages on", required = true)
private String subName;
@Parameter(names = { "-t", "--expireTime" }, description = "Expire messages older than time in seconds", required = true)
private long expireTimeInSeconds;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.expireMessages(persistentTopic, subName, expireTimeInSeconds);
}
}
@Parameters(commandDescription = "Expire messages that older than given expiry time (in seconds) for all subscriptions")
private class ExpireMessagesForAllSubscriptions extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = { "-t", "--expireTime" }, description = "Expire messages older than time in seconds", required = true)
private long expireTimeInSeconds;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
persistentTopics.expireMessagesForAllSubscriptions(persistentTopic, expireTimeInSeconds);
}
}
@Parameters(commandDescription = "Reset position for subscription to position closest to timestamp")
private class ResetCursor extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = { "-s",
"--subscription" }, description = "Subscription to reset position on", required = true)
private String subName;
@Parameter(names = { "--time",
"-t" }, description = "time in minutes to reset back to (or minutes, hours,days,weeks eg: 100m, 3h, 2d, 5w)", required = true)
private String resetTimeStr;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
int resetBackTimeInMin = validateTimeString(resetTimeStr);
long resetTimeInMillis = TimeUnit.MILLISECONDS.convert(resetBackTimeInMin, TimeUnit.MINUTES);
// now - go back time
long timestamp = System.currentTimeMillis() - resetTimeInMillis;
persistentTopics.resetCursor(persistentTopic, subName, timestamp);
}
}
@Parameters(commandDescription = "Peek some messages for the subscription")
private class PeekMessages extends CliCommand {
@Parameter(description = "persistent://property/cluster/namespace/destination", required = true)
private java.util.List<String> params;
@Parameter(names = { "-s",
"--subscription" }, description = "Subscription to get messages from", required = true)
private String subName;
@Parameter(names = { "-n", "--count" }, description = "Number of messages (default 1)", required = false)
private int numMessages = 1;
@Override
void run() throws PulsarAdminException {
String persistentTopic = validatePersistentTopic(params);
List<Message> messages = persistentTopics.peekMessages(persistentTopic, subName, numMessages);
int position = 0;
for (Message msg : messages) {
if (++position != 1) {
System.out.println("-------------------------------------------------------------------------\n");
}
MessageIdImpl msgId = (MessageIdImpl) msg.getMessageId();
System.out.println("Message ID: " + msgId.getLedgerId() + ":" + msgId.getEntryId());
if (msg.getProperties().size() > 0) {
System.out.println("Properties:");
print(msg.getProperties());
}
ByteBuf data = Unpooled.wrappedBuffer(msg.getData());
System.out.println(ByteBufUtil.prettyHexDump(data));
}
}
}
private static int validateTimeString(String s) {
char last = s.charAt(s.length() - 1);
String subStr = s.substring(0, s.length() - 1);
switch (last) {
case 'm':
case 'M':
return Integer.parseInt(subStr);
case 'h':
case 'H':
return Integer.parseInt(subStr) * 60;
case 'd':
case 'D':
return Integer.parseInt(subStr) * 24 * 60;
case 'w':
case 'W':
return Integer.parseInt(subStr) * 7 * 24 * 60;
default:
return Integer.parseInt(s);
}
}
}