Merge "Merge branch 'stable-3.0'"
This commit is contained in:
@@ -112,7 +112,8 @@ patchSet:: link:json.html#patchSet[patchSet attribute]
|
|||||||
|
|
||||||
submitter:: link:json.html#account[account attribute]
|
submitter:: link:json.html#account[account attribute]
|
||||||
|
|
||||||
newRev:: The resulting revision of the merge.
|
newRev:: The state (revision) of the target branch after the operation that
|
||||||
|
closed the change was completed.
|
||||||
|
|
||||||
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
|
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
|
||||||
created.
|
created.
|
||||||
|
|||||||
@@ -4271,7 +4271,8 @@ Number of threads to use when executing SSH command requests.
|
|||||||
If additional requests are received while all threads are busy they
|
If additional requests are received while all threads are busy they
|
||||||
are queued and serviced in a first-come-first-served order.
|
are queued and serviced in a first-come-first-served order.
|
||||||
+
|
+
|
||||||
By default, 2x the number of CPUs available to the JVM.
|
By default, 2x the number of CPUs available to the JVM (but at least 4
|
||||||
|
threads).
|
||||||
+
|
+
|
||||||
[NOTE]
|
[NOTE]
|
||||||
When SSH daemon is enabled then this setting also defines the max number of
|
When SSH daemon is enabled then this setting also defines the max number of
|
||||||
|
|||||||
@@ -1072,18 +1072,18 @@ maven_jar(
|
|||||||
sha1 = "0f5a654e4675769c716e5b387830d19b501ca191",
|
sha1 = "0f5a654e4675769c716e5b387830d19b501ca191",
|
||||||
)
|
)
|
||||||
|
|
||||||
TESTCONTAINERS_VERSION = "1.11.4"
|
TESTCONTAINERS_VERSION = "1.12.0"
|
||||||
|
|
||||||
maven_jar(
|
maven_jar(
|
||||||
name = "testcontainers",
|
name = "testcontainers",
|
||||||
artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
|
artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
|
||||||
sha1 = "b0c70b1a3608f43deafba7649b344a422a442585",
|
sha1 = "ac89643ce1ddde504da09172086aba0c7df10bff",
|
||||||
)
|
)
|
||||||
|
|
||||||
maven_jar(
|
maven_jar(
|
||||||
name = "testcontainers-elasticsearch",
|
name = "testcontainers-elasticsearch",
|
||||||
artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
|
artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
|
||||||
sha1 = "faab09a8876b8dbb326cbc10bbaa5ea86f5f5299",
|
sha1 = "cd9020f1803396c45ef935312bf232f9b17332b0",
|
||||||
)
|
)
|
||||||
|
|
||||||
maven_jar(
|
maven_jar(
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package com.google.gerrit.acceptance;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.FluentIterable;
|
import com.google.common.collect.FluentIterable;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.LinkedListMultimap;
|
import com.google.common.collect.LinkedListMultimap;
|
||||||
@@ -109,7 +110,8 @@ public class EventRecorder {
|
|||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImmutableList<ChangeMergedEvent> getChangeMergedEvents(
|
@VisibleForTesting
|
||||||
|
public ImmutableList<ChangeMergedEvent> getChangeMergedEvents(
|
||||||
String project, String branch, int expectedSize) {
|
String project, String branch, int expectedSize) {
|
||||||
String key = refEventKey(ChangeMergedEvent.TYPE, project, branch);
|
String key = refEventKey(ChangeMergedEvent.TYPE, project, branch);
|
||||||
if (expectedSize == 0) {
|
if (expectedSize == 0) {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
|
|
||||||
package com.google.gerrit.acceptance;
|
package com.google.gerrit.acceptance;
|
||||||
|
|
||||||
|
import static com.google.gerrit.server.git.receive.LazyPostReceiveHookChain.affectsSize;
|
||||||
|
import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.gerrit.acceptance.InProcessProtocol.Context;
|
import com.google.gerrit.acceptance.InProcessProtocol.Context;
|
||||||
import com.google.gerrit.common.data.Capable;
|
import com.google.gerrit.common.data.Capable;
|
||||||
@@ -40,6 +44,9 @@ import com.google.gerrit.server.permissions.ProjectPermission;
|
|||||||
import com.google.gerrit.server.plugincontext.PluginSetContext;
|
import com.google.gerrit.server.plugincontext.PluginSetContext;
|
||||||
import com.google.gerrit.server.project.ProjectCache;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
import com.google.gerrit.server.project.ProjectState;
|
import com.google.gerrit.server.project.ProjectState;
|
||||||
|
import com.google.gerrit.server.quota.QuotaBackend;
|
||||||
|
import com.google.gerrit.server.quota.QuotaException;
|
||||||
|
import com.google.gerrit.server.quota.QuotaResponse;
|
||||||
import com.google.gerrit.server.util.RequestContext;
|
import com.google.gerrit.server.util.RequestContext;
|
||||||
import com.google.gerrit.server.util.RequestScopePropagator;
|
import com.google.gerrit.server.util.RequestScopePropagator;
|
||||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
||||||
@@ -261,6 +268,7 @@ class InProcessProtocol extends TestProtocol<Context> {
|
|||||||
private final DynamicSet<PostReceiveHook> postReceiveHooks;
|
private final DynamicSet<PostReceiveHook> postReceiveHooks;
|
||||||
private final ThreadLocalRequestContext threadContext;
|
private final ThreadLocalRequestContext threadContext;
|
||||||
private final PermissionBackend permissionBackend;
|
private final PermissionBackend permissionBackend;
|
||||||
|
private final QuotaBackend quotaBackend;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Receive(
|
Receive(
|
||||||
@@ -271,7 +279,8 @@ class InProcessProtocol extends TestProtocol<Context> {
|
|||||||
PluginSetContext<ReceivePackInitializer> receivePackInitializers,
|
PluginSetContext<ReceivePackInitializer> receivePackInitializers,
|
||||||
DynamicSet<PostReceiveHook> postReceiveHooks,
|
DynamicSet<PostReceiveHook> postReceiveHooks,
|
||||||
ThreadLocalRequestContext threadContext,
|
ThreadLocalRequestContext threadContext,
|
||||||
PermissionBackend permissionBackend) {
|
PermissionBackend permissionBackend,
|
||||||
|
QuotaBackend quotaBackend) {
|
||||||
this.userProvider = userProvider;
|
this.userProvider = userProvider;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
@@ -280,6 +289,7 @@ class InProcessProtocol extends TestProtocol<Context> {
|
|||||||
this.postReceiveHooks = postReceiveHooks;
|
this.postReceiveHooks = postReceiveHooks;
|
||||||
this.threadContext = threadContext;
|
this.threadContext = threadContext;
|
||||||
this.permissionBackend = permissionBackend;
|
this.permissionBackend = permissionBackend;
|
||||||
|
this.quotaBackend = quotaBackend;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -319,10 +329,35 @@ class InProcessProtocol extends TestProtocol<Context> {
|
|||||||
|
|
||||||
receivePackInitializers.runEach(
|
receivePackInitializers.runEach(
|
||||||
initializer -> initializer.init(projectState.getNameKey(), rp));
|
initializer -> initializer.init(projectState.getNameKey(), rp));
|
||||||
|
QuotaResponse.Aggregated availableTokens =
|
||||||
|
quotaBackend
|
||||||
|
.user(identifiedUser)
|
||||||
|
.project(req.project)
|
||||||
|
.availableTokens(REPOSITORY_SIZE_GROUP);
|
||||||
|
availableTokens.throwOnError();
|
||||||
|
availableTokens.availableTokens().ifPresent(v -> rp.setMaxObjectSizeLimit(v));
|
||||||
|
|
||||||
rp.setPostReceiveHook(PostReceiveHookChain.newChain(Lists.newArrayList(postReceiveHooks)));
|
ImmutableList<PostReceiveHook> hooks =
|
||||||
|
ImmutableList.<PostReceiveHook>builder()
|
||||||
|
.add(
|
||||||
|
(pack, commands) -> {
|
||||||
|
if (affectsSize(pack, commands)) {
|
||||||
|
try {
|
||||||
|
quotaBackend
|
||||||
|
.user(identifiedUser)
|
||||||
|
.project(req.project)
|
||||||
|
.requestTokens(REPOSITORY_SIZE_GROUP, pack.getPackSize())
|
||||||
|
.throwOnError();
|
||||||
|
} catch (QuotaException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addAll(postReceiveHooks)
|
||||||
|
.build();
|
||||||
|
rp.setPostReceiveHook(PostReceiveHookChain.newChain(hooks));
|
||||||
return rp;
|
return rp;
|
||||||
} catch (IOException | PermissionBackendException e) {
|
} catch (IOException | PermissionBackendException | QuotaException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public class AccountCacheImpl implements AccountCache {
|
|||||||
return byId.get(accountId);
|
return byId.get(accountId);
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
logger.atWarning().withCause(e).log("Cannot load AccountState for ID %s", accountId);
|
logger.atWarning().withCause(e).log("Cannot load AccountState for ID %s", accountId);
|
||||||
return null;
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ public class AccountCacheImpl implements AccountCache {
|
|||||||
.orElseGet(Optional::empty);
|
.orElseGet(Optional::empty);
|
||||||
} catch (IOException | ConfigInvalidException e) {
|
} catch (IOException | ConfigInvalidException e) {
|
||||||
logger.atWarning().withCause(e).log("Cannot load AccountState for username %s", username);
|
logger.atWarning().withCause(e).log("Cannot load AccountState for username %s", username);
|
||||||
return null;
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ public class NotifyResolver {
|
|||||||
|
|
||||||
public abstract NotifyHandling handling();
|
public abstract NotifyHandling handling();
|
||||||
|
|
||||||
// TODO(dborowitz): Should be ImmutableSetMultimap.
|
|
||||||
public abstract ImmutableSetMultimap<RecipientType, Account.Id> accounts();
|
public abstract ImmutableSetMultimap<RecipientType, Account.Id> accounts();
|
||||||
|
|
||||||
public Result withHandling(NotifyHandling notifyHandling) {
|
public Result withHandling(NotifyHandling notifyHandling) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class ThreadSettingsConfig {
|
|||||||
@Inject
|
@Inject
|
||||||
ThreadSettingsConfig(@GerritServerConfig Config cfg) {
|
ThreadSettingsConfig(@GerritServerConfig Config cfg) {
|
||||||
int cores = Runtime.getRuntime().availableProcessors();
|
int cores = Runtime.getRuntime().availableProcessors();
|
||||||
sshdThreads = cfg.getInt("sshd", "threads", 2 * cores);
|
sshdThreads = cfg.getInt("sshd", "threads", Math.max(4, 2 * cores));
|
||||||
httpdMaxThreads = cfg.getInt("httpd", "maxThreads", 25);
|
httpdMaxThreads = cfg.getInt("httpd", "maxThreads", 25);
|
||||||
int defaultDatabasePoolLimit = sshdThreads + httpdMaxThreads + 2;
|
int defaultDatabasePoolLimit = sshdThreads + httpdMaxThreads + 2;
|
||||||
databasePoolLimit = cfg.getInt("database", "poolLimit", defaultDatabasePoolLimit);
|
databasePoolLimit = cfg.getInt("database", "poolLimit", defaultDatabasePoolLimit);
|
||||||
|
|||||||
@@ -49,7 +49,10 @@ public class MergedByPushOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
public interface Factory {
|
public interface Factory {
|
||||||
MergedByPushOp create(
|
MergedByPushOp create(
|
||||||
RequestScopePropagator requestScopePropagator, PatchSet.Id psId, String refName);
|
RequestScopePropagator requestScopePropagator,
|
||||||
|
PatchSet.Id psId,
|
||||||
|
@Assisted("refName") String refName,
|
||||||
|
@Assisted("mergeResultRevId") String mergeResultRevId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final RequestScopePropagator requestScopePropagator;
|
private final RequestScopePropagator requestScopePropagator;
|
||||||
@@ -62,6 +65,7 @@ public class MergedByPushOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
private final PatchSet.Id psId;
|
private final PatchSet.Id psId;
|
||||||
private final String refName;
|
private final String refName;
|
||||||
|
private final String mergeResultRevId;
|
||||||
|
|
||||||
private Change change;
|
private Change change;
|
||||||
private boolean correctBranch;
|
private boolean correctBranch;
|
||||||
@@ -79,7 +83,8 @@ public class MergedByPushOp implements BatchUpdateOp {
|
|||||||
ChangeMerged changeMerged,
|
ChangeMerged changeMerged,
|
||||||
@Assisted RequestScopePropagator requestScopePropagator,
|
@Assisted RequestScopePropagator requestScopePropagator,
|
||||||
@Assisted PatchSet.Id psId,
|
@Assisted PatchSet.Id psId,
|
||||||
@Assisted String refName) {
|
@Assisted("refName") String refName,
|
||||||
|
@Assisted("mergeResultRevId") String mergeResultRevId) {
|
||||||
this.patchSetInfoFactory = patchSetInfoFactory;
|
this.patchSetInfoFactory = patchSetInfoFactory;
|
||||||
this.cmUtil = cmUtil;
|
this.cmUtil = cmUtil;
|
||||||
this.mergedSenderFactory = mergedSenderFactory;
|
this.mergedSenderFactory = mergedSenderFactory;
|
||||||
@@ -89,6 +94,7 @@ public class MergedByPushOp implements BatchUpdateOp {
|
|||||||
this.requestScopePropagator = requestScopePropagator;
|
this.requestScopePropagator = requestScopePropagator;
|
||||||
this.psId = psId;
|
this.psId = psId;
|
||||||
this.refName = refName;
|
this.refName = refName;
|
||||||
|
this.mergeResultRevId = mergeResultRevId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMergedIntoRef() {
|
public String getMergedIntoRef() {
|
||||||
@@ -184,8 +190,7 @@ public class MergedByPushOp implements BatchUpdateOp {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
changeMerged.fire(
|
changeMerged.fire(change, patchSet, ctx.getAccount(), mergeResultRevId, ctx.getWhen());
|
||||||
change, patchSet, ctx.getAccount(), patchSet.commitId().name(), ctx.getWhen());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PatchSetInfo getPatchSetInfo(ChangeContext ctx) throws IOException {
|
private PatchSetInfo getPatchSetInfo(ChangeContext ctx) throws IOException {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git.receive;
|
package com.google.gerrit.server.git.receive;
|
||||||
|
|
||||||
|
import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP;
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
@@ -45,6 +46,9 @@ import com.google.gerrit.server.permissions.ProjectPermission;
|
|||||||
import com.google.gerrit.server.project.ContributorAgreementsChecker;
|
import com.google.gerrit.server.project.ContributorAgreementsChecker;
|
||||||
import com.google.gerrit.server.project.ProjectState;
|
import com.google.gerrit.server.project.ProjectState;
|
||||||
import com.google.gerrit.server.query.change.InternalChangeQuery;
|
import com.google.gerrit.server.query.change.InternalChangeQuery;
|
||||||
|
import com.google.gerrit.server.quota.QuotaBackend;
|
||||||
|
import com.google.gerrit.server.quota.QuotaException;
|
||||||
|
import com.google.gerrit.server.quota.QuotaResponse;
|
||||||
import com.google.gerrit.server.util.MagicBranch;
|
import com.google.gerrit.server.util.MagicBranch;
|
||||||
import com.google.gerrit.server.util.RequestScopePropagator;
|
import com.google.gerrit.server.util.RequestScopePropagator;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -92,6 +96,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
|||||||
public static class Module extends PrivateModule {
|
public static class Module extends PrivateModule {
|
||||||
@Override
|
@Override
|
||||||
public void configure() {
|
public void configure() {
|
||||||
|
install(new FactoryModuleBuilder().build(LazyPostReceiveHookChain.Factory.class));
|
||||||
install(new FactoryModuleBuilder().build(AsyncReceiveCommits.Factory.class));
|
install(new FactoryModuleBuilder().build(AsyncReceiveCommits.Factory.class));
|
||||||
expose(AsyncReceiveCommits.Factory.class);
|
expose(AsyncReceiveCommits.Factory.class);
|
||||||
// Don't expose the binding for ReceiveCommits.Factory. All callers should
|
// Don't expose the binding for ReceiveCommits.Factory. All callers should
|
||||||
@@ -257,9 +262,10 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
|||||||
RequestScopePropagator scopePropagator,
|
RequestScopePropagator scopePropagator,
|
||||||
ReceiveConfig receiveConfig,
|
ReceiveConfig receiveConfig,
|
||||||
TransferConfig transferConfig,
|
TransferConfig transferConfig,
|
||||||
Provider<LazyPostReceiveHookChain> lazyPostReceive,
|
LazyPostReceiveHookChain.Factory lazyPostReceive,
|
||||||
ContributorAgreementsChecker contributorAgreements,
|
ContributorAgreementsChecker contributorAgreements,
|
||||||
Metrics metrics,
|
Metrics metrics,
|
||||||
|
QuotaBackend quotaBackend,
|
||||||
@Named(TIMEOUT_NAME) long timeoutMillis,
|
@Named(TIMEOUT_NAME) long timeoutMillis,
|
||||||
@Assisted ProjectState projectState,
|
@Assisted ProjectState projectState,
|
||||||
@Assisted IdentifiedUser user,
|
@Assisted IdentifiedUser user,
|
||||||
@@ -288,7 +294,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
|||||||
receivePack.setRefFilter(new ReceiveRefFilter());
|
receivePack.setRefFilter(new ReceiveRefFilter());
|
||||||
receivePack.setAllowPushOptions(true);
|
receivePack.setAllowPushOptions(true);
|
||||||
receivePack.setPreReceiveHook(this);
|
receivePack.setPreReceiveHook(this);
|
||||||
receivePack.setPostReceiveHook(lazyPostReceive.get());
|
receivePack.setPostReceiveHook(lazyPostReceive.create(user, projectName));
|
||||||
|
|
||||||
// If the user lacks READ permission, some references may be filtered and hidden from view.
|
// If the user lacks READ permission, some references may be filtered and hidden from view.
|
||||||
// Check objects mentioned inside the incoming pack file are reachable from visible refs.
|
// Check objects mentioned inside the incoming pack file are reachable from visible refs.
|
||||||
@@ -310,6 +316,17 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
|||||||
factory.create(
|
factory.create(
|
||||||
projectState, user, receivePack, allRefsWatcher, messageSender, resultChangeIds);
|
projectState, user, receivePack, allRefsWatcher, messageSender, resultChangeIds);
|
||||||
receiveCommits.init();
|
receiveCommits.init();
|
||||||
|
QuotaResponse.Aggregated availableTokens =
|
||||||
|
quotaBackend.user(user).project(projectName).availableTokens(REPOSITORY_SIZE_GROUP);
|
||||||
|
try {
|
||||||
|
availableTokens.throwOnError();
|
||||||
|
} catch (QuotaException e) {
|
||||||
|
logger.atWarning().withCause(e).log(
|
||||||
|
"Quota %s availableTokens request failed for project %s",
|
||||||
|
REPOSITORY_SIZE_GROUP, projectName);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
availableTokens.availableTokens().ifPresent(v -> receivePack.setMaxObjectSizeLimit(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Determine if the user can upload commits. */
|
/** Determine if the user can upload commits. */
|
||||||
|
|||||||
@@ -14,23 +14,78 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git.receive;
|
package com.google.gerrit.server.git.receive;
|
||||||
|
|
||||||
|
import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP;
|
||||||
|
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.plugincontext.PluginSetContext;
|
import com.google.gerrit.server.plugincontext.PluginSetContext;
|
||||||
|
import com.google.gerrit.server.quota.QuotaBackend;
|
||||||
|
import com.google.gerrit.server.quota.QuotaResponse;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import org.eclipse.jgit.transport.PostReceiveHook;
|
import org.eclipse.jgit.transport.PostReceiveHook;
|
||||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
import org.eclipse.jgit.transport.ReceivePack;
|
import org.eclipse.jgit.transport.ReceivePack;
|
||||||
|
|
||||||
class LazyPostReceiveHookChain implements PostReceiveHook {
|
/**
|
||||||
|
* Class is responsible for calling all registered post-receive hooks. In addition, in case when
|
||||||
|
* repository size quota is defined, it requests tokens (pack size) that were received. This is the
|
||||||
|
* final step of enforcing repository size quota that deducts token from available tokens.
|
||||||
|
*/
|
||||||
|
public class LazyPostReceiveHookChain implements PostReceiveHook {
|
||||||
|
interface Factory {
|
||||||
|
LazyPostReceiveHookChain create(CurrentUser user, Project.NameKey project);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
private final PluginSetContext<PostReceiveHook> hooks;
|
private final PluginSetContext<PostReceiveHook> hooks;
|
||||||
|
private final QuotaBackend quotaBackend;
|
||||||
|
private final CurrentUser user;
|
||||||
|
private final Project.NameKey project;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LazyPostReceiveHookChain(PluginSetContext<PostReceiveHook> hooks) {
|
LazyPostReceiveHookChain(
|
||||||
|
PluginSetContext<PostReceiveHook> hooks,
|
||||||
|
QuotaBackend quotaBackend,
|
||||||
|
@Assisted CurrentUser user,
|
||||||
|
@Assisted Project.NameKey project) {
|
||||||
this.hooks = hooks;
|
this.hooks = hooks;
|
||||||
|
this.quotaBackend = quotaBackend;
|
||||||
|
this.user = user;
|
||||||
|
this.project = project;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
|
public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
|
||||||
hooks.runEach(h -> h.onPostReceive(rp, commands));
|
hooks.runEach(h -> h.onPostReceive(rp, commands));
|
||||||
|
if (affectsSize(rp, commands)) {
|
||||||
|
QuotaResponse.Aggregated a =
|
||||||
|
quotaBackend
|
||||||
|
.user(user)
|
||||||
|
.project(project)
|
||||||
|
.requestTokens(REPOSITORY_SIZE_GROUP, rp.getPackSize());
|
||||||
|
if (a.hasError()) {
|
||||||
|
String msg =
|
||||||
|
String.format(
|
||||||
|
"%s request failed for project %s with [%s]",
|
||||||
|
REPOSITORY_SIZE_GROUP, project, a.errorMessage());
|
||||||
|
logger.atWarning().log(msg);
|
||||||
|
throw new RuntimeException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean affectsSize(ReceivePack rp, Collection<ReceiveCommand> commands) {
|
||||||
|
if (rp.getPackSize() > 0L) {
|
||||||
|
for (ReceiveCommand cmd : commands) {
|
||||||
|
if (cmd.getType() != ReceiveCommand.Type.DELETE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2952,6 +2952,7 @@ class ReceiveCommits {
|
|||||||
projectState,
|
projectState,
|
||||||
notes.getChange().getDest(),
|
notes.getChange().getDest(),
|
||||||
checkMergedInto,
|
checkMergedInto,
|
||||||
|
checkMergedInto ? inputCommand.getNewId().name() : null,
|
||||||
priorPatchSet,
|
priorPatchSet,
|
||||||
priorCommit,
|
priorCommit,
|
||||||
psId,
|
psId,
|
||||||
@@ -3187,6 +3188,9 @@ class ReceiveCommits {
|
|||||||
int limit = receiveConfig.maxBatchCommits;
|
int limit = receiveConfig.maxBatchCommits;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (RevCommit c; (c = walk.next()) != null; ) {
|
for (RevCommit c; (c = walk.next()) != null; ) {
|
||||||
|
// Even if skipValidation is set, we still get here when at least one plugin
|
||||||
|
// commit validator requires to validate all commits. In this case, however,
|
||||||
|
// we don't need to check the commit limit.
|
||||||
if (++n > limit && !skipValidation) {
|
if (++n > limit && !skipValidation) {
|
||||||
logger.atFine().log("Number of new commits exceeds limit of %d", limit);
|
logger.atFine().log("Number of new commits exceeds limit of %d", limit);
|
||||||
reject(
|
reject(
|
||||||
@@ -3263,7 +3267,8 @@ class ReceiveCommits {
|
|||||||
bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
|
bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
|
||||||
bu.addOp(
|
bu.addOp(
|
||||||
psId.changeId(),
|
psId.changeId(),
|
||||||
mergedByPushOpFactory.create(requestScopePropagator, psId, refName));
|
mergedByPushOpFactory.create(
|
||||||
|
requestScopePropagator, psId, refName, newTip.getId().getName()));
|
||||||
continue COMMIT;
|
continue COMMIT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3297,7 +3302,8 @@ class ReceiveCommits {
|
|||||||
bu.addOp(
|
bu.addOp(
|
||||||
id,
|
id,
|
||||||
mergedByPushOpFactory
|
mergedByPushOpFactory
|
||||||
.create(requestScopePropagator, req.psId, refName)
|
.create(
|
||||||
|
requestScopePropagator, req.psId, refName, newTip.getId().getName())
|
||||||
.setPatchSetProvider(req.replaceOp::getPatchSet));
|
.setPatchSetProvider(req.replaceOp::getPatchSet));
|
||||||
bu.addOp(id, new ChangeProgressOp(progress));
|
bu.addOp(id, new ChangeProgressOp(progress));
|
||||||
ids.add(id);
|
ids.add(id);
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ public class ReplaceOp implements BatchUpdateOp {
|
|||||||
ProjectState projectState,
|
ProjectState projectState,
|
||||||
BranchNameKey dest,
|
BranchNameKey dest,
|
||||||
boolean checkMergedInto,
|
boolean checkMergedInto,
|
||||||
|
@Nullable String mergeResultRevId,
|
||||||
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
|
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
|
||||||
@Assisted("priorCommitId") ObjectId priorCommit,
|
@Assisted("priorCommitId") ObjectId priorCommit,
|
||||||
@Assisted("patchSetId") PatchSet.Id patchSetId,
|
@Assisted("patchSetId") PatchSet.Id patchSetId,
|
||||||
@@ -133,6 +134,7 @@ public class ReplaceOp implements BatchUpdateOp {
|
|||||||
private final ProjectState projectState;
|
private final ProjectState projectState;
|
||||||
private final BranchNameKey dest;
|
private final BranchNameKey dest;
|
||||||
private final boolean checkMergedInto;
|
private final boolean checkMergedInto;
|
||||||
|
private final String mergeResultRevId;
|
||||||
private final PatchSet.Id priorPatchSetId;
|
private final PatchSet.Id priorPatchSetId;
|
||||||
private final ObjectId priorCommitId;
|
private final ObjectId priorCommitId;
|
||||||
private final PatchSet.Id patchSetId;
|
private final PatchSet.Id patchSetId;
|
||||||
@@ -177,6 +179,7 @@ public class ReplaceOp implements BatchUpdateOp {
|
|||||||
@Assisted ProjectState projectState,
|
@Assisted ProjectState projectState,
|
||||||
@Assisted BranchNameKey dest,
|
@Assisted BranchNameKey dest,
|
||||||
@Assisted boolean checkMergedInto,
|
@Assisted boolean checkMergedInto,
|
||||||
|
@Assisted @Nullable String mergeResultRevId,
|
||||||
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
|
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
|
||||||
@Assisted("priorCommitId") ObjectId priorCommitId,
|
@Assisted("priorCommitId") ObjectId priorCommitId,
|
||||||
@Assisted("patchSetId") PatchSet.Id patchSetId,
|
@Assisted("patchSetId") PatchSet.Id patchSetId,
|
||||||
@@ -205,6 +208,7 @@ public class ReplaceOp implements BatchUpdateOp {
|
|||||||
this.projectState = projectState;
|
this.projectState = projectState;
|
||||||
this.dest = dest;
|
this.dest = dest;
|
||||||
this.checkMergedInto = checkMergedInto;
|
this.checkMergedInto = checkMergedInto;
|
||||||
|
this.mergeResultRevId = mergeResultRevId;
|
||||||
this.priorPatchSetId = priorPatchSetId;
|
this.priorPatchSetId = priorPatchSetId;
|
||||||
this.priorCommitId = priorCommitId.copy();
|
this.priorCommitId = priorCommitId.copy();
|
||||||
this.patchSetId = patchSetId;
|
this.patchSetId = patchSetId;
|
||||||
@@ -231,7 +235,8 @@ public class ReplaceOp implements BatchUpdateOp {
|
|||||||
String mergedInto = findMergedInto(ctx, dest.branch(), commit);
|
String mergedInto = findMergedInto(ctx, dest.branch(), commit);
|
||||||
if (mergedInto != null) {
|
if (mergedInto != null) {
|
||||||
mergedByPushOp =
|
mergedByPushOp =
|
||||||
mergedByPushOpFactory.create(requestScopePropagator, patchSetId, mergedInto);
|
mergedByPushOpFactory.create(
|
||||||
|
requestScopePropagator, patchSetId, mergedInto, mergeResultRevId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.google.gerrit.reviewdb.client.Project;
|
|||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.plugincontext.PluginSetContext;
|
import com.google.gerrit.server.plugincontext.PluginSetContext;
|
||||||
import com.google.gerrit.server.plugincontext.PluginSetEntryContext;
|
import com.google.gerrit.server.plugincontext.PluginSetEntryContext;
|
||||||
|
import com.google.gerrit.server.quota.QuotaResponse.Aggregated;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
@@ -100,6 +101,20 @@ public class DefaultQuotaBackend implements QuotaBackend {
|
|||||||
return QuotaResponse.Aggregated.create(ImmutableList.copyOf(responses));
|
return QuotaResponse.Aggregated.create(ImmutableList.copyOf(responses));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static QuotaResponse.Aggregated availableTokens(
|
||||||
|
PluginSetContext<QuotaEnforcer> quotaEnforcers,
|
||||||
|
String quotaGroup,
|
||||||
|
QuotaRequestContext requestContext) {
|
||||||
|
// PluginSets can change their content when plugins (de-)register. Copy the currently registered
|
||||||
|
// plugins so that we can iterate twice on a stable list.
|
||||||
|
List<PluginSetEntryContext<QuotaEnforcer>> enforcers = ImmutableList.copyOf(quotaEnforcers);
|
||||||
|
List<QuotaResponse> responses = new ArrayList<>(enforcers.size());
|
||||||
|
for (PluginSetEntryContext<QuotaEnforcer> enforcer : enforcers) {
|
||||||
|
responses.add(enforcer.call(p -> p.availableTokens(quotaGroup, requestContext)));
|
||||||
|
}
|
||||||
|
return QuotaResponse.Aggregated.create(responses);
|
||||||
|
}
|
||||||
|
|
||||||
private static void refillAfterErrorOrException(
|
private static void refillAfterErrorOrException(
|
||||||
List<PluginSetEntryContext<QuotaEnforcer>> enforcers,
|
List<PluginSetEntryContext<QuotaEnforcer>> enforcers,
|
||||||
List<QuotaResponse> collectedResponses,
|
List<QuotaResponse> collectedResponses,
|
||||||
@@ -158,5 +173,10 @@ public class DefaultQuotaBackend implements QuotaBackend {
|
|||||||
return DefaultQuotaBackend.request(
|
return DefaultQuotaBackend.request(
|
||||||
quotaEnforcers, quotaGroup, requestContext, numTokens, false);
|
quotaEnforcers, quotaGroup, requestContext, numTokens, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Aggregated availableTokens(String quotaGroup) {
|
||||||
|
return DefaultQuotaBackend.availableTokens(quotaEnforcers, quotaGroup, requestContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,5 +82,22 @@ public interface QuotaBackend {
|
|||||||
* not to deduct any quota yet. Can be used to do pre-flight requests where necessary
|
* not to deduct any quota yet. Can be used to do pre-flight requests where necessary
|
||||||
*/
|
*/
|
||||||
QuotaResponse.Aggregated dryRun(String quotaGroup, long tokens);
|
QuotaResponse.Aggregated dryRun(String quotaGroup, long tokens);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a minimum number of tokens available in implementations. This is a pre-flight check
|
||||||
|
* for the exceptional case when the requested number of tokens is not known in advance but
|
||||||
|
* boundary can be specified. For instance, when the commit is received its size is not known
|
||||||
|
* until the transfer happens however one can specify how many bytes can be accepted to meet the
|
||||||
|
* repository size quota.
|
||||||
|
*
|
||||||
|
* <p>By definition, this is not an allocating request, therefore, it should be followed by the
|
||||||
|
* call to {@link #requestTokens(String, long)} when the size gets determined so that quota
|
||||||
|
* could be properly adjusted. It is in developer discretion to ensure that it gets called.
|
||||||
|
* There might be a case when particular quota gets temporarily overbooked when multiple
|
||||||
|
* requests are performed but the following calls to {@link #requestTokens(String, long)} will
|
||||||
|
* fail at the moment when a quota is exhausted. It is not a subject of quota backend to reclaim
|
||||||
|
* tokens that were used due to overbooking.
|
||||||
|
*/
|
||||||
|
QuotaResponse.Aggregated availableTokens(String quotaGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,19 @@ public interface QuotaEnforcer {
|
|||||||
*/
|
*/
|
||||||
QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens);
|
QuotaResponse dryRun(String quotaGroup, QuotaRequestContext ctx, long numTokens);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns available tokens that can be later requested.
|
||||||
|
*
|
||||||
|
* <p>This is used as a pre-flight check for the exceptional case when the requested number of
|
||||||
|
* tokens is not known in advance. Implementation should not deduct tokens from a bucket. It
|
||||||
|
* should be followed by a call to {@link #requestTokens(String, QuotaRequestContext, long)} with
|
||||||
|
* the number of tokens that were eventually used. It is in {@link QuotaBackend} callers
|
||||||
|
* discretion to ensure that {@link
|
||||||
|
* com.google.gerrit.server.quota.QuotaBackend.WithResource#requestTokens(String, long)} is
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
|
QuotaResponse availableTokens(String quotaGroup, QuotaRequestContext ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A previously requested and deducted quota has to be refilled (if possible) because the request
|
* A previously requested and deducted quota has to be refilled (if possible) because the request
|
||||||
* failed other quota checks. Implementations can choose to leave this a no-op in case they are
|
* failed other quota checks. Implementations can choose to leave this a no-op in case they are
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (C) 2019 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.server.quota;
|
||||||
|
|
||||||
|
public class QuotaGroupDefinitions {
|
||||||
|
/**
|
||||||
|
* Definition of repository size quota group. {@link QuotaEnforcer} implementations for repository
|
||||||
|
* size quota have to act on requests with this group name.
|
||||||
|
*/
|
||||||
|
public static final String REPOSITORY_SIZE_GROUP = "/repository:size";
|
||||||
|
|
||||||
|
private QuotaGroupDefinitions() {}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.google.common.collect.Streams;
|
import com.google.common.collect.Streams;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalLong;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@AutoValue
|
@AutoValue
|
||||||
@@ -55,6 +56,10 @@ public abstract class QuotaResponse {
|
|||||||
return new AutoValue_QuotaResponse.Builder().status(Status.OK).build();
|
return new AutoValue_QuotaResponse.Builder().status(Status.OK).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static QuotaResponse ok(long tokens) {
|
||||||
|
return new AutoValue_QuotaResponse.Builder().status(Status.OK).availableTokens(tokens).build();
|
||||||
|
}
|
||||||
|
|
||||||
public static QuotaResponse noOp() {
|
public static QuotaResponse noOp() {
|
||||||
return new AutoValue_QuotaResponse.Builder().status(Status.NO_OP).build();
|
return new AutoValue_QuotaResponse.Builder().status(Status.NO_OP).build();
|
||||||
}
|
}
|
||||||
@@ -65,12 +70,16 @@ public abstract class QuotaResponse {
|
|||||||
|
|
||||||
public abstract Status status();
|
public abstract Status status();
|
||||||
|
|
||||||
|
public abstract Optional<Long> availableTokens();
|
||||||
|
|
||||||
public abstract Optional<String> message();
|
public abstract Optional<String> message();
|
||||||
|
|
||||||
@AutoValue.Builder
|
@AutoValue.Builder
|
||||||
public abstract static class Builder {
|
public abstract static class Builder {
|
||||||
public abstract QuotaResponse.Builder status(Status status);
|
public abstract QuotaResponse.Builder status(Status status);
|
||||||
|
|
||||||
|
public abstract QuotaResponse.Builder availableTokens(Long tokens);
|
||||||
|
|
||||||
public abstract QuotaResponse.Builder message(String message);
|
public abstract QuotaResponse.Builder message(String message);
|
||||||
|
|
||||||
public abstract QuotaResponse build();
|
public abstract QuotaResponse build();
|
||||||
@@ -96,6 +105,13 @@ public abstract class QuotaResponse {
|
|||||||
return responses().stream().filter(r -> r.status().isOk()).collect(toImmutableList());
|
return responses().stream().filter(r -> r.status().isOk()).collect(toImmutableList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OptionalLong availableTokens() {
|
||||||
|
return responses().stream()
|
||||||
|
.filter(r -> r.status().isOk() && r.availableTokens().isPresent())
|
||||||
|
.mapToLong(r -> r.availableTokens().get())
|
||||||
|
.min();
|
||||||
|
}
|
||||||
|
|
||||||
public ImmutableList<QuotaResponse> error() {
|
public ImmutableList<QuotaResponse> error() {
|
||||||
return responses().stream().filter(r -> r.status().isError()).collect(toImmutableList());
|
return responses().stream().filter(r -> r.status().isError()).collect(toImmutableList());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class LsUserRefs extends SshCommand {
|
|||||||
try {
|
try {
|
||||||
Map<String, Ref> refsMap =
|
Map<String, Ref> refsMap =
|
||||||
permissionBackend
|
permissionBackend
|
||||||
.user(user)
|
.user(ctx.getUser())
|
||||||
.project(projectName)
|
.project(projectName)
|
||||||
.filter(repo.getRefDatabase().getRefs(), repo, RefFilterOptions.defaults());
|
.filter(repo.getRefDatabase().getRefs(), repo, RefFilterOptions.defaults());
|
||||||
|
|
||||||
|
|||||||
@@ -2250,6 +2250,34 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
|
|||||||
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
|
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
|
||||||
@Test
|
@Test
|
||||||
public void maxBatchCommits() throws Exception {
|
public void maxBatchCommits() throws Exception {
|
||||||
|
testMaxBatchCommits();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
|
||||||
|
@Test
|
||||||
|
public void maxBatchCommitsWithDefaultValidator() throws Exception {
|
||||||
|
TestValidator validator = new TestValidator();
|
||||||
|
RegistrationHandle handle = commitValidators.add("test-validator", validator);
|
||||||
|
try {
|
||||||
|
testMaxBatchCommits();
|
||||||
|
} finally {
|
||||||
|
handle.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GerritConfig(name = "receive.maxBatchCommits", value = "2")
|
||||||
|
@Test
|
||||||
|
public void maxBatchCommitsWithValidateAllCommitsValidator() throws Exception {
|
||||||
|
TestValidator validator = new TestValidator(true);
|
||||||
|
RegistrationHandle handle = commitValidators.add("test-validator", validator);
|
||||||
|
try {
|
||||||
|
testMaxBatchCommits();
|
||||||
|
} finally {
|
||||||
|
handle.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testMaxBatchCommits() throws Exception {
|
||||||
List<RevCommit> commits = new ArrayList<>();
|
List<RevCommit> commits = new ArrayList<>();
|
||||||
commits.addAll(initChanges(2));
|
commits.addAll(initChanges(2));
|
||||||
String master = "refs/heads/master";
|
String master = "refs/heads/master";
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
|
import com.google.gerrit.server.events.ChangeMergedEvent;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
import com.google.gerrit.testing.FakeEmailSender.Message;
|
import com.google.gerrit.testing.FakeEmailSender.Message;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import java.util.List;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.api.errors.InvalidRemoteException;
|
import org.eclipse.jgit.api.errors.InvalidRemoteException;
|
||||||
import org.eclipse.jgit.api.errors.TransportException;
|
import org.eclipse.jgit.api.errors.TransportException;
|
||||||
@@ -201,6 +203,23 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
|
|||||||
assertThat(cd.patchSet(psId).commitId()).isEqualTo(c);
|
assertThat(cd.patchSet(psId).commitId()).isEqualTo(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void correctNewRevOnMergeByPushToBranch() throws Exception {
|
||||||
|
projectOperations
|
||||||
|
.project(project)
|
||||||
|
.forUpdate()
|
||||||
|
.add(allow(Permission.PUSH).ref("refs/heads/master").group(adminGroupUuid()))
|
||||||
|
.update();
|
||||||
|
push("refs/for/master", PushOneCommit.SUBJECT, "one.txt", "One");
|
||||||
|
PushOneCommit.Result r = push("refs/for/master", PushOneCommit.SUBJECT, "two.txt", "Two");
|
||||||
|
startEventRecorder();
|
||||||
|
git().push().setRefSpecs(new RefSpec(r.getCommit().name() + ":refs/heads/master")).call();
|
||||||
|
List<ChangeMergedEvent> changeMergedEvents =
|
||||||
|
eventRecorder.getChangeMergedEvents(project.get(), "refs/heads/master", 2);
|
||||||
|
assertThat(changeMergedEvents.get(0).newRev).isEqualTo(r.getPatchSet().commitId().name());
|
||||||
|
assertThat(changeMergedEvents.get(1).newRev).isEqualTo(r.getPatchSet().commitId().name());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mergeOnPushToBranchWithChangeMergedInOther() throws Exception {
|
public void mergeOnPushToBranchWithChangeMergedInOther() throws Exception {
|
||||||
enableCreateNewChangeForAllNotInTarget();
|
enableCreateNewChangeForAllNotInTarget();
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import static org.easymock.EasyMock.resetToStrict;
|
|||||||
|
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.extensions.annotations.Exports;
|
import com.google.gerrit.extensions.annotations.Exports;
|
||||||
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
|
import com.google.gerrit.extensions.common.ChangeInput;
|
||||||
import com.google.gerrit.extensions.config.FactoryModule;
|
import com.google.gerrit.extensions.config.FactoryModule;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
@@ -35,7 +37,9 @@ import com.google.inject.Module;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import org.easymock.EasyMock;
|
import org.easymock.EasyMock;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
public class DefaultQuotaBackendIT extends AbstractDaemonTest {
|
public class DefaultQuotaBackendIT extends AbstractDaemonTest {
|
||||||
|
|
||||||
@@ -44,6 +48,8 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
|
|||||||
private IdentifiedUser identifiedAdmin;
|
private IdentifiedUser identifiedAdmin;
|
||||||
@Inject private QuotaBackend quotaBackend;
|
@Inject private QuotaBackend quotaBackend;
|
||||||
|
|
||||||
|
@Rule public ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Module createModule() {
|
public Module createModule() {
|
||||||
return new FactoryModule() {
|
return new FactoryModule() {
|
||||||
@@ -93,7 +99,7 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestTokenForUserAndChange() throws Exception {
|
public void requestTokenForUserAndChange() throws Exception {
|
||||||
Change.Id changeId = createChange().getChange().getId();
|
Change.Id changeId = retrieveChangeId();
|
||||||
QuotaRequestContext ctx =
|
QuotaRequestContext ctx =
|
||||||
QuotaRequestContext.builder()
|
QuotaRequestContext.builder()
|
||||||
.user(identifiedAdmin)
|
.user(identifiedAdmin)
|
||||||
@@ -125,6 +131,58 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
|
|||||||
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
|
.isEqualTo(singletonAggregation(QuotaResponse.ok()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availableTokensForUserAndAccount() {
|
||||||
|
QuotaRequestContext ctx =
|
||||||
|
QuotaRequestContext.builder().user(identifiedAdmin).account(user.id()).build();
|
||||||
|
QuotaResponse r = QuotaResponse.ok(10L);
|
||||||
|
expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
|
||||||
|
replay(quotaEnforcer);
|
||||||
|
assertThat(quotaBackend.user(identifiedAdmin).account(user.id()).availableTokens("testGroup"))
|
||||||
|
.isEqualTo(singletonAggregation(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availableTokensForUserAndProject() {
|
||||||
|
QuotaRequestContext ctx =
|
||||||
|
QuotaRequestContext.builder().user(identifiedAdmin).project(project).build();
|
||||||
|
QuotaResponse r = QuotaResponse.ok(10L);
|
||||||
|
expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
|
||||||
|
replay(quotaEnforcer);
|
||||||
|
assertThat(quotaBackend.user(identifiedAdmin).project(project).availableTokens("testGroup"))
|
||||||
|
.isEqualTo(singletonAggregation(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availableTokensForUserAndChange() throws Exception {
|
||||||
|
Change.Id changeId = retrieveChangeId();
|
||||||
|
QuotaRequestContext ctx =
|
||||||
|
QuotaRequestContext.builder()
|
||||||
|
.user(identifiedAdmin)
|
||||||
|
.change(changeId)
|
||||||
|
.project(project)
|
||||||
|
.build();
|
||||||
|
QuotaResponse r = QuotaResponse.ok(10L);
|
||||||
|
expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
|
||||||
|
replay(quotaEnforcer);
|
||||||
|
assertThat(
|
||||||
|
quotaBackend
|
||||||
|
.user(identifiedAdmin)
|
||||||
|
.change(changeId, project)
|
||||||
|
.availableTokens("testGroup"))
|
||||||
|
.isEqualTo(singletonAggregation(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availableTokens() {
|
||||||
|
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
||||||
|
QuotaResponse r = QuotaResponse.ok(10L);
|
||||||
|
expect(quotaEnforcer.availableTokens("testGroup", ctx)).andReturn(r);
|
||||||
|
replay(quotaEnforcer);
|
||||||
|
assertThat(quotaBackend.user(identifiedAdmin).availableTokens("testGroup"))
|
||||||
|
.isEqualTo(singletonAggregation(r));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestTokenError() throws Exception {
|
public void requestTokenError() throws Exception {
|
||||||
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
||||||
@@ -138,6 +196,20 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
|
|||||||
assertThat(thrown).hasMessageThat().contains("failed");
|
assertThat(thrown).hasMessageThat().contains("failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availableTokensError() throws Exception {
|
||||||
|
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
||||||
|
expect(quotaEnforcer.availableTokens("testGroup", ctx))
|
||||||
|
.andReturn(QuotaResponse.error("failed"));
|
||||||
|
replay(quotaEnforcer);
|
||||||
|
QuotaResponse.Aggregated result =
|
||||||
|
quotaBackend.user(identifiedAdmin).availableTokens("testGroup");
|
||||||
|
assertThat(result).isEqualTo(singletonAggregation(QuotaResponse.error("failed")));
|
||||||
|
exception.expect(QuotaException.class);
|
||||||
|
exception.expectMessage("failed");
|
||||||
|
result.throwOnError();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestTokenPluginThrowsAndRethrows() {
|
public void requestTokenPluginThrowsAndRethrows() {
|
||||||
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
||||||
@@ -148,6 +220,23 @@ public class DefaultQuotaBackendIT extends AbstractDaemonTest {
|
|||||||
() -> quotaBackend.user(identifiedAdmin).requestToken("testGroup"));
|
() -> quotaBackend.user(identifiedAdmin).requestToken("testGroup"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availableTokensPluginThrowsAndRethrows() {
|
||||||
|
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
||||||
|
expect(quotaEnforcer.availableTokens("testGroup", ctx)).andThrow(new NullPointerException());
|
||||||
|
replay(quotaEnforcer);
|
||||||
|
|
||||||
|
exception.expect(NullPointerException.class);
|
||||||
|
quotaBackend.user(identifiedAdmin).availableTokens("testGroup");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Change.Id retrieveChangeId() throws Exception {
|
||||||
|
// use REST API so that repository size quota doesn't have to be stubbed
|
||||||
|
ChangeInfo changeInfo =
|
||||||
|
gApi.changes().create(new ChangeInput(project.get(), "master", "test")).get();
|
||||||
|
return Change.id(changeInfo._number);
|
||||||
|
}
|
||||||
|
|
||||||
private static QuotaResponse.Aggregated singletonAggregation(QuotaResponse response) {
|
private static QuotaResponse.Aggregated singletonAggregation(QuotaResponse response) {
|
||||||
return QuotaResponse.Aggregated.create(Collections.singleton(response));
|
return QuotaResponse.Aggregated.create(Collections.singleton(response));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.google.gerrit.server.quota.QuotaRequestContext;
|
|||||||
import com.google.gerrit.server.quota.QuotaResponse;
|
import com.google.gerrit.server.quota.QuotaResponse;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
|
import java.util.OptionalLong;
|
||||||
import org.easymock.EasyMock;
|
import org.easymock.EasyMock;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -123,4 +124,34 @@ public class MultipleQuotaPluginsIT extends AbstractDaemonTest {
|
|||||||
QuotaResponse.Aggregated.create(
|
QuotaResponse.Aggregated.create(
|
||||||
ImmutableList.of(QuotaResponse.error("fail"), QuotaResponse.noOp())));
|
ImmutableList.of(QuotaResponse.error("fail"), QuotaResponse.noOp())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void minimumAvailableTokens() {
|
||||||
|
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
||||||
|
expect(quotaEnforcerA.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.ok(20L));
|
||||||
|
expect(quotaEnforcerB.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.ok(10L));
|
||||||
|
|
||||||
|
replay(quotaEnforcerA);
|
||||||
|
replay(quotaEnforcerB);
|
||||||
|
|
||||||
|
OptionalLong tokens =
|
||||||
|
quotaBackend.user(identifiedAdmin).availableTokens("testGroup").availableTokens();
|
||||||
|
assertThat(tokens.isPresent()).isTrue();
|
||||||
|
assertThat(tokens.getAsLong()).isEqualTo(10L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ignoreNoOpForAvailableTokens() {
|
||||||
|
QuotaRequestContext ctx = QuotaRequestContext.builder().user(identifiedAdmin).build();
|
||||||
|
expect(quotaEnforcerA.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.noOp());
|
||||||
|
expect(quotaEnforcerB.availableTokens("testGroup", ctx)).andReturn(QuotaResponse.ok(20L));
|
||||||
|
|
||||||
|
replay(quotaEnforcerA);
|
||||||
|
replay(quotaEnforcerB);
|
||||||
|
|
||||||
|
OptionalLong tokens =
|
||||||
|
quotaBackend.user(identifiedAdmin).availableTokens("testGroup").availableTokens();
|
||||||
|
assertThat(tokens.isPresent()).isTrue();
|
||||||
|
assertThat(tokens.getAsLong()).isEqualTo(20L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (C) 2019 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.server.quota;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
import static com.google.gerrit.server.quota.QuotaGroupDefinitions.REPOSITORY_SIZE_GROUP;
|
||||||
|
import static com.google.gerrit.server.quota.QuotaResponse.ok;
|
||||||
|
import static org.easymock.EasyMock.anyLong;
|
||||||
|
import static org.easymock.EasyMock.eq;
|
||||||
|
import static org.easymock.EasyMock.expect;
|
||||||
|
import static org.easymock.EasyMock.replay;
|
||||||
|
import static org.easymock.EasyMock.resetToStrict;
|
||||||
|
import static org.easymock.EasyMock.verify;
|
||||||
|
|
||||||
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
|
import com.google.gerrit.acceptance.UseLocalDisk;
|
||||||
|
import com.google.gerrit.extensions.config.FactoryModule;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.quota.QuotaBackend;
|
||||||
|
import com.google.gerrit.server.quota.QuotaResponse;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.easymock.EasyMock;
|
||||||
|
import org.eclipse.jgit.api.errors.TooLargeObjectInPackException;
|
||||||
|
import org.eclipse.jgit.api.errors.TransportException;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@UseLocalDisk
|
||||||
|
public class RepositorySizeQuotaIT extends AbstractDaemonTest {
|
||||||
|
private static final QuotaBackend.WithResource quotaBackendWithResource =
|
||||||
|
EasyMock.createStrictMock(QuotaBackend.WithResource.class);
|
||||||
|
private static final QuotaBackend.WithUser quotaBackendWithUser =
|
||||||
|
EasyMock.createStrictMock(QuotaBackend.WithUser.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Module createModule() {
|
||||||
|
return new FactoryModule() {
|
||||||
|
@Override
|
||||||
|
public void configure() {
|
||||||
|
bind(QuotaBackend.class)
|
||||||
|
.toInstance(
|
||||||
|
new QuotaBackend() {
|
||||||
|
@Override
|
||||||
|
public WithUser currentUser() {
|
||||||
|
return quotaBackendWithUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WithUser user(CurrentUser user) {
|
||||||
|
return quotaBackendWithUser;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
resetToStrict(quotaBackendWithResource);
|
||||||
|
resetToStrict(quotaBackendWithUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pushWithAvailableTokens() throws Exception {
|
||||||
|
expect(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
|
||||||
|
.andReturn(singletonAggregation(ok(276L)))
|
||||||
|
.times(2);
|
||||||
|
expect(quotaBackendWithResource.requestTokens(eq(REPOSITORY_SIZE_GROUP), anyLong()))
|
||||||
|
.andReturn(singletonAggregation(ok()));
|
||||||
|
expect(quotaBackendWithUser.project(project)).andReturn(quotaBackendWithResource).anyTimes();
|
||||||
|
replay(quotaBackendWithResource);
|
||||||
|
replay(quotaBackendWithUser);
|
||||||
|
pushCommit();
|
||||||
|
verify(quotaBackendWithUser);
|
||||||
|
verify(quotaBackendWithResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pushWithNotSufficientTokens() throws Exception {
|
||||||
|
long availableTokens = 1L;
|
||||||
|
expect(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
|
||||||
|
.andReturn(singletonAggregation(ok(availableTokens)))
|
||||||
|
.anyTimes();
|
||||||
|
expect(quotaBackendWithUser.project(project)).andReturn(quotaBackendWithResource).anyTimes();
|
||||||
|
replay(quotaBackendWithResource);
|
||||||
|
replay(quotaBackendWithUser);
|
||||||
|
try {
|
||||||
|
pushCommit();
|
||||||
|
assertWithMessage("expected TooLargeObjectInPackException").fail();
|
||||||
|
} catch (TooLargeObjectInPackException e) {
|
||||||
|
String msg = e.getMessage();
|
||||||
|
assertThat(msg).contains("Object too large");
|
||||||
|
assertThat(msg)
|
||||||
|
.contains(String.format("Max object size limit is %d bytes.", availableTokens));
|
||||||
|
}
|
||||||
|
verify(quotaBackendWithUser);
|
||||||
|
verify(quotaBackendWithResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void errorGettingAvailableTokens() throws Exception {
|
||||||
|
String msg = "quota error";
|
||||||
|
expect(quotaBackendWithResource.availableTokens(REPOSITORY_SIZE_GROUP))
|
||||||
|
.andReturn(singletonAggregation(QuotaResponse.error(msg)))
|
||||||
|
.anyTimes();
|
||||||
|
expect(quotaBackendWithUser.project(project)).andReturn(quotaBackendWithResource).anyTimes();
|
||||||
|
replay(quotaBackendWithResource);
|
||||||
|
replay(quotaBackendWithUser);
|
||||||
|
try {
|
||||||
|
pushCommit();
|
||||||
|
assertWithMessage("expected TransportException").fail();
|
||||||
|
} catch (TransportException e) {
|
||||||
|
// TransportException has not much info about the cause
|
||||||
|
}
|
||||||
|
verify(quotaBackendWithUser);
|
||||||
|
verify(quotaBackendWithResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushCommit() throws Exception {
|
||||||
|
createCommitAndPush(testRepo, "refs/heads/master", "test 01", "file.test", "some content");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static QuotaResponse.Aggregated singletonAggregation(QuotaResponse response) {
|
||||||
|
return QuotaResponse.Aggregated.create(Collections.singleton(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import static org.easymock.EasyMock.reset;
|
|||||||
import static org.easymock.EasyMock.verify;
|
import static org.easymock.EasyMock.verify;
|
||||||
|
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInput;
|
import com.google.gerrit.extensions.common.ChangeInput;
|
||||||
import com.google.gerrit.extensions.config.FactoryModule;
|
import com.google.gerrit.extensions.config.FactoryModule;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
@@ -68,7 +69,7 @@ public class RestApiQuotaIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void changeDetail() throws Exception {
|
public void changeDetail() throws Exception {
|
||||||
Change.Id changeId = createChange().getChange().getId();
|
Change.Id changeId = retrieveChangeId();
|
||||||
expect(quotaBackendWithResource.requestToken("/restapi/changes/detail:GET"))
|
expect(quotaBackendWithResource.requestToken("/restapi/changes/detail:GET"))
|
||||||
.andReturn(singletonAggregation(QuotaResponse.ok()));
|
.andReturn(singletonAggregation(QuotaResponse.ok()));
|
||||||
replay(quotaBackendWithResource);
|
replay(quotaBackendWithResource);
|
||||||
@@ -81,7 +82,7 @@ public class RestApiQuotaIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void revisionDetail() throws Exception {
|
public void revisionDetail() throws Exception {
|
||||||
Change.Id changeId = createChange().getChange().getId();
|
Change.Id changeId = retrieveChangeId();
|
||||||
expect(quotaBackendWithResource.requestToken("/restapi/changes/revisions/actions:GET"))
|
expect(quotaBackendWithResource.requestToken("/restapi/changes/revisions/actions:GET"))
|
||||||
.andReturn(singletonAggregation(QuotaResponse.ok()));
|
.andReturn(singletonAggregation(QuotaResponse.ok()));
|
||||||
replay(quotaBackendWithResource);
|
replay(quotaBackendWithResource);
|
||||||
@@ -130,6 +131,13 @@ public class RestApiQuotaIT extends AbstractDaemonTest {
|
|||||||
adminRestSession.get("/config/server/version").assertStatus(429);
|
adminRestSession.get("/config/server/version").assertStatus(429);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Change.Id retrieveChangeId() throws Exception {
|
||||||
|
// use REST API so that repository size quota doesn't have to be stubbed
|
||||||
|
ChangeInfo changeInfo =
|
||||||
|
gApi.changes().create(new ChangeInput(project.get(), "master", "test")).get();
|
||||||
|
return Change.id(changeInfo._number);
|
||||||
|
}
|
||||||
|
|
||||||
private static QuotaResponse.Aggregated singletonAggregation(QuotaResponse response) {
|
private static QuotaResponse.Aggregated singletonAggregation(QuotaResponse response) {
|
||||||
return QuotaResponse.Aggregated.create(Collections.singleton(response));
|
return QuotaResponse.Aggregated.create(Collections.singleton(response));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user