Merge branch 'stable-3.0' into stable-3.1

* stable-3.0:
  Bump Bazel version to 3.5.1
  Upgrade jackson-core to 2.11.3
  Register graceful shutdown for version command
  Register graceful shutdown for show queue command
  Register graceful shutdown for show connections command
  Register graceful shutdown for show caches command
  Register graceful shutdown for set reviewers command
  Register graceful shutdown for set project command
  Register graceful shutdown for set parent command
  Register graceful shutdown for set members command
  Register graceful shutdown for set logging level command
  Register graceful shutdown for set head command
  Register graceful shutdown for set account command
  Register graceful shutdown for review command
  Register graceful shutdown for rename group command
  Register graceful shutdown for reload config command
  Register graceful shutdown for query command
  Register graceful shutdown for list plugins command
  Register graceful shutdown for plugin admin commands
  Register graceful shutdown for list user refs command
  Register graceful shutdown for list projects command
  Register graceful shutdown for list members command
  Register graceful shutdown for list logging level command
  Register graceful shutdown for list groups command
  Register graceful shutdown for kill command
  Register graceful shutdown for index start command
  Register graceful shutdown for index changes in project command
  Register graceful shutdown for index changes command
  Register graceful shutdown for index activate command
  Register graceful shutdown for gc command
  Register graceful shutdown for flush caches command
  Register graceful shutdown for create project command
  Register graceful shutdown for create group command
  Register graceful shutdown for create branch command
  Register graceful shutdown for create account command
  Register graceful shutdown for close connection command
  Register graceful shutdown for prolog test commands
  Register graceful shutdown for ban commit command
  Register graceful shutdown for apropos command
  Limit graceful shutdown to SSH sessions serving git requests
  Update git submodules

Change-Id: I519ba25b10209f8856ecf289915c65e5eb8ee09a
This commit is contained in:
Marco Miller
2020-10-06 08:28:15 -04:00
53 changed files with 381 additions and 17 deletions

View File

@@ -1 +1 @@
3.5.0 3.5.1

View File

@@ -377,6 +377,15 @@ public abstract class AbstractDaemonTest {
initSsh(); initSsh();
} }
protected void restart() throws Exception {
server = GerritServer.restart(server, createModule(), createSshModule());
server.getTestInjector().injectMembers(this);
if (resetter != null) {
server.getTestInjector().injectMembers(resetter);
}
initSsh();
}
protected void evictAndReindexAccount(Account.Id accountId) { protected void evictAndReindexAccount(Account.Id accountId) {
accountCache.evict(accountId); accountCache.evict(accountId);
accountIndexer.index(accountId); accountIndexer.index(accountId);
@@ -419,13 +428,16 @@ public abstract class AbstractDaemonTest {
baseConfig.setInt("receive", null, "changeUpdateThreads", 4); baseConfig.setInt("receive", null, "changeUpdateThreads", 4);
Module module = createModule(); Module module = createModule();
Module sshModule = createSshModule();
if (classDesc.equals(methodDesc) && !classDesc.sandboxed() && !methodDesc.sandboxed()) { if (classDesc.equals(methodDesc) && !classDesc.sandboxed() && !methodDesc.sandboxed()) {
if (commonServer == null) { if (commonServer == null) {
commonServer = GerritServer.initAndStart(temporaryFolder, classDesc, baseConfig, module); commonServer =
GerritServer.initAndStart(temporaryFolder, classDesc, baseConfig, module, sshModule);
} }
server = commonServer; server = commonServer;
} else { } else {
server = GerritServer.initAndStart(temporaryFolder, methodDesc, baseConfig, module); server =
GerritServer.initAndStart(temporaryFolder, methodDesc, baseConfig, module, sshModule);
} }
server.getTestInjector().injectMembers(this); server.getTestInjector().injectMembers(this);
@@ -518,6 +530,11 @@ public abstract class AbstractDaemonTest {
return null; return null;
} }
/** Override to bind an additional Guice module for SSH injector */
public Module createSshModule() {
return null;
}
protected void initSsh() throws Exception { protected void initSsh() throws Exception {
if (testRequiresSsh if (testRequiresSsh
&& SshMode.useSsh() && SshMode.useSsh()

View File

@@ -312,6 +312,7 @@ public class GerritServer implements AutoCloseable {
* @param desc server description. * @param desc server description.
* @param baseConfig default config values; merged with config from {@code desc}. * @param baseConfig default config values; merged with config from {@code desc}.
* @param testSysModule additional Guice module to use. * @param testSysModule additional Guice module to use.
* @param testSshModule additional Guice module to use.
* @return started server. * @return started server.
* @throws Exception * @throws Exception
*/ */
@@ -319,14 +320,15 @@ public class GerritServer implements AutoCloseable {
TemporaryFolder temporaryFolder, TemporaryFolder temporaryFolder,
Description desc, Description desc,
Config baseConfig, Config baseConfig,
@Nullable Module testSysModule) @Nullable Module testSysModule,
@Nullable Module testSshModule)
throws Exception { throws Exception {
Path site = temporaryFolder.newFolder().toPath(); Path site = temporaryFolder.newFolder().toPath();
try { try {
if (!desc.memory()) { if (!desc.memory()) {
init(desc, baseConfig, site); init(desc, baseConfig, site);
} }
return start(desc, baseConfig, site, testSysModule, null); return start(desc, baseConfig, site, testSysModule, testSshModule, null);
} catch (Exception e) { } catch (Exception e) {
throw e; throw e;
} }
@@ -342,6 +344,7 @@ public class GerritServer implements AutoCloseable {
* initialize this directory. Can be retrieved from the returned instance via {@link * initialize this directory. Can be retrieved from the returned instance via {@link
* #getSitePath()}. * #getSitePath()}.
* @param testSysModule optional additional module to add to the system injector. * @param testSysModule optional additional module to add to the system injector.
* @param testSshModule optional additional module to add to the ssh injector.
* @param inMemoryRepoManager {@link InMemoryRepositoryManager} that should be used if the site is * @param inMemoryRepoManager {@link InMemoryRepositoryManager} that should be used if the site is
* started in memory * started in memory
* @param additionalArgs additional command-line arguments for the daemon program; only allowed if * @param additionalArgs additional command-line arguments for the daemon program; only allowed if
@@ -354,6 +357,7 @@ public class GerritServer implements AutoCloseable {
Config baseConfig, Config baseConfig,
Path site, Path site,
@Nullable Module testSysModule, @Nullable Module testSysModule,
@Nullable Module testSshModule,
@Nullable InMemoryRepositoryManager inMemoryRepoManager, @Nullable InMemoryRepositoryManager inMemoryRepoManager,
String... additionalArgs) String... additionalArgs)
throws Exception { throws Exception {
@@ -376,6 +380,9 @@ public class GerritServer implements AutoCloseable {
if (testSysModule != null) { if (testSysModule != null) {
daemon.addAdditionalSysModuleForTesting(testSysModule); daemon.addAdditionalSysModuleForTesting(testSysModule);
} }
if (testSshModule != null) {
daemon.addAdditionalSshModuleForTesting(testSshModule);
}
daemon.setEnableSshd(desc.useSsh()); daemon.setEnableSshd(desc.useSsh());
if (desc.memory()) { if (desc.memory()) {
@@ -596,7 +603,24 @@ public class GerritServer implements AutoCloseable {
server.close(); server.close();
server.daemon.stop(); server.daemon.stop();
return start(server.desc, cfg, site, null, inMemoryRepoManager); return start(server.desc, cfg, site, null, null, inMemoryRepoManager);
}
public static GerritServer restart(
GerritServer server, @Nullable Module testSysModule, @Nullable Module testSshModule)
throws Exception {
checkState(server.desc.sandboxed(), "restarting as slave requires @Sandboxed");
Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
InMemoryRepositoryManager inMemoryRepoManager = null;
if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
}
server.close();
server.daemon.stop();
return start(server.desc, cfg, site, testSysModule, testSshModule, inMemoryRepoManager);
} }
private static boolean hasBinding(Injector injector, Class<?> clazz) { private static boolean hasBinding(Injector injector, Class<?> clazz) {

View File

@@ -66,6 +66,22 @@ public class SshSession {
} }
} }
@SuppressWarnings("resource")
public int execAndReturnStatus(String command) throws Exception {
ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
try {
channel.setCommand(command);
InputStream err = channel.getErrStream();
channel.connect();
Scanner s = new Scanner(err, UTF_8.name()).useDelimiter("\\A");
error = s.hasNext() ? s.next() : null;
return channel.getExitStatus();
} finally {
channel.disconnect();
}
}
public InputStream exec2(String command, InputStream opt) throws Exception { public InputStream exec2(String command, InputStream opt) throws Exception {
ChannelExec channel = (ChannelExec) getSession().openChannel("exec"); ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
channel.setCommand(command); channel.setCommand(command);

View File

@@ -187,7 +187,7 @@ public abstract class StandaloneSiteTest {
private GerritServer startImpl(@Nullable Module testSysModule, String... additionalArgs) private GerritServer startImpl(@Nullable Module testSysModule, String... additionalArgs)
throws Exception { throws Exception {
return GerritServer.start( return GerritServer.start(
serverDesc, baseConfig, sitePaths.site_path, testSysModule, null, additionalArgs); serverDesc, baseConfig, sitePaths.site_path, testSysModule, null, null, additionalArgs);
} }
protected static void runGerrit(String... args) throws Exception { protected static void runGerrit(String... args) throws Exception {

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2020 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.acceptance.ssh;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.sshd.CommandMetaData;
@CommandMetaData(
name = "graceful",
description = "Test command for graceful shutdown",
runsAt = MASTER_OR_SLAVE)
public class GracefulCommand extends TestCommand {
@Override
boolean isGraceful() {
return true;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2020 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.acceptance.ssh;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.sshd.CommandMetaData;
@CommandMetaData(
name = "non-graceful",
description = "Test command for immediate shutdown",
runsAt = MASTER_OR_SLAVE)
public class NonGracefulCommand extends TestCommand {
@Override
boolean isGraceful() {
return false;
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2020 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.acceptance.ssh;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.sshd.SshCommand;
import java.util.concurrent.CyclicBarrier;
import org.kohsuke.args4j.Option;
public abstract class TestCommand extends SshCommand {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final CyclicBarrier syncPoint = new CyclicBarrier(2);
@Option(
name = "--duration",
aliases = {"-d"},
required = true,
usage = "Duration of the command execution in seconds")
private int duration;
@Override
protected void run() throws UnloggedFailure, Failure, Exception {
logger.atFine().log("Starting command.");
if (isGraceful()) {
enableGracefulStop();
}
try {
syncPoint.await();
Thread.sleep(duration * 1000);
logger.atFine().log("Stopping command.");
} catch (Exception e) {
throw die("Command ended prematurely.", e);
}
}
abstract boolean isGraceful();
}

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2020 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.acceptance.ssh;
import com.google.gerrit.sshd.CommandModule;
public class TestSshCommandModule extends CommandModule {
@Override
protected void configure() {
command("graceful").to(GracefulCommand.class);
command("non-graceful").to(NonGracefulCommand.class);
}
}

View File

@@ -192,6 +192,7 @@ public class Daemon extends SiteProgram {
private AbstractModule luceneModule; private AbstractModule luceneModule;
private Module emailModule; private Module emailModule;
private List<Module> testSysModules = new ArrayList<>(); private List<Module> testSysModules = new ArrayList<>();
private List<Module> testSshModules = new ArrayList<>();
private Module auditEventModule; private Module auditEventModule;
private Runnable serverStarted; private Runnable serverStarted;
@@ -322,6 +323,11 @@ public class Daemon extends SiteProgram {
testSysModules.addAll(Arrays.asList(modules)); testSysModules.addAll(Arrays.asList(modules));
} }
@VisibleForTesting
public void addAdditionalSshModuleForTesting(@Nullable Module... modules) {
testSshModules.addAll(Arrays.asList(modules));
}
@VisibleForTesting @VisibleForTesting
public void start() throws IOException { public void start() throws IOException {
if (dbInjector == null) { if (dbInjector == null) {
@@ -515,6 +521,8 @@ public class Daemon extends SiteProgram {
replica, replica,
sysInjector.getInstance(DownloadConfig.class), sysInjector.getInstance(DownloadConfig.class),
sysInjector.getInstance(LfsPluginAuthCommand.Module.class))); sysInjector.getInstance(LfsPluginAuthCommand.Module.class)));
modules.addAll(testSshModules);
if (!replica) { if (!replica) {
modules.add(new IndexCommandsModule(sysInjector)); modules.add(new IndexCommandsModule(sysInjector));
} }

View File

@@ -52,6 +52,7 @@ public abstract class AbstractGitCommand extends BaseCommand {
@Override @Override
public void start(ChannelSession channel, Environment env) { public void start(ChannelSession channel, Environment env) {
enableGracefulStop();
String gitProtocol = env.getEnv().get(GIT_PROTOCOL); String gitProtocol = env.getEnv().get(GIT_PROTOCOL);
if (gitProtocol != null) { if (gitProtocol != null) {
extraParameters = gitProtocol.split(":"); extraParameters = gitProtocol.split(":");

View File

@@ -403,6 +403,10 @@ public abstract class BaseCommand implements Command {
} }
} }
protected void enableGracefulStop() {
context.getSession().setGracefulStop(true);
}
protected String getTaskDescription() { protected String getTaskDescription() {
String[] ta = getTrimmedArguments(); String[] ta = getTrimmedArguments();
if (ta != null) { if (ta != null) {

View File

@@ -89,6 +89,7 @@ import org.apache.sshd.common.mac.Mac;
import org.apache.sshd.common.random.Random; import org.apache.sshd.common.random.Random;
import org.apache.sshd.common.random.SingletonRandomFactory; import org.apache.sshd.common.random.SingletonRandomFactory;
import org.apache.sshd.common.session.Session; import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.common.session.helpers.DefaultUnknownChannelReferenceHandler; import org.apache.sshd.common.session.helpers.DefaultUnknownChannelReferenceHandler;
import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer; import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -368,14 +369,24 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
Collection<IoSession> ioSessions = daemonAcceptor.getManagedSessions().values(); Collection<IoSession> ioSessions = daemonAcceptor.getManagedSessions().values();
CountDownLatch allSessionsClosed = new CountDownLatch(ioSessions.size()); CountDownLatch allSessionsClosed = new CountDownLatch(ioSessions.size());
for (IoSession io : ioSessions) { for (IoSession io : ioSessions) {
logger.atFine().log("Waiting for session %s to stop.", io.getId()); AbstractSession serverSession = AbstractSession.getSession(io, true);
io.addCloseFutureListener( SshSession sshSession =
new SshFutureListener<CloseFuture>() { serverSession != null ? serverSession.getAttribute(SshSession.KEY) : null;
@Override if (sshSession != null && sshSession.requiresGracefulStop()) {
public void operationComplete(CloseFuture future) { logger.atFine().log("Waiting for session %s to stop.", io.getId());
allSessionsClosed.countDown(); io.addCloseFutureListener(
} new SshFutureListener<CloseFuture>() {
}); @Override
public void operationComplete(CloseFuture future) {
logger.atFine().log("Session %s was stopped.", io.getId());
allSessionsClosed.countDown();
}
});
} else {
logger.atFine().log("Stopping session %s immediately.", io.getId());
io.close(true);
allSessionsClosed.countDown();
}
} }
try { try {
if (!allSessionsClosed.await(gracefulStopTimeout, TimeUnit.SECONDS)) { if (!allSessionsClosed.await(gracefulStopTimeout, TimeUnit.SECONDS)) {

View File

@@ -35,6 +35,8 @@ public class SshSession {
private volatile String authError; private volatile String authError;
private volatile String peerAgent; private volatile String peerAgent;
private volatile boolean gracefulStop = false;
SshSession(int sessionId, SocketAddress peer) { SshSession(int sessionId, SocketAddress peer) {
this.sessionId = sessionId; this.sessionId = sessionId;
this.remoteAddress = peer; this.remoteAddress = peer;
@@ -58,6 +60,14 @@ public class SshSession {
return sessionId; return sessionId;
} }
public boolean requiresGracefulStop() {
return gracefulStop;
}
public void setGracefulStop(boolean gracefulStop) {
this.gracefulStop = gracefulStop;
}
/** Identity of the authenticated user account on the socket. */ /** Identity of the authenticated user account on the socket. */
public CurrentUser getUser() { public CurrentUser getUser() {
return identity; return identity;

View File

@@ -39,6 +39,7 @@ final class AproposCommand extends SshCommand {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
enableGracefulStop();
try { try {
List<QueryDocumentationExecutor.DocResult> res = searcher.doQuery(q); List<QueryDocumentationExecutor.DocResult> res = searcher.doQuery(q);
for (DocResult docResult : res) { for (DocResult docResult : res) {

View File

@@ -63,6 +63,7 @@ public class BanCommitCommand extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
try { try {
BanCommitInput input = BanCommitInput input =
BanCommitInput.fromCommits(Lists.transform(commitsToBan, ObjectId::getName)); BanCommitInput.fromCommits(Lists.transform(commitsToBan, ObjectId::getName));

View File

@@ -59,6 +59,7 @@ abstract class BaseTestPrologCommand extends SshCommand {
@Override @Override
protected final void run() throws UnloggedFailure { protected final void run() throws UnloggedFailure {
enableGracefulStop();
try { try {
RevisionResource revision = RevisionResource revision =
revisions.parse( revisions.parse(

View File

@@ -57,6 +57,7 @@ final class CloseConnection extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
SshUtil.forEachSshSession( SshUtil.forEachSshSession(
sshDaemon, sshDaemon,
(k, sshSession, abstractSession, ioSession) -> { (k, sshSession, abstractSession, ioSession) -> {

View File

@@ -72,6 +72,7 @@ final class CreateAccountCommand extends SshCommand {
@Override @Override
protected void run() protected void run()
throws IOException, ConfigInvalidException, UnloggedFailure, PermissionBackendException { throws IOException, ConfigInvalidException, UnloggedFailure, PermissionBackendException {
enableGracefulStop();
AccountInput input = new AccountInput(); AccountInput input = new AccountInput();
input.username = username; input.username = username;
input.email = email; input.email = email;

View File

@@ -44,6 +44,7 @@ public final class CreateBranchCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
enableGracefulStop();
try { try {
BranchInput in = new BranchInput(); BranchInput in = new BranchInput();
in.revision = revision; in.revision = revision;

View File

@@ -102,6 +102,7 @@ final class CreateGroupCommand extends SshCommand {
@Override @Override
protected void run() protected void run()
throws Failure, IOException, ConfigInvalidException, PermissionBackendException { throws Failure, IOException, ConfigInvalidException, PermissionBackendException {
enableGracefulStop();
try { try {
GroupResource rsrc = createGroup(); GroupResource rsrc = createGroup();

View File

@@ -166,6 +166,7 @@ final class CreateProjectCommand extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
try { try {
if (!suggestParent) { if (!suggestParent) {
if (projectName == null) { if (projectName == null) {

View File

@@ -55,6 +55,7 @@ final class FlushCaches extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
try { try {
if (list) { if (list) {
if (all || !caches.isEmpty()) { if (all || !caches.isEmpty()) {

View File

@@ -62,6 +62,7 @@ public class GarbageCollectionCommand extends SshCommand {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
enableGracefulStop();
verifyCommandLine(); verifyCommandLine();
runGC(); runGC();
} }

View File

@@ -34,6 +34,7 @@ public class IndexActivateCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
enableGracefulStop();
try { try {
if (versionManager.isKnownIndex(name)) { if (versionManager.isKnownIndex(name)) {
if (versionManager.activateLatestIndex(name)) { if (versionManager.activateLatestIndex(name)) {

View File

@@ -53,6 +53,7 @@ final class IndexChangesCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
enableGracefulStop();
boolean ok = true; boolean ok = true;
for (ChangeResource rsrc : changes.values()) { for (ChangeResource rsrc : changes.values()) {
try { try {

View File

@@ -43,6 +43,7 @@ final class IndexChangesInProjectCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure, Failure, Exception { protected void run() throws UnloggedFailure, Failure, Exception {
enableGracefulStop();
if (projects.isEmpty()) { if (projects.isEmpty()) {
throw die("needs at least one project as command arguments"); throw die("needs at least one project as command arguments");
} }

View File

@@ -38,6 +38,7 @@ public class IndexStartCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
enableGracefulStop();
try { try {
if (versionManager.isKnownIndex(name)) { if (versionManager.isKnownIndex(name)) {
if (versionManager.startReindexer(name, force)) { if (versionManager.startReindexer(name, force)) {

View File

@@ -47,6 +47,7 @@ final class KillCommand extends SshCommand {
@Override @Override
protected void run() { protected void run() {
enableGracefulStop();
ConfigResource cfgRsrc = new ConfigResource(); ConfigResource cfgRsrc = new ConfigResource();
for (String id : taskIds) { for (String id : taskIds) {
try { try {

View File

@@ -52,6 +52,7 @@ public class ListGroupsCommand extends SshCommand {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
enableGracefulStop();
if (listGroups.getUser() != null && !listGroups.getProjects().isEmpty()) { if (listGroups.getUser() != null && !listGroups.getProjects().isEmpty()) {
throw die("--user and --project options are not compatible."); throw die("--user and --project options are not compatible.");
} }

View File

@@ -40,6 +40,7 @@ public class ListLoggingLevelCommand extends SshCommand {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
protected void run() { protected void run() {
enableGracefulStop();
Map<String, String> logs = new TreeMap<>(); Map<String, String> logs = new TreeMap<>();
for (Enumeration<Logger> logger = LogManager.getCurrentLoggers(); logger.hasMoreElements(); ) { for (Enumeration<Logger> logger = LogManager.getCurrentLoggers(); logger.hasMoreElements(); ) {
Logger log = logger.nextElement(); Logger log = logger.nextElement();

View File

@@ -45,6 +45,7 @@ public class ListMembersCommand extends SshCommand {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
enableGracefulStop();
impl.display(stdout); impl.display(stdout);
} }

View File

@@ -32,6 +32,7 @@ public class ListProjectsCommand extends SshCommand {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
enableGracefulStop();
if (!impl.getFormat().isJson()) { if (!impl.getFormat().isJson()) {
List<String> showBranch = impl.getShowBranch(); List<String> showBranch = impl.getShowBranch();
if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) { if (impl.isShowTree() && (showBranch != null) && !showBranch.isEmpty()) {

View File

@@ -74,6 +74,7 @@ public class LsUserRefs extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
Account.Id userAccountId; Account.Id userAccountId;
try { try {
userAccountId = accountResolver.resolve(userName).asUnique().account().id(); userAccountId = accountResolver.resolve(userName).asUnique().account().id();

View File

@@ -28,6 +28,7 @@ public abstract class PluginAdminSshCommand extends SshCommand {
@Override @Override
protected final void run() throws UnloggedFailure { protected final void run() throws UnloggedFailure {
enableGracefulStop();
if (!loader.isRemoteAdminEnabled()) { if (!loader.isRemoteAdminEnabled()) {
throw die("remote plugin administration is disabled"); throw die("remote plugin administration is disabled");
} }

View File

@@ -41,6 +41,7 @@ public class PluginLsCommand extends SshCommand {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
enableGracefulStop();
Map<String, PluginInfo> output = list.apply(TopLevelResource.INSTANCE).value(); Map<String, PluginInfo> output = list.apply(TopLevelResource.INSTANCE).value();
if (format.isJson()) { if (format.isJson()) {

View File

@@ -106,6 +106,7 @@ public class Query extends SshCommand implements DynamicOptions.BeanReceiver {
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
enableGracefulStop();
processor.query(join(query, " ")); processor.query(join(query, " "));
} }

View File

@@ -38,6 +38,7 @@ public class ReloadConfig extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
Multimap<UpdateResult, ConfigUpdateEntry> updates = gerritServerConfigReloader.reloadConfig(); Multimap<UpdateResult, ConfigUpdateEntry> updates = gerritServerConfigReloader.reloadConfig();
if (updates.isEmpty()) { if (updates.isEmpty()) {
stdout.println("No config entries updated!"); stdout.println("No config entries updated!");

View File

@@ -46,6 +46,7 @@ public class RenameGroupCommand extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
try { try {
GroupResource rsrc = groups.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(groupName)); GroupResource rsrc = groups.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(groupName));
NameInput input = new NameInput(); NameInput input = new NameInput();

View File

@@ -167,6 +167,7 @@ public class ReviewCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
enableGracefulStop();
if (abandonChange) { if (abandonChange) {
if (restoreChange) { if (restoreChange) {
throw die("abandon and restore actions are mutually exclusive"); throw die("abandon and restore actions are mutually exclusive");

View File

@@ -154,6 +154,7 @@ final class SetAccountCommand extends SshCommand {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
enableGracefulStop();
user = genericUserFactory.create(id); user = genericUserFactory.create(id);
validate(); validate();

View File

@@ -43,6 +43,7 @@ public class SetHeadCommand extends SshCommand {
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
enableGracefulStop();
HeadInput input = new HeadInput(); HeadInput input = new HeadInput();
input.ref = newHead; input.ref = newHead;
try { try {

View File

@@ -61,6 +61,7 @@ public class SetLoggingLevelCommand extends SshCommand {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
protected void run() throws MalformedURLException { protected void run() throws MalformedURLException {
enableGracefulStop();
if (level == LevelOption.RESET) { if (level == LevelOption.RESET) {
reset(); reset();
} else { } else {

View File

@@ -102,6 +102,7 @@ public class SetMembersCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure, Failure, Exception { protected void run() throws UnloggedFailure, Failure, Exception {
enableGracefulStop();
try { try {
for (AccountGroup.UUID groupUuid : groups) { for (AccountGroup.UUID groupUuid : groups) {
GroupResource resource = GroupResource resource =

View File

@@ -88,6 +88,7 @@ final class SetParentCommand extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
if (oldParent == null && children.isEmpty()) { if (oldParent == null && children.isEmpty()) {
throw die( throw die(
"child projects have to be specified as " "child projects have to be specified as "

View File

@@ -132,6 +132,7 @@ final class SetProjectCommand extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
ConfigInput configInput = new ConfigInput(); ConfigInput configInput = new ConfigInput();
configInput.requireChangeId = requireChangeID; configInput.requireChangeId = requireChangeID;
configInput.submitType = submitType; configInput.submitType = submitType;

View File

@@ -96,6 +96,7 @@ public class SetReviewersCommand extends SshCommand {
@Override @Override
protected void run() throws UnloggedFailure { protected void run() throws UnloggedFailure {
enableGracefulStop();
boolean ok = true; boolean ok = true;
for (ChangeResource rsrc : changes.values()) { for (ChangeResource rsrc : changes.values()) {
try { try {

View File

@@ -112,6 +112,7 @@ final class ShowCaches extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
nw = columns - 50; nw = columns - 50;
Date now = new Date(); Date now = new Date();
stdout.format( stdout.format(

View File

@@ -86,6 +86,7 @@ final class ShowConnections extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
final IoAcceptor acceptor = daemon.getIoAcceptor(); final IoAcceptor acceptor = daemon.getIoAcceptor();
if (acceptor == null) { if (acceptor == null) {
throw new Failure(1, "fatal: sshd no longer running"); throw new Failure(1, "fatal: sshd no longer running");

View File

@@ -85,6 +85,7 @@ final class ShowQueue extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
maxCommandWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4; maxCommandWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4;
stdout.print( stdout.print(
String.format( String.format(

View File

@@ -25,6 +25,7 @@ final class VersionCommand extends SshCommand {
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
enableGracefulStop();
String v = Version.getVersion(); String v = Version.getVersion();
if (v == null) { if (v == null) {
throw new Failure(1, "fatal: version unavailable"); throw new Failure(1, "fatal: version unavailable");

View File

@@ -0,0 +1,100 @@
// Copyright (C) 2020 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.acceptance.ssh;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.restapi.config.ListTasks;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
import com.google.inject.Module;
import java.time.LocalDateTime;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@NoHttpd
@UseSsh
@Sandboxed
@RunWith(ConfigSuite.class)
@SuppressWarnings("unused")
public class SshDaemonIT extends AbstractDaemonTest {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject private ListTasks listTasks;
@Inject private SitePaths gerritSitePath;
@ConfigSuite.Parameter protected Config config;
@ConfigSuite.Config
public static Config gracefulConfig() {
Config config = new Config();
config.setString("sshd", null, "gracefulStopTimeout", "10s");
return config;
}
@Override
public Module createSshModule() {
return new TestSshCommandModule();
}
public Future<Integer> startCommand(String command) throws Exception {
Callable<Integer> gracefulSession =
() -> {
int returnCode = -1;
logger.atFine().log("Before Command");
returnCode = userSshSession.execAndReturnStatus(command);
logger.atFine().log("After Command");
return returnCode;
};
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(gracefulSession);
LocalDateTime timeout = LocalDateTime.now().plusSeconds(10);
TestCommand.syncPoint.await();
return future;
}
@Test
public void NonGracefulCommandIsStoppedImmediately() throws Exception {
Future<Integer> future = startCommand("non-graceful -d 5");
restart();
Assert.assertTrue(future.get() == -1);
}
@Test
public void GracefulCommandIsStoppedGracefully() throws Exception {
Future<Integer> future = startCommand("graceful -d 5");
restart();
if (cfg.getTimeUnit("sshd", null, "gracefulStopTimeout", 0, TimeUnit.SECONDS) == 0) {
Assert.assertTrue(future.get() == -1);
} else {
Assert.assertTrue(future.get() == 0);
}
}
}

View File

@@ -108,8 +108,8 @@ def declare_nongoogle_deps():
maven_jar( maven_jar(
name = "jackson-core", name = "jackson-core",
artifact = "com.fasterxml.jackson.core:jackson-core:2.11.2", artifact = "com.fasterxml.jackson.core:jackson-core:2.11.3",
sha1 = "bc022ab0f0c83c07f9c52c5ab9a6a4932b15cc35", sha1 = "c2351800432bdbdd8284c3f5a7f0782a352aa84a",
) )
# Google internal dependencies: these are developed at Google, so there is # Google internal dependencies: these are developed at Google, so there is