Introduce repository size quota enforcer
This patch introduces "/repository:size" quota group that gets examined when commits get pushed to the repository. It moves the responsibility of quota check to Gerrit core so that implementation details don't have to be exploited by quota plugin. Pros: * simplified implementation of quota plugin (only QuotaEnforcer needs to be implemented) * better compatibility in long term (quota plugin doesn't have to recognise all the places when it is necessary to hook into in order to deliver repository size quota functionality) Change-Id: I481bec083be7f5b28bf69b9c8de119e74b32a095 Signed-off-by: Jacek Centkowski <jcentkowski@collab.net>
This commit is contained in:
committed by
David Pursehouse
parent
adce023ba9
commit
b8f7400d74
@@ -14,6 +14,10 @@
|
||||
|
||||
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.gerrit.acceptance.InProcessProtocol.Context;
|
||||
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.project.ProjectCache;
|
||||
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.RequestScopePropagator;
|
||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
||||
@@ -263,6 +270,7 @@ class InProcessProtocol extends TestProtocol<Context> {
|
||||
private final DynamicSet<PostReceiveHook> postReceiveHooks;
|
||||
private final ThreadLocalRequestContext threadContext;
|
||||
private final PermissionBackend permissionBackend;
|
||||
private final QuotaBackend quotaBackend;
|
||||
|
||||
@Inject
|
||||
Receive(
|
||||
@@ -273,7 +281,8 @@ class InProcessProtocol extends TestProtocol<Context> {
|
||||
PluginSetContext<ReceivePackInitializer> receivePackInitializers,
|
||||
DynamicSet<PostReceiveHook> postReceiveHooks,
|
||||
ThreadLocalRequestContext threadContext,
|
||||
PermissionBackend permissionBackend) {
|
||||
PermissionBackend permissionBackend,
|
||||
QuotaBackend quotaBackend) {
|
||||
this.userProvider = userProvider;
|
||||
this.projectCache = projectCache;
|
||||
this.factory = factory;
|
||||
@@ -282,6 +291,7 @@ class InProcessProtocol extends TestProtocol<Context> {
|
||||
this.postReceiveHooks = postReceiveHooks;
|
||||
this.threadContext = threadContext;
|
||||
this.permissionBackend = permissionBackend;
|
||||
this.quotaBackend = quotaBackend;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -321,10 +331,35 @@ class InProcessProtocol extends TestProtocol<Context> {
|
||||
|
||||
receivePackInitializers.runEach(
|
||||
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;
|
||||
} catch (IOException | PermissionBackendException e) {
|
||||
} catch (IOException | PermissionBackendException | QuotaException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
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 com.google.common.flogger.FluentLogger;
|
||||
@@ -46,6 +47,9 @@ import com.google.gerrit.server.permissions.ProjectPermission;
|
||||
import com.google.gerrit.server.project.ContributorAgreementsChecker;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
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.RequestScopePropagator;
|
||||
import com.google.inject.Inject;
|
||||
@@ -96,6 +100,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
||||
public static class Module extends PrivateModule {
|
||||
@Override
|
||||
public void configure() {
|
||||
install(new FactoryModuleBuilder().build(LazyPostReceiveHookChain.Factory.class));
|
||||
install(new FactoryModuleBuilder().build(AsyncReceiveCommits.Factory.class));
|
||||
expose(AsyncReceiveCommits.Factory.class);
|
||||
// Don't expose the binding for ReceiveCommits.Factory. All callers should
|
||||
@@ -253,9 +258,10 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
||||
RequestScopePropagator scopePropagator,
|
||||
ReceiveConfig receiveConfig,
|
||||
TransferConfig transferConfig,
|
||||
Provider<LazyPostReceiveHookChain> lazyPostReceive,
|
||||
LazyPostReceiveHookChain.Factory lazyPostReceive,
|
||||
ContributorAgreementsChecker contributorAgreements,
|
||||
Metrics metrics,
|
||||
QuotaBackend quotaBackend,
|
||||
@Named(TIMEOUT_NAME) long timeoutMillis,
|
||||
@Assisted ProjectState projectState,
|
||||
@Assisted IdentifiedUser user,
|
||||
@@ -284,7 +290,7 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
||||
receivePack.setRefFilter(new ReceiveRefFilter());
|
||||
receivePack.setAllowPushOptions(true);
|
||||
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.
|
||||
// Check objects mentioned inside the incoming pack file are reachable from visible refs.
|
||||
@@ -311,6 +317,17 @@ public class AsyncReceiveCommits implements PreReceiveHook {
|
||||
factory.create(
|
||||
projectState, user, receivePack, allRefsWatcher, messageSender, resultChangeIds);
|
||||
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. */
|
||||
|
||||
@@ -14,23 +14,78 @@
|
||||
|
||||
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.quota.QuotaBackend;
|
||||
import com.google.gerrit.server.quota.QuotaResponse;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import java.util.Collection;
|
||||
import org.eclipse.jgit.transport.PostReceiveHook;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
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 QuotaBackend quotaBackend;
|
||||
private final CurrentUser user;
|
||||
private final Project.NameKey project;
|
||||
|
||||
@Inject
|
||||
LazyPostReceiveHookChain(PluginSetContext<PostReceiveHook> hooks) {
|
||||
LazyPostReceiveHookChain(
|
||||
PluginSetContext<PostReceiveHook> hooks,
|
||||
QuotaBackend quotaBackend,
|
||||
@Assisted CurrentUser user,
|
||||
@Assisted Project.NameKey project) {
|
||||
this.hooks = hooks;
|
||||
this.quotaBackend = quotaBackend;
|
||||
this.user = user;
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
Reference in New Issue
Block a user