Refactor the way of binding ssh commands

If a command should only be used on a server in master mode, but not
slave mode, it should be bound in both MasterCommandModule and
SlaveCommandModule, we have to remember to add it separately on each
of the commands list, the maintenance work is tedious.

Refactor the way of binding ssh commands. Add a field 'runsWith' to
CommandMetaData to mark a given ssh command to run on MASTER mode or
MASTER_OR_SLAVE mode. Remove SlaveCommandModule and MasterCommandModule,
and use only DefaultCommandModule.

This reverts commit 84bcead690240e07e0314b165fb8d6dc95d2c5e7.

Change-Id: Ia4908267b135be04ff9c8a9470a243a6c8d665a8
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index aba492d..ae8e308 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -71,8 +71,7 @@
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
-import com.google.gerrit.sshd.commands.MasterCommandModule;
-import com.google.gerrit.sshd.commands.SlaveCommandModule;
+import com.google.gerrit.sshd.commands.DefaultCommandModule;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
@@ -378,11 +377,8 @@
     if (!test) {
       modules.add(new SshHostKeyModule());
     }
-    if (slave) {
-      modules.add(new SlaveCommandModule());
-    } else {
-      modules.add(new MasterCommandModule());
-    }
+    modules.add(new DefaultCommandModule(slave));
+
     return sysInjector.createChildInjector(modules);
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
index e0fe7de..7fb9226 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandMetaData.java
@@ -21,11 +21,19 @@
 import java.lang.annotation.Target;
 
 /**
- * Annotation tagged on a concrete Command to describe what it is doing
+ * Annotation tagged on a concrete Command to describe what it is doing and
+ * whether it can be run on slaves.
  */
-@Target( {ElementType.TYPE})
+@Target({ElementType.TYPE})
 @Retention(RUNTIME)
 public @interface CommandMetaData {
+  public enum Mode {
+    MASTER, MASTER_OR_SLAVE;
+    public boolean isSupported(boolean slaveMode) {
+      return this == MASTER_OR_SLAVE || !slaveMode;
+    }
+  }
   String name();
   String description() default "";
+  Mode runsAt() default Mode.MASTER;
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
index bfa4051..1e409d2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandModule.java
@@ -21,6 +21,8 @@
 
 /** Module to register commands in the SSH daemon. */
 public abstract class CommandModule extends LifecycleModule {
+  protected boolean slaveMode;
+
   /**
    * Configure a command to be invoked by name.
    *
@@ -74,7 +76,9 @@
     if (meta == null) {
       throw new IllegalStateException("no CommandMetaData annotation found");
     }
-    bind(Commands.key(parent, meta.name(), meta.description())).to(clazz);
+    if (meta.runsAt().isSupported(slaveMode)) {
+      bind(Commands.key(parent, meta.name(), meta.description())).to(clazz);
+    }
   }
 
   /**
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index d548d34..fa5ab53 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityUtils;
 import com.google.gerrit.server.args4j.SubcommandHandler;
-import com.google.gerrit.sshd.commands.ErrorSlaveMode;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
@@ -155,13 +154,9 @@
     String format = "%-" + maxLength + "s   %s";
     for (String name : Sets.newTreeSet(commands.keySet())) {
       final CommandProvider p = commands.get(name);
-      Command c = p.getProvider().get();
-      String description = c instanceof ErrorSlaveMode
-          ? "Command disabled: server is running in slave mode"
-          : Strings.nullToEmpty(p.getDescription());
-
       usage.append("   ");
-      usage.append(String.format(format, name, description));
+      usage.append(String.format(format, name,
+          Strings.nullToEmpty(p.getDescription())));
       usage.append("\n");
     }
     usage.append("\n");
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 2322a3b..a275989 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -31,7 +31,6 @@
 import com.google.gerrit.server.plugins.StartPluginListener;
 import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.util.RequestScopePropagator;
-import com.google.gerrit.sshd.commands.DefaultCommandModule;
 import com.google.gerrit.sshd.commands.QueryShell;
 import com.google.inject.Inject;
 import com.google.inject.internal.UniqueAnnotations;
@@ -84,8 +83,6 @@
     bind(GSSAuthenticator.class).to(GerritGSSAuthenticator.class);
     bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
 
-    install(new DefaultCommandModule());
-
     bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class);
     bind(SshPluginStarterCallback.class);
     bind(StartPluginListener.class)
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
index 813f132..6629157 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AproposCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.documentation.QueryDocumentationExecutor;
 import com.google.gerrit.server.documentation.QueryDocumentationExecutor.DocResult;
@@ -25,7 +27,8 @@
 
 import java.util.List;
 
-@CommandMetaData(name = "apropos", description = "Search in Gerrit documentation")
+@CommandMetaData(name = "apropos", description = "Search in Gerrit documentation",
+  runsAt = MASTER_OR_SLAVE)
 final class AproposCommand extends SshCommand {
   @Inject
   private QueryDocumentationExecutor searcher;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index dc22a29..1d4b900 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.server.git.BanCommit;
 import com.google.gerrit.server.git.BanCommitResult;
@@ -33,7 +35,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@CommandMetaData(name = "ban-commit", description = "Ban a commit from a project's repository")
+@CommandMetaData(name = "ban-commit", description = "Ban a commit from a project's repository",
+  runsAt = MASTER_OR_SLAVE)
 public class BanCommitCommand extends SshCommand {
   @Option(name = "--reason", aliases = {"-r"}, metaVar = "REASON", usage = "reason for banning the commit")
   private String reason;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 75743b0..f905c5b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -21,18 +21,18 @@
 import com.google.gerrit.sshd.SuExec;
 
 
-/** Register the basic commands any Gerrit server should support. */
+/** Register the commands a Gerrit server supports. */
 public class DefaultCommandModule extends CommandModule {
+  public DefaultCommandModule(boolean slave) {
+    slaveMode = slave;
+  }
+
   @Override
   protected void configure() {
     final CommandName git = Commands.named("git");
     final CommandName gerrit = Commands.named("gerrit");
     final CommandName plugin = Commands.named(gerrit, "plugin");
-
-    // The following commands can be ran on a server in either Master or Slave
-    // mode. If a command should only be used on a server in one mode, but not
-    // both, it should be bound in both MasterCommandModule and
-    // SlaveCommandModule.
+    final CommandName testSubmit = Commands.named(gerrit, "test-submit");
 
     command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
     command(gerrit, AproposCommand.class);
@@ -49,7 +49,6 @@
     command(gerrit, StreamEvents.class);
     command(gerrit, VersionCommand.class);
     command(gerrit, GarbageCollectionCommand.class);
-
     command(gerrit, "plugin").toProvider(new DispatchCommandProvider(plugin));
 
     command(plugin, PluginLsCommand.class);
@@ -61,21 +60,43 @@
     alias(plugin, "rm", PluginRemoveCommand.class);
 
     command(git).toProvider(new DispatchCommandProvider(git));
-    command(git, "receive-pack").to(Commands.key(gerrit, "receive-pack"));
-    command(git, "upload-pack").to(Upload.class);
 
     command("ps").to(ShowQueue.class);
     command("kill").to(KillCommand.class);
     command("scp").to(ScpCommand.class);
 
     // Honor the legacy hyphenated forms as aliases for the non-hyphenated forms
-    //
     command("git-upload-pack").to(Commands.key(git, "upload-pack"));
-    command("git-receive-pack").to(Commands.key(git, "receive-pack"));
-    command("gerrit-receive-pack").to(Commands.key(git, "receive-pack"));
-
+    command(git, "upload-pack").to(Upload.class);
     command("suexec").to(SuExec.class);
-
     listener().to(ShowCaches.StartupListener.class);
+
+    // The following commands can only be ran on a server in Master mode
+    command(gerrit, CreateAccountCommand.class);
+    command(gerrit, CreateGroupCommand.class);
+    command(gerrit, CreateProjectCommand.class);
+    command(gerrit, AdminQueryShell.class);
+    if (!slaveMode) {
+      command("git-receive-pack").to(Commands.key(git, "receive-pack"));
+      command("gerrit-receive-pack").to(Commands.key(git, "receive-pack"));
+      command(git, "receive-pack").to(Commands.key(gerrit, "receive-pack"));
+      command(gerrit, "test-submit").toProvider(
+          new DispatchCommandProvider(testSubmit));
+    }
+    command(gerrit, Receive.class);
+
+    command(gerrit, RenameGroupCommand.class);
+    command(gerrit, ReviewCommand.class);
+    command(gerrit, SetProjectCommand.class);
+    command(gerrit, SetReviewersCommand.class);
+
+    command(gerrit, SetMembersCommand.class);
+    command(gerrit, CreateBranchCommand.class);
+    command(gerrit, SetAccountCommand.class);
+    command(gerrit, AdminSetParent.class);
+
+    command(gerrit, CreateAccountCommand.class);
+    command(testSubmit, TestSubmitRuleCommand.class);
+    command(testSubmit, TestSubmitTypeCommand.class);
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ErrorSlaveMode.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ErrorSlaveMode.java
deleted file mode 100644
index 32c72038..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ErrorSlaveMode.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// 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.google.gerrit.sshd.commands;
-
-import com.google.gerrit.sshd.BaseCommand;
-
-import org.apache.sshd.server.Environment;
-
-import java.io.IOException;
-
-/**
- * A command which just throws an error because it shouldn't be ran on this
- * server. This is used when a user tries to run a command on a server in Slave
- * Mode, but the command only applies to the Master server.
- */
-public final class ErrorSlaveMode extends BaseCommand {
-  @Override
-  public void start(final Environment env) {
-    String msg =
-        "error: That command is disabled on this server.\n\n"
-            + "Please use the master server URL.\n";
-    try {
-      err.write(msg.getBytes(ENC));
-      err.flush();
-    } catch (IOException e) {
-      // Ignore errors writing to the client
-    }
-    onExit(1);
-  }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index 6c07ddd..40152b0 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.cache.Cache;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -31,7 +33,8 @@
 
 /** Causes the caches to purge all entries and reload. */
 @RequiresCapability(GlobalCapability.FLUSH_CACHES)
-@CommandMetaData(name = "flush-caches", description = "Flush some/all server caches from memory")
+@CommandMetaData(name = "flush-caches", description = "Flush some/all server caches from memory",
+  runsAt = MASTER_OR_SLAVE)
 final class FlushCaches extends CacheCommand {
   private static final String WEB_SESSIONS = "web_sessions";
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index 81af0d8..821701d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GarbageCollectionResult;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -37,7 +39,8 @@
 
 /** Runs the Git garbage collection. */
 @RequiresCapability(GlobalCapability.RUN_GC)
-@CommandMetaData(name = "gc", description = "Run Git garbage collection")
+@CommandMetaData(name = "gc", description = "Run Git garbage collection",
+  runsAt = MASTER_OR_SLAVE)
 public class GarbageCollectionCommand extends BaseCommand {
 
   @Option(name = "--all", usage = "runs the Git garbage collection for all projects")
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index aadb1d9..f0169a8 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.Url;
@@ -37,7 +39,8 @@
 
 import java.io.PrintWriter;
 
-@CommandMetaData(name = "ls-groups", description = "List groups visible to the caller")
+@CommandMetaData(name = "ls-groups", description = "List groups visible to the caller",
+  runsAt = MASTER_OR_SLAVE)
 public class ListGroupsCommand extends BaseCommand {
   @Inject
   private MyListGroups impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
index b7dd380..2f367de 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -39,7 +41,8 @@
 /**
  * Implements a command that allows the user to see the members of a group.
  */
-@CommandMetaData(name = "ls-members", description = "Lists the members of a given group")
+@CommandMetaData(name = "ls-members", description = "Lists the members of a given group",
+  runsAt = MASTER_OR_SLAVE)
 public class ListMembersCommand extends BaseCommand {
   @Inject
   ListMembersCommandImpl impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
index 8bcae4b..78034fc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjectsCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.server.project.ListProjects;
 import com.google.gerrit.sshd.BaseCommand;
 import com.google.gerrit.sshd.CommandMetaData;
@@ -23,7 +25,8 @@
 
 import java.util.List;
 
-@CommandMetaData(name = "ls-projects", description = "List projects visible to the caller")
+@CommandMetaData(name = "ls-projects", description = "List projects visible to the caller",
+  runsAt = MASTER_OR_SLAVE)
 final class ListProjectsCommand extends BaseCommand {
   @Inject
   private ListProjects impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
index 207e5c2..c41fcdc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 
 import com.google.gerrit.common.data.GlobalCapability;
@@ -42,7 +43,8 @@
 import java.util.Map;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "ls-user-refs", description = "List refs visible to a specific user")
+@CommandMetaData(name = "ls-user-refs", description = "List refs visible to a specific user",
+  runsAt = MASTER_OR_SLAVE)
 public class LsUserRefs extends SshCommand {
   @Inject
   private AccountResolver accountResolver;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
deleted file mode 100644
index a79e862..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// 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.google.gerrit.sshd.commands;
-
-import com.google.gerrit.sshd.CommandModule;
-import com.google.gerrit.sshd.CommandName;
-import com.google.gerrit.sshd.Commands;
-import com.google.gerrit.sshd.DispatchCommandProvider;
-
-
-/** Register the commands a Gerrit server in master mode supports. */
-public class MasterCommandModule extends CommandModule {
-  @Override
-  protected void configure() {
-    final CommandName gerrit = Commands.named("gerrit");
-    final CommandName testSubmit = Commands.named(gerrit, "test-submit");
-
-    command(gerrit, CreateAccountCommand.class);
-    command(gerrit, CreateGroupCommand.class);
-    command(gerrit, RenameGroupCommand.class);
-    command(gerrit, CreateProjectCommand.class);
-    command(gerrit, CreateBranchCommand.class);
-    command(gerrit, AdminQueryShell.class);
-    command(gerrit, SetReviewersCommand.class);
-    command(gerrit, Receive.class);
-    command(gerrit, AdminSetParent.class);
-    command(gerrit, ReviewCommand.class);
-    command(gerrit, SetAccountCommand.class);
-    command(gerrit, SetMembersCommand.class);
-    command(gerrit, SetProjectCommand.class);
-
-    command(gerrit, "test-submit").toProvider(new DispatchCommandProvider(testSubmit));
-    command(testSubmit, TestSubmitRuleCommand.class);
-    command(testSubmit, TestSubmitTypeCommand.class);
-  }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
index 47c2d68..b2afde1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -28,7 +30,8 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "enable", description = "Enable plugins")
+@CommandMetaData(name = "enable", description = "Enable plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginEnableCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin(s) to enable")
   List<String> names;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 1e8035d..71f1517 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -35,7 +37,8 @@
 import java.net.URL;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "install", description = "Install/Add a plugin")
+@CommandMetaData(name = "install", description = "Install/Add a plugin",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginInstallCommand extends SshCommand {
   @Option(name = "--name", aliases = {"-n"}, usage = "install under name")
   private String name;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index 7e44641..9f6bb50 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.plugins.ListPlugins;
@@ -26,7 +28,8 @@
 import java.io.IOException;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "ls", description = "List the installed plugins")
+@CommandMetaData(name = "ls", description = "List the installed plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginLsCommand extends BaseCommand {
   @Inject
   private ListPlugins impl;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
index 3ed1011..8449160 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
@@ -14,10 +14,12 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.plugins.InvalidPluginException;
 import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.plugins.PluginLoader;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -28,7 +30,8 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "reload", description = "Reload/Restart plugins")
+@CommandMetaData(name = "reload", description = "Reload/Restart plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginReloadCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", usage = "plugins to reload/restart")
   private List<String> names;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
index 0ae11af..757348f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -27,7 +29,8 @@
 import java.util.List;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-@CommandMetaData(name = "remove", description = "Disable plugins")
+@CommandMetaData(name = "remove", description = "Disable plugins",
+  runsAt = MASTER_OR_SLAVE)
 final class PluginRemoveCommand extends SshCommand {
   @Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin to remove")
   List<String> names;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index af42e1b..2bda15d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.server.query.change.QueryProcessor;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -24,7 +26,8 @@
 
 import java.util.List;
 
-@CommandMetaData(name = "query", description = "Query the change database")
+@CommandMetaData(name = "query", description = "Query the change database",
+  runsAt = MASTER_OR_SLAVE)
 class Query extends SshCommand {
   @Inject
   private QueryProcessor processor;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index ff1de80..397120f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheStats;
 import com.google.common.collect.Maps;
@@ -54,7 +56,8 @@
 
 /** Show the current cache states. */
 @RequiresCapability(GlobalCapability.VIEW_CACHES)
-@CommandMetaData(name = "show-caches", description = "Display current cache statistics")
+@CommandMetaData(name = "show-caches", description = "Display current cache statistics",
+  runsAt = MASTER_OR_SLAVE)
 final class ShowCaches extends CacheCommand {
   private static volatile long serverStarted;
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index d97d750..17bea45 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.CurrentUser;
@@ -46,7 +48,8 @@
 
 /** Show the current SSH connections. */
 @RequiresCapability(GlobalCapability.VIEW_CONNECTIONS)
-@CommandMetaData(name = "show-connections", description = "Display active client SSH connections")
+@CommandMetaData(name = "show-connections", description = "Display active client SSH connections",
+  runsAt = MASTER_OR_SLAVE)
 final class ShowConnections extends SshCommand {
   @Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
   private boolean numeric;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index afb5787..40f7059 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.TaskInfoFactory;
@@ -42,7 +44,8 @@
 
 /** Display the current work queue. */
 @AdminHighPriorityCommand
-@CommandMetaData(name = "show-queue", description = "Display the background work queues")
+@CommandMetaData(name = "show-queue", description = "Display the background work queues",
+  runsAt = MASTER_OR_SLAVE)
 final class ShowQueue extends SshCommand {
   @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
   private boolean wide;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java
deleted file mode 100644
index ac0eb0d..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// 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.google.gerrit.sshd.commands;
-
-import com.google.gerrit.sshd.CommandModule;
-import com.google.gerrit.sshd.CommandName;
-import com.google.gerrit.sshd.Commands;
-
-
-/** Register the commands a Gerrit server in slave mode supports. */
-public class SlaveCommandModule extends CommandModule {
-  @Override
-  protected void configure() {
-    final CommandName gerrit = Commands.named("gerrit");
-
-    command(gerrit, "create-account").to(ErrorSlaveMode.class);
-    command(gerrit, "create-group").to(ErrorSlaveMode.class);
-    command(gerrit, "create-project").to(ErrorSlaveMode.class);
-    command(gerrit, "gsql").to(ErrorSlaveMode.class);
-    command(gerrit, "receive-pack").to(ErrorSlaveMode.class);
-    command(gerrit, "rename-group").to(ErrorSlaveMode.class);
-    command(gerrit, "review").to(ErrorSlaveMode.class);
-    command(gerrit, "set-project-parent").to(ErrorSlaveMode.class);
-    command(gerrit, "set-reviewers").to(ErrorSlaveMode.class);
-  }
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
index fd4a9ec..c55c7ed2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.ChangeListener;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -36,7 +38,8 @@
 import java.util.concurrent.LinkedBlockingQueue;
 
 @RequiresCapability(GlobalCapability.STREAM_EVENTS)
-@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time")
+@CommandMetaData(name = "stream-events", description = "Monitor events occurring in real time",
+  runsAt = MASTER_OR_SLAVE)
 final class StreamEvents extends BaseCommand {
   /** Maximum number of events that may be queued up for each connection. */
   private static final int MAX_EVENTS = 128;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
index 19888c8..50f880d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/VersionCommand.java
@@ -14,11 +14,14 @@
 
 package com.google.gerrit.sshd.commands;
 
+import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+
 import com.google.gerrit.common.Version;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 
-@CommandMetaData(name = "version", description = "Display gerrit version")
+@CommandMetaData(name = "version", description = "Display gerrit version",
+  runsAt = MASTER_OR_SLAVE)
 final class VersionCommand extends SshCommand {
 
   @Override
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 32fac33..45cc4a2a 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -57,7 +57,7 @@
 import com.google.gerrit.sshd.SshHostKeyModule;
 import com.google.gerrit.sshd.SshKeyCacheImpl;
 import com.google.gerrit.sshd.SshModule;
-import com.google.gerrit.sshd.commands.MasterCommandModule;
+import com.google.gerrit.sshd.commands.DefaultCommandModule;
 import com.google.inject.AbstractModule;
 import com.google.inject.CreationException;
 import com.google.inject.Guice;
@@ -304,7 +304,7 @@
     final List<Module> modules = new ArrayList<Module>();
     modules.add(sysInjector.getInstance(SshModule.class));
     modules.add(new SshHostKeyModule());
-    modules.add(new MasterCommandModule());
+    modules.add(new DefaultCommandModule(false));
     return sysInjector.createChildInjector(modules);
   }