All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.admin.client.TopologyCommand Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

There is a newer version: 18.3.10
Show newest version
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.admin.client;

import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import oracle.kv.impl.admin.CommandJsonUtils;
import oracle.kv.impl.admin.CommandResult;
import oracle.kv.impl.admin.CommandResult.CommandSucceeds;
import oracle.kv.impl.admin.CommandResult.CommandWarns;
import oracle.kv.impl.admin.CommandServiceAPI;
import oracle.kv.impl.admin.param.Parameters;
import oracle.kv.impl.admin.topo.TopologyCandidate;
import oracle.kv.impl.topo.DatacenterId;
import oracle.kv.impl.topo.DatacenterType;
import oracle.kv.impl.topo.RepGroupId;
import oracle.kv.impl.topo.RepNodeId;
import oracle.kv.impl.topo.StorageNodeId;
import oracle.kv.impl.util.CommandParser;
import oracle.kv.impl.util.JsonUtils;
import oracle.kv.impl.util.SerialVersion;
import oracle.kv.impl.util.TopologyPrinter;
import oracle.kv.util.shell.CommandWithSubs;
import oracle.kv.util.shell.Shell;
import oracle.kv.util.shell.ShellArgumentException;
import oracle.kv.util.shell.ShellCommandResult;
import oracle.kv.util.shell.ShellException;

import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;

/*
 * Subcommands of topology
 */
class TopologyCommand extends CommandWithSubs {

    private static final List subs = Arrays.asList(
        new TopologyChangeRFSub(),              /* change-repfactor */
        new TopologyChangeZoneArbitersSub(),    /* change-zone-arbiters */
        new TopologyChangeZoneAffinitySub(),    /* change-zone-affinity */
        new TopologyChangeZoneTypeSub(),        /* change-zone-type */
        new TopologyCloneSub(),                 /* clone */
        new TopologyContractSub(),              /* contract */
        new TopologyCreateSub(),                /* create */
        new TopologyDeleteSub(),                /* delete */
        new TopologyListSub(),                  /* list */
        new TopologyMoveRNSub(),                /* move-repnode */
        new TopologyPreviewSub(),               /* preview */
        new TopologyRebalanceSub(),             /* rebalance */
        new TopologyRedistributeSub(),          /* redistribute */
        new TopologyRemoveShardSub(),           /* remove-shard */
        new TopologyValidateSub(),              /* validate */
        new TopologyViewSub()                   /* view */
        );

    private static final String TOPOLOGY_COMMAND_NAME = "topology";

    /**
     * A warning message to be displayed when commands attempt to create
     * topology candidates whose names contain the substring reserved for
     * system use.
     */
    public static final String RESERVED_CANDIDATE_NAME_WARNING =
        "Warning: The topology candidate name contains '" +
        TopologyCandidate.RESERVED_SUBSTRING +
        "', which is reserved for" + eol + " system use." + eol + eol;

    TopologyCommand() {
        super(subs,
              TOPOLOGY_COMMAND_NAME,
              4,  /* prefix length */
              0); /* min args -- let subs control it */
    }

    @Override
    protected String getCommandOverview() {
        return "Encapsulates commands that manipulate store topologies." + eol +
            "Examples are " +
            "redistribution/rebalancing of nodes or changing replication" +
            eol + "factor.  Topologies are created and modified using this " +
            "command.  They" + eol + "are then deployed by using the " +
            "\"plan deploy-topology\" command.";
    }

    private static ObjectNode readObjectValue(String input)
        throws ShellException {
        return CommandJsonUtils.handleConversionFailure(
            (CommandJsonUtils.JsonConversionTask)() -> {
                return CommandJsonUtils.
                           readObjectValue(input);
        });
    }

    @POST
    static class TopologyChangeRFSub extends SubCommand {

        final static String dcFlagsDeprecation =
            "The -dc and -dcname flags, and the dc ID format, are" +
            " deprecated" + eol +
            "and have been replaced by -zn, -znname, and zn." +
            eol + eol;

        TopologyChangeRFSub() {
            super("change-repfactor", 8);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyChangeRFExecutor() {

                @Override
                public String successMessage(String message,
                                             String deprecatedDcFlagPrefix) {
                    return deprecatedDcFlagPrefix + message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyChangeRFExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyChangeRFSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                String poolName = null;
                DatacenterId dcid = null;
                String dcName = null;
                int rf = 0;
                boolean deprecatedDcFlag = false;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++, TopologyChangeRFSub.this);
                    } else if ("-pool".equals(arg)) {
                        poolName =
                            Shell.nextArg(args, i++, TopologyChangeRFSub.this);
                    } else if (CommandUtils.isDatacenterIdFlag(arg)) {
                        dcid =
                            parseDatacenterId(
                                Shell.nextArg(
                                    args, i++, TopologyChangeRFSub.this));
                        if (CommandUtils.
                                isDeprecatedDatacenterId(arg, args[i])) {
                            deprecatedDcFlag = true;
                        }
                    } else if (CommandUtils.isDatacenterNameFlag(arg)) {
                        dcName =
                            Shell.nextArg(args, i++, TopologyChangeRFSub.this);
                        if (CommandUtils.isDeprecatedDatacenterName(arg)) {
                            deprecatedDcFlag = true;
                        }
                    } else if ("-rf".equals(arg)) {
                        String rfString =
                            Shell.nextArg(args, i++, TopologyChangeRFSub.this);
                        rf = parseUnsignedInt(rfString);

                        /* this is more for typos than actual validation */
                        if (rf > 30) {
                            throw new ShellArgumentException(
                                "Replication factor out of valid range: " +
                                rf);
                        }
                    } else {
                        shell.unknownArgument(arg, TopologyChangeRFSub.this);
                    }
                }
                if (topoName == null || poolName == null || rf == 0 ||
                    (dcid == null && dcName == null)) {
                    shell.requiredArg(null, TopologyChangeRFSub.this);
                }
                final String deprecatedDcFlagPrefix =
                    !deprecatedDcFlag ? "" : dcFlagsDeprecation;
                try {
                    if (dcid == null) {
                        dcid =
                            CommandUtils.getDatacenterId(
                                dcName, cs, TopologyChangeRFSub.this);
                    } else {
                        CommandUtils.ensureDatacenterExists(
                            dcid, cs, TopologyChangeRFSub.this);
                    }
                    CommandUtils.validatePool(
                        poolName, cs, TopologyChangeRFSub.this);
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyChangeRFSub.this);
                    CommandUtils.validateRepFactor(
                        dcid, rf, cs, TopologyChangeRFSub.this);
                    return successMessage(cs.changeRepFactor(
                        topoName, poolName, dcid, rf), deprecatedDcFlagPrefix);
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T
                successMessage(String message, String deprecatedDcFlagPrefix);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                 ShellCommandResult.getDefault("topology change rf");
            return new TopologyChangeRFExecutor() {

                @Override
                public ShellCommandResult
                    successMessage(String message,
                                   String deprecatedDcFlagPrefix) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology change-repfactor -name  -pool " +
                "" + eolt + "-zn  | -znname  -rf " +
                " " +
                CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Modifies the topology to change the replication factor of " +
                "the specified" + eolt + "zone to a new value.  The " +
                "replication factor may not be" + eolt + "decreased at " +
                "this time.";
        }
    }

    @POST
    static class TopologyChangeZoneTypeSub extends SubCommand {

        TopologyChangeZoneTypeSub() {
            super("change-zone-type", 13);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyChangeZoneTypeExecutor() {
                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyChangeZoneTypeExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyChangeZoneTypeSub.this);
                final CommandShell cmd = (CommandShell)shell;
                final CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                DatacenterId dcid = null;
                DatacenterType type = null;
                String dcName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneTypeSub.this);
                    } else if (CommandUtils.isDatacenterIdFlag(arg)) {
                        dcid = parseDatacenterId(
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneTypeSub.this));
                    } else if (CommandUtils.isDatacenterNameFlag(arg)) {
                        dcName =
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneTypeSub.this);
                    } else if ("-type".equals(arg)) {
                        final String typeValue =
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneTypeSub.this);
                        type = parseDatacenterType(typeValue);
                    } else {
                        shell.unknownArgument(arg,
                                              TopologyChangeZoneTypeSub.this);
                    }
                }
                if (topoName == null || type == null ||
                    (dcid == null && dcName == null)) {
                    shell.requiredArg(null, TopologyChangeZoneTypeSub.this);
                }

                try {
                    if (dcid == null) {
                        dcid =
                            CommandUtils.getDatacenterId(
                                dcName, cs, TopologyChangeZoneTypeSub.this);
                    } else {
                        CommandUtils.ensureDatacenterExists(
                            dcid, cs, TopologyChangeZoneTypeSub.this);
                    }
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyChangeZoneTypeSub.this);
                    return successMessage(
                        cs.changeZoneType(topoName, dcid, type));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology change zone type");
            return new TopologyChangeZoneTypeExecutor() {
                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology change-zone-type -name  " + eolt +
                   "{-zn  | -znname } " +
                   "-type {primary | secondary} " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Modifies the topology to change the type of " +
                "the specified" + eolt + "zone to a new type.";
        }
    }

    @SuppressWarnings("null")
    @POST
    static class TopologyChangeZoneAffinitySub extends SubCommand {

        TopologyChangeZoneAffinitySub() {
            super("change-zone-master-affinity", 16);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyChangeZoneAffinityExecutor() {

                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyChangeZoneAffinityExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyChangeZoneAffinitySub.this);
                final CommandShell cmd = (CommandShell)shell;
                final CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                DatacenterId dcid = null;
                Boolean masterAffinity = null;
                String dcName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneAffinitySub.this);
                    } else if (CommandUtils.isDatacenterIdFlag(arg)) {
                        dcid =
                            parseDatacenterId(Shell.nextArg(
                                args, i++,
                                TopologyChangeZoneAffinitySub.this));
                    } else if (CommandUtils.isDatacenterNameFlag(arg)) {
                        dcName =
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneAffinitySub.this);
                    } else if ("-master-affinity".equals(arg)) {
                        masterAffinity = true;
                    } else if ("-no-master-affinity".equals(arg)) {
                        masterAffinity = false;
                    } else {
                        shell.unknownArgument(
                            arg, TopologyChangeZoneAffinitySub.this);
                    }
                }
                if (topoName == null || masterAffinity == null ||
                    (dcid == null && dcName == null)) {
                    shell.requiredArg(
                        null, TopologyChangeZoneAffinitySub.this);
                }

                try {
                    if (dcid == null) {
                        dcid =
                            CommandUtils.getDatacenterId(
                                dcName, cs,
                                TopologyChangeZoneAffinitySub.this);
                    } else {
                        CommandUtils.ensureDatacenterExists(
                            dcid, cs, TopologyChangeZoneAffinitySub.this);
                    }
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyChangeZoneAffinitySub.this);
                    return successMessage(
                        cs.changeZoneMasterAffinity(topoName, dcid,
                                                    masterAffinity));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("change zone master affinity");
            return
                new TopologyChangeZoneAffinityExecutor() {

                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology change-zone-master-affinity -name  " + eolt +
                "{-zn  | -znname } " + eolt +
                "{-master-affinity | -no-master-affinity} " +
                CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Modifies the topology to change the master affinity of " +
                "the specified" + eolt + "zone.";
        }
    }

    @SuppressWarnings("null")
    @POST
    static class TopologyChangeZoneArbitersSub extends SubCommand {

        TopologyChangeZoneArbitersSub() {
            super("change-zone-arbiters", 13);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyChangeZoneArbitersExecutor() {
                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyChangeZoneArbitersExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyChangeZoneArbitersSub.this);
                final CommandShell cmd = (CommandShell)shell;
                final CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                DatacenterId dcid = null;
                Boolean allowArbiters = null;
                String dcName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneArbitersSub.this);
                    } else if (CommandUtils.isDatacenterIdFlag(arg)) {
                        dcid =
                            parseDatacenterId(
                                Shell.nextArg(
                                    args, i++,
                                    TopologyChangeZoneArbitersSub.this));
                    } else if (CommandUtils.isDatacenterNameFlag(arg)) {
                        dcName =
                            Shell.nextArg(args, i++,
                                          TopologyChangeZoneArbitersSub.this);
                    } else if ("-arbiters".equals(arg)) {
                        allowArbiters = true;
                    } else if ("-no-arbiters".equals(arg)) {
                       allowArbiters = false;
                    } else {
                        shell.unknownArgument(
                            arg, TopologyChangeZoneArbitersSub.this);
                    }
                }
                if (topoName == null || allowArbiters == null ||
                    (dcid == null && dcName == null)) {
                    shell.requiredArg(
                        null, TopologyChangeZoneArbitersSub.this);
                }

                try {
                    if (dcid == null) {
                        dcid =
                            CommandUtils.getDatacenterId(
                                dcName, cs,
                                TopologyChangeZoneArbitersSub.this);
                    } else {
                        CommandUtils.
                            ensureDatacenterExists(
                                dcid, cs,
                                TopologyChangeZoneArbitersSub.this);
                    }
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyChangeZoneArbitersSub.this);
                    return successMessage(
                        cs.changeZoneArbiters(topoName, dcid, allowArbiters));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology change zone arbiters");
            return
                new TopologyChangeZoneArbitersExecutor() {
                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology change-zone-arbiters -name  " + eolt +
                   "{-zn  | -znname } " +
                   "{-arbiters | -no-arbiters} " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Modifies the topology to change the arbiter attribute of " +
                "the specified" + eolt + "zone.";
        }
    }

    @POST
    static class TopologyCloneSub extends SubCommand {

        TopologyCloneSub() {
            super("clone", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyCloneExecutor() {

                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyCloneExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyCloneSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                String fromName = null;
                boolean isCurrent = false;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++, TopologyCloneSub.this);
                    } else if ("-from".equals(arg)) {
                        fromName =
                            Shell.nextArg(args, i++, TopologyCloneSub.this);
                    } else if ("-current".equals(arg)) {
                        isCurrent = true;
                    } else {
                        shell.unknownArgument(arg, TopologyCloneSub.this);
                    }
                }
                if (topoName == null || (fromName == null && !isCurrent)) {
                    shell.requiredArg(null, TopologyCloneSub.this);
                    throw new AssertionError("Not reached");
                }

                final String reservedWarning =
                    topoName.contains(TopologyCandidate.RESERVED_SUBSTRING) ?
                    RESERVED_CANDIDATE_NAME_WARNING : "";
                try {
                    if (isCurrent) {
                        return successMessage(
                            reservedWarning +
                            cs.copyCurrentTopology(topoName));
                    }
                    CommandUtils.ensureTopoExists(
                        fromName, cs, TopologyCloneSub.this);
                    return successMessage(
                        reservedWarning +
                        cs.copyTopology(fromName, topoName));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                    throw new AssertionError("Not reached");
                }
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology clone");
            return new TopologyCloneExecutor() {

                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology clone -from  -name " +
                " or "+
                eolt + "topology clone -current -name  " +
                CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Clones an existing topology so as to create a new " +
                "candidate topology " + eolt +
                "to be used for topology change operations.";
        }
    }

    @POST
    static class TopologyCreateSub extends SubCommand {

        TopologyCreateSub() {
            super("create", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyCreateExecutor() {

                @Override
                public String topologyCandidateResult(CommandServiceAPI cs,
                                                      String poolName,
                                                      String topoName,
                                                      int numPartitions,
                                                      Shell commandShell)
                    throws ShellException {
                    CommandShell cmd = (CommandShell)commandShell;
                    String returnValue = "";
                    try {
                        CommandUtils.validatePool(poolName, cs,
                                                  TopologyCreateSub.this);
                        returnValue = cs.createTopology(topoName, poolName,
                                                        numPartitions,
                                                        shell.getJson());
                    } catch (RemoteException re) {
                        cmd.noAdmin(re);
                        throw new AssertionError("Not reached");
                    }
                    final boolean isReservedName =
                        topoName.contains(
                            TopologyCandidate.RESERVED_SUBSTRING);
                    if (shell.getJson()) {
                        String operation = TOPOLOGY_COMMAND_NAME + " " +
                            getCommandName();
                        CommandResult result;
                        if (isReservedName) {
                            result =
                                new CommandWarns(
                                    RESERVED_CANDIDATE_NAME_WARNING,
                                    returnValue);
                        } else {
                            result = new CommandSucceeds(returnValue);
                        }
                        return Shell.toJsonReport(operation, result);
                    }
                    if (isReservedName) {
                        return RESERVED_CANDIDATE_NAME_WARNING + returnValue;
                    }
                    return returnValue;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyCreateExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyCreateSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                String poolName = null;
                int numPartitions = 0;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++, TopologyCreateSub.this);
                    } else if ("-pool".equals(arg)) {
                        poolName =
                            Shell.nextArg(args, i++, TopologyCreateSub.this);
                    } else if ("-partitions".equals(arg)) {
                        String partString =
                            Shell.nextArg(args, i++, TopologyCreateSub.this);
                        numPartitions = parseUnsignedInt(partString);
                    } else {
                        shell.unknownArgument(arg, TopologyCreateSub.this);
                    }
                }
                if (topoName == null || poolName == null ||
                    numPartitions == 0) {
                    shell.requiredArg(null, TopologyCreateSub.this);
                    throw new AssertionError("Not reached");
                }

                return topologyCandidateResult(
                    cs, poolName, topoName, numPartitions, shell);
            }

            public abstract T
                topologyCandidateResult(CommandServiceAPI cs,
                                        String poolName,
                                        String topoName,
                                        int numPartitions, Shell shell)
                throws ShellException;
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology create");
            return new TopologyCreateExecutor() {
                @Override
                public ShellCommandResult
                    topologyCandidateResult(CommandServiceAPI cs,
                                            String poolName,
                                            String topoName,
                                            int numPartitions,
                                            Shell commandShell)
                    throws ShellException {
                    CommandShell cmd = (CommandShell)commandShell;
                    try {
                        CommandUtils.validatePool(poolName, cs,
                                                  TopologyCreateSub.this);
                        final String serverJson =
                            cs.createTopology(
                                topoName, poolName,
                                numPartitions, true,
                                SerialVersion.ADMIN_CLI_JSON_V2_VERSION);

                        final ObjectNode result = readObjectValue(serverJson);
                        final boolean isReservedName =
                            topoName.contains(
                                TopologyCandidate.RESERVED_SUBSTRING);
                        if (isReservedName) {
                            scr.setDescription(
                                RESERVED_CANDIDATE_NAME_WARNING);
                        }
                        scr.setReturnValue(result);
                        return scr;
                    } catch (RemoteException re) {
                        cmd.noAdmin(re);
                        throw new AssertionError("Not reached");
                    }
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology create -name  -pool " +
                   "" + eolt + "-partitions  " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Creates a new topology with the specified number of " +
                "partitions" + eolt + "using the specified storage pool.";
        }
    }

    @POST
    static class TopologyDeleteSub extends SubCommand {

        TopologyDeleteSub() {
            super("delete", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyDeleteExecutor() {

                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyDeleteExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyDeleteSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++, TopologyDeleteSub.this);
                    } else {
                        shell.unknownArgument(arg, TopologyDeleteSub.this);
                    }
                }
                if (topoName == null) {
                    shell.requiredArg("-name", TopologyDeleteSub.this);
                }

                try {
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyDeleteSub.this);
                    return successMessage(cs.deleteTopology(topoName));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology delete");
            return new TopologyDeleteExecutor() {

                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology delete -name  " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return "Deletes a topology.";
        }
    }

    @POST
    static class TopologyListSub extends SubCommand {

        TopologyListSub() {
            super("list", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyListExecutor() {

                @Override
                public String topologyList(List topos,
                                           boolean showHidden) {
                    StringBuilder sb = new StringBuilder();
                    for (String oneTopo : topos) {
                        if (!showHidden &&
                            oneTopo.startsWith(
                                TopologyCandidate.INTERNAL_NAME_PREFIX)) {
                            continue;
                        }
                        sb.append(oneTopo).append(eol);
                    }
                    return sb.toString();
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyListExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyListSub.this);
                if (args.length > 1) {
                    shell.unknownArgument(args[1], TopologyListSub.this);
                }
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                final boolean showHidden = cmd.getHidden();
                try {
                    List topos = cs.listTopologies();
                    Collections.sort(topos);
                    return topologyList(topos, showHidden);
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T topologyList(List topos,
                                           boolean showHidden);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology list");
            return new TopologyListExecutor() {

                @Override
                public ShellCommandResult
                    topologyList(List topos, boolean showHidden) {
                    final ObjectNode top = JsonUtils.createObjectNode();
                    final ArrayNode topoArray = top.putArray("topologies");
                    for (String oneTopo : topos) {
                        if (!showHidden &&
                            oneTopo.startsWith(
                                TopologyCandidate.INTERNAL_NAME_PREFIX)) {
                            continue;
                        }
                        topoArray.add(oneTopo);
                    }
                    scr.setReturnValue(top);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology list " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return "Lists existing topologies.";
        }
    }

    @POST
    static class TopologyMoveRNSub extends SubCommand {

        TopologyMoveRNSub() {
            super("move-repnode", 4);
        }

        @Override
        protected boolean isHidden() {
            return true;
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyMoveRNExecutor() {

                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyMoveRNExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyMoveRNSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                RepNodeId rnid = null;
                StorageNodeId snid = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(
                                args, i++, TopologyMoveRNSub.this);
                    } else if ("-rn".equals(arg)) {
                        String rnString =
                            Shell.nextArg(args, i++, TopologyMoveRNSub.this);
                        try {
                            rnid = RepNodeId.parse(rnString);
                        } catch (IllegalArgumentException iae) {
                            throw new ShellArgumentException(
                                "Invalid RepNode id: " + rnString);
                        }
                    } else if ("-sn".equals(arg)) {
                        String snString =
                            Shell.nextArg(args, i++, TopologyMoveRNSub.this);
                        try {
                            snid = StorageNodeId.parse(snString);
                        } catch (IllegalArgumentException iae) {
                            throw new ShellArgumentException(
                                "Invalid StorageNode id: " + snString);
                        }
                    } else {
                        shell.unknownArgument(arg, TopologyMoveRNSub.this);
                    }
                }
                if (topoName == null || rnid == null) {
                    shell.requiredArg(null, TopologyMoveRNSub.this);
                }

                try {
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyMoveRNSub.this);
                    CommandUtils.ensureRepNodeExists(
                        rnid, cs, TopologyMoveRNSub.this);
                    if (snid != null) {
                        CommandUtils.ensureStorageNodeExists(
                            snid, cs, TopologyMoveRNSub.this);
                    }
                    return successMessage(cs.moveRN(topoName, rnid, snid));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            ShellCommandResult scr =
                ShellCommandResult.getDefault("topology move repnode");
            return new TopologyMoveRNExecutor() {

                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology move-repnode -name  -rn  " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Modifies the topology to move the specified RepNode to " +
                "an available" + eolt + "storage node chosen by the system.";
        }
    }

    @POST
    static class TopologyPreviewSub extends SubCommand {

        TopologyPreviewSub() {
            super("preview", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyPreviewExecutor() {

                @Override
                public String topologyPreviewResult(CommandServiceAPI cs,
                                                    String topoName,
                                                    String startName,
                                                    boolean b)
                    throws RemoteException {
                    return cs.preview(topoName, startName, b);
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyPreviewExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyPreviewSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                String startName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++, TopologyPreviewSub.this);
                    } else if ("-start".equals(arg)) {
                        startName =
                            Shell.nextArg(args, i++, TopologyPreviewSub.this);
                    } else {
                        shell.unknownArgument(arg, TopologyPreviewSub.this);
                    }
                }
                if (topoName == null) {
                    shell.requiredArg("-name", TopologyPreviewSub.this);
                }

                try {
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyPreviewSub.this);
                    if (startName != null) {
                        CommandUtils.ensureTopoExists(
                            startName, cs, TopologyPreviewSub.this);
                    }
                    return topologyPreviewResult(cs,
                        topoName, startName, shell.getVerbose());
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T
                topologyPreviewResult(
                    CommandServiceAPI cs, String topoName,
                    String startName, boolean b)
                throws RemoteException, ShellException;
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology preview");
            return new TopologyPreviewExecutor() {

                @Override
                public ShellCommandResult
                    topologyPreviewResult(CommandServiceAPI cs,
                                          String topoName,
                                          String startName,
                                          boolean b)
                    throws RemoteException, ShellException {
                    final String serverResult =
                        cs.preview(topoName, startName, b,
                                   SerialVersion.ADMIN_CLI_JSON_V2_VERSION);
                    final ObjectNode result = readObjectValue(serverResult);
                    scr.setReturnValue(result);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology preview -name  [-start ] " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Describes the actions that would be taken to transition " +
                "from the " + eolt + "starting topology to the named, target " +
                "topology. If -start is not " + eolt + "specified "  +
                "the current topology is used. This command should be used " +
                eolt +  "before deploying a new topology.";
        }
    }

    @POST
    static class TopologyRebalanceSub extends SubCommand {

        static final String dcFlagsDeprecation =
            "The -dc and -dcname flags, and the dc ID format, are" +
            " deprecated" + eol +
            "and have been replaced by -zn, -znname, and zn." +
            eol + eol;

        TopologyRebalanceSub() {
            super("rebalance", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyRebalanceExecutor() {

                @Override
                public String successMessage(String message,
                                             String deprecatedDcFlagPrefix) {
                    return deprecatedDcFlagPrefix + message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyRebalanceExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyRebalanceSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                String poolName = null;
                DatacenterId dcid = null;
                String dcName = null;
                boolean deprecatedDcFlag = false;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args,
                                          i++, TopologyRebalanceSub.this);
                    } else if ("-pool".equals(arg)) {
                        poolName =
                            Shell.nextArg(args,
                                          i++, TopologyRebalanceSub.this);
                    } else if (CommandUtils.isDatacenterIdFlag(arg)) {
                        dcid =
                            parseDatacenterId(
                                Shell.nextArg(args, i++,
                                              TopologyRebalanceSub.this));
                        if (CommandUtils.
                                isDeprecatedDatacenterId(arg, args[i])) {
                            deprecatedDcFlag = true;
                        }
                    } else if (CommandUtils.isDatacenterNameFlag(arg)) {
                        dcName =
                            Shell.nextArg(args, i++,
                                          TopologyRebalanceSub.this);
                        if (CommandUtils.isDeprecatedDatacenterName(arg)) {
                            deprecatedDcFlag = true;
                        }
                    } else {
                        shell.unknownArgument(arg, TopologyRebalanceSub.this);
                    }
                }
                if (topoName == null || poolName == null) {
                    shell.requiredArg(null, TopologyRebalanceSub.this);
                }
                final String deprecatedDcFlagPrefix =
                    !deprecatedDcFlag ? "" : dcFlagsDeprecation;
                try {
                    CommandUtils.validatePool(
                        poolName, cs, TopologyRebalanceSub.this);
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyRebalanceSub.this);
                    if (dcName != null) {
                        dcid = CommandUtils.getDatacenterId(
                            dcName, cs, TopologyRebalanceSub.this);
                    }
                    if (dcid != null) {
                        CommandUtils.ensureDatacenterExists(
                            dcid, cs, TopologyRebalanceSub.this);
                    }
                    return successMessage(
                        cs.rebalanceTopology(topoName, poolName, dcid),
                        deprecatedDcFlagPrefix);
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T
                successMessage(String message, String deprecatedDcFlagPrefix);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology rebalance");
            return new TopologyRebalanceExecutor() {

                @Override
                public ShellCommandResult
                    successMessage(String message,
                                   String deprecatedDcFlagPrefix) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology rebalance -name  -pool " +
                   " [-zn  | -znname ] " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Modifies the named topology to create a \"balanced\" " +
                "topology. If the" + eolt + "optional -zn flag is used " +
                "only storage nodes from the specified" + eolt +
                "zone will be used for the operation.";
        }
    }

    @POST
    static class TopologyContractSub extends SubCommand {

        TopologyContractSub() {
            super("contract", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyContractExecutor() {

                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyContractExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyContractSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                String poolName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++, TopologyContractSub.this);
                    } else if ("-pool".equals(arg)) {
                        poolName =
                            Shell.nextArg(args, i++, TopologyContractSub.this);
                    } else {
                        shell.unknownArgument(arg, TopologyContractSub.this);
                    }
                }
                if (topoName == null || poolName == null) {
                    shell.requiredArg(null, TopologyContractSub.this);
                }

                String contractInfo = "";
                try {
                    CommandUtils.validatePool(
                        poolName, cs, TopologyContractSub.this);
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyContractSub.this);
                    contractInfo = cs.contractTopology(topoName, poolName);
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }

                /* Check whether there are snapshots for the topology. */
                try {
                    String [] list = cs.listSnapshots(null);

                    if (list.length == 0) {
                        return successMessage(contractInfo);
                    }

                    String snapshotsList = "";
                    for (String ss : list) {
                        snapshotsList += ss + eol;
                    }
                    String warnMessage = null;
                    if (list.length == 1) {
                        warnMessage =
                            "Warning: the following snapshot will " +
                            "be removed, please backup it:";
                    } else {
                        warnMessage =
                            "Warning: the following snapshots will " +
                            "be removed, please backup them:";
                    }
                    return
                        successMessage(contractInfo + eol + eol +
                                       warnMessage + eol + snapshotsList);

                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                } catch (IllegalArgumentException iae) {
                    throw new ShellException(iae.getMessage());
                }
                return null;
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology contract");
            return new TopologyContractExecutor() {

                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology contract -name  -pool " +
                   " " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return "Modifies the named topology to contract storage nodes.";
        }
    }

    @POST
    static class TopologyRedistributeSub extends SubCommand {

        TopologyRedistributeSub() {
            super("redistribute", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyRedistributeExecutor() {

                @Override
                public String successMessage(String message) {
                    return message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyRedistributeExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyRedistributeSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                String poolName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++,
                                          TopologyRedistributeSub.this);
                    } else if ("-pool".equals(arg)) {
                        poolName =
                            Shell.nextArg(args, i++,
                                          TopologyRedistributeSub.this);
                    } else {
                        shell.unknownArgument(arg,
                                              TopologyRedistributeSub.this);
                    }
                }
                if (topoName == null || poolName == null) {
                    shell.requiredArg(null, TopologyRedistributeSub.this);
                }

                try {
                    CommandUtils.validatePool(
                        poolName, cs, TopologyRedistributeSub.this);
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyRedistributeSub.this);
                    return successMessage(
                        cs.redistributeTopology(topoName, poolName));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T successMessage(String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            ShellCommandResult scr =
                ShellCommandResult.getDefault("topology redistribute");
            return new TopologyRedistributeExecutor() {
                @Override
                public ShellCommandResult successMessage(String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology redistribute -name  -pool " +
                   " " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Modifies the named topology to redistribute resources " +
                "to more efficiently" + eolt + "use those available.";
        }
    }

    @POST
    static class TopologyValidateSub extends SubCommand {

        TopologyValidateSub() {
            super("validate", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyValidateExecutor() {

                @Override
                public String validateResult(CommandServiceAPI cs,
                                             String topoName)
                    throws RemoteException {
                    return cs.validateTopology(topoName);
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyValidateExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyValidateSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName =
                            Shell.nextArg(args, i++,
                                          TopologyValidateSub.this);
                    } else {
                        shell.unknownArgument(arg,
                                              TopologyValidateSub.this);
                    }
                }

                try {
                    if (topoName != null) {
                        CommandUtils.ensureTopoExists(
                            topoName, cs, TopologyValidateSub.this);
                    }
                    return validateResult(cs, topoName);
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T validateResult(CommandServiceAPI cs,
                                             String topoName)
                throws RemoteException, ShellException;
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology validate");
            return new TopologyValidateExecutor() {

                @Override
                public ShellCommandResult
                    validateResult(CommandServiceAPI cs,
                                   String topoName)
                    throws RemoteException, ShellException {
                    final String serverResult =
                        cs.validateTopology(
                            topoName,
                            SerialVersion.ADMIN_CLI_JSON_V2_VERSION);
                    final ObjectNode result = readObjectValue(serverResult);
                    scr.setReturnValue(result);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology validate [-name ] " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Validates the specified topology. If no name is given, " +
                "the current " + eolt +
                "topology is validated. Validation will generate " +
                "\"violations\" and " + eolt + "\"notes\". Violations are " +
                "issues that can cause problems and should be " + eolt +
                "investigated. Notes are informational and highlight " +
                "configuration " + eolt +
                "oddities that could be potential issues or could be expected.";

        }
    }

    @POST
    static class TopologyViewSub extends SubCommand {

        TopologyViewSub() {
            super("view", 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            return new TopologyViewExecutor() {

                @Override
                public String topologyResult(TopologyCandidate tc,
                                             Parameters params,
                                             boolean verbose) {
                    return TopologyPrinter.printTopology(tc, params, verbose);
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyViewExecutor
            implements Executor {

            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyViewSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                String topoName = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-name".equals(arg)) {
                        topoName = Shell.nextArg(
                            args, i++, TopologyViewSub.this);
                    } else {
                        shell.unknownArgument(arg, TopologyViewSub.this);
                    }
                }
                if (topoName == null) {
                    shell.requiredArg("-name", TopologyViewSub.this);
                }

                try {
                    CommandUtils.ensureTopoExists(
                        topoName, cs, TopologyViewSub.this);
                    TopologyCandidate tc = cs.getTopologyCandidate (topoName);
                    Parameters params = cs.getParameters();
                    return topologyResult(tc, params, shell.getVerbose());
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T
                topologyResult(TopologyCandidate tc,
                               Parameters params,
                               boolean verbose);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology view");
            return new TopologyViewExecutor() {

                @Override
                public ShellCommandResult
                    topologyResult(TopologyCandidate tc,
                                   Parameters params,
                                   boolean verbose) {
                    scr.setReturnValue(
                        TopologyPrinter.printTopologyJson(
                            tc.getTopology(), params, TopologyPrinter.all,
                            verbose));
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology view -name  " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return
                "Displays details of the specified topology.";
        }
    }

    @POST
    static class TopologyRemoveShardSub extends SubCommand {

        TopologyRemoveShardSub() {
            super("remove-shard", 9);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {
            return new TopologyRemoveShardExecutor() {
                @Override
                public String successResult(String reservedWarning,
                                            String message) {
                    return reservedWarning + message;
                }
            }.commonExecute(args, shell);
        }

        private abstract class TopologyRemoveShardExecutor
            implements Executor {
            @Override
            public T commonExecute(String[] args, Shell shell)
                throws ShellException {
                Shell.checkHelp(args, TopologyRemoveShardSub.this);
                CommandShell cmd = (CommandShell)shell;
                CommandServiceAPI cs = cmd.getAdmin();
                RepGroupId failedShard = null;
                String toponame = null;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if ("-failed-shard".equals(arg)) {
                        failedShard =
                            RepGroupId.parse(
                                Shell.nextArg(args, i++,
                                              TopologyRemoveShardSub.this));
                    } else if ("-name".equals(arg)) {
                        toponame =
                            Shell.nextArg(args, i++,
                                          TopologyRemoveShardSub.this);
                    }
                    else {
                        shell.unknownArgument(arg,
                                              TopologyRemoveShardSub.this);
                    }
                }
                if (toponame == null || failedShard == null) {
                    shell.requiredArg(null,
                                      TopologyRemoveShardSub.this);
                    throw new AssertionError("Not reached");
                }

                try {
                    /*
                     * Need to ensure the failed shardId exists in topology.
                     *
                     * Also raise warning message if user gives reserved topology
                     * candidate name for new topology.
                     */
                    final String reservedWarning =
                        toponame.contains(TopologyCandidate.RESERVED_SUBSTRING) ?
                        RESERVED_CANDIDATE_NAME_WARNING : "";
                    CommandUtils.ensureShardExists(
                        failedShard, cs, TopologyRemoveShardSub.this);
                    return successResult(reservedWarning,
                                         cs.removeFailedShard(
                                             failedShard, toponame));
                } catch (RemoteException re) {
                    cmd.noAdmin(re);
                }
                return null;
            }

            public abstract T
                successResult(String reservedWarning, String message);
        }

        @Override
        public ShellCommandResult
            executeJsonOutput(String[] args, Shell shell)
            throws ShellException {
            final ShellCommandResult scr =
                ShellCommandResult.getDefault("topology remove-shard");
            return new TopologyRemoveShardExecutor() {
                @Override
                public ShellCommandResult
                    successResult(String reservedWarning,
                                  String message) {
                    scr.setDescription(message);
                    return scr;
                }
            }.commonExecute(args, shell);
        }

        @Override
        protected String getCommandSyntax() {
            return "topology remove-shard -failed-shard  -name " +
                   " " +
                   CommandParser.getJsonUsage();
        }

        @Override
        protected String getCommandDescription() {
            return "Removes a failed shard from a topology";
        }

        /**
         * Currently we have kept this as hidden command. Returning true.
         * If we decide to make this visible command then will return false.
         */
        @Override
        protected boolean isHidden() {
            return true;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy