Merge "New option to schedule the Git garbage collection as a background task"
This commit is contained in:
commit
dc2da0d4d5
@ -844,6 +844,34 @@ The response is the streamed output of the garbage collection.
|
|||||||
done.
|
done.
|
||||||
----
|
----
|
||||||
|
|
||||||
|
==== Asynchronous Execution
|
||||||
|
|
||||||
|
The option `async` allows to schedule a background task that asynchronously
|
||||||
|
executes a Git garbage collection.
|
||||||
|
|
||||||
|
The `Location` header of the response refers to the link:rest-api-config.html#get-task[background task]
|
||||||
|
which allows to inspect the progress of its execution. In case of asynchronous
|
||||||
|
execution the `show_progress` option is ignored.
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
POST /projects/plugins%2Freplication/gc HTTP/1.0
|
||||||
|
Content-Type: application/json;charset=UTF-8
|
||||||
|
|
||||||
|
{
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
The response is empty.
|
||||||
|
|
||||||
|
.Response
|
||||||
|
----
|
||||||
|
HTTP/1.1 202 Accepted
|
||||||
|
Content-Disposition: attachment
|
||||||
|
Location: https:<host>/a/config/server/tasks/383a0602
|
||||||
|
----
|
||||||
|
|
||||||
[[ban-commit]]
|
[[ban-commit]]
|
||||||
=== Ban Commit
|
=== Ban Commit
|
||||||
--
|
--
|
||||||
@ -2212,6 +2240,8 @@ collection.
|
|||||||
Whether progress information should be shown.
|
Whether progress information should be shown.
|
||||||
|`aggressive` |`false` if not set|
|
|`aggressive` |`false` if not set|
|
||||||
Whether an aggressive garbage collection should be done.
|
Whether an aggressive garbage collection should be done.
|
||||||
|
|`async` |`false` if not set|
|
||||||
|
Whether the garbage collection should run asynchronously.
|
||||||
|=============================
|
|=============================
|
||||||
|
|
||||||
[[head-input]]
|
[[head-input]]
|
||||||
|
@ -36,6 +36,11 @@ public abstract class Response<T> {
|
|||||||
return new Impl<>(201, value);
|
return new Impl<>(201, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** HTTP 202 Accepted: accepted as background task. */
|
||||||
|
public static Accepted accepted(String location) {
|
||||||
|
return new Accepted(location);
|
||||||
|
}
|
||||||
|
|
||||||
/** HTTP 204 No Content: typically used when the resource is deleted. */
|
/** HTTP 204 No Content: typically used when the resource is deleted. */
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> Response<T> none() {
|
public static <T> Response<T> none() {
|
||||||
@ -168,4 +173,33 @@ public abstract class Response<T> {
|
|||||||
return String.format("[302 Redirect] %s", location);
|
return String.format("[302 Redirect] %s", location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Accepted as task for asynchronous execution. */
|
||||||
|
public static final class Accepted {
|
||||||
|
private final String location;
|
||||||
|
|
||||||
|
private Accepted(String url) {
|
||||||
|
this.location = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String location() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return location.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof Accepted
|
||||||
|
&& ((Accepted) o).location.equals(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("[202 Accepted] %s", location);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
import static java.math.RoundingMode.CEILING;
|
import static java.math.RoundingMode.CEILING;
|
||||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
|
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
|
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
|
||||||
@ -347,6 +348,11 @@ public class RestApiServlet extends HttpServlet {
|
|||||||
CacheHeaders.setNotCacheable(res);
|
CacheHeaders.setNotCacheable(res);
|
||||||
res.sendRedirect(((Response.Redirect) result).location());
|
res.sendRedirect(((Response.Redirect) result).location());
|
||||||
return;
|
return;
|
||||||
|
} else if (result instanceof Response.Accepted) {
|
||||||
|
CacheHeaders.setNotCacheable(res);
|
||||||
|
res.setStatus(SC_ACCEPTED);
|
||||||
|
res.setHeader(HttpHeaders.LOCATION, ((Response.Accepted)result).location());
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
CacheHeaders.setNotCacheable(res);
|
CacheHeaders.setNotCacheable(res);
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,19 @@ import com.google.gerrit.common.data.GarbageCollectionResult;
|
|||||||
import com.google.gerrit.common.data.GlobalCapability;
|
import com.google.gerrit.common.data.GlobalCapability;
|
||||||
import com.google.gerrit.extensions.annotations.RequiresCapability;
|
import com.google.gerrit.extensions.annotations.RequiresCapability;
|
||||||
import com.google.gerrit.extensions.restapi.BinaryResult;
|
import com.google.gerrit.extensions.restapi.BinaryResult;
|
||||||
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
import com.google.gerrit.extensions.webui.UiAction;
|
import com.google.gerrit.extensions.webui.UiAction;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||||
import com.google.gerrit.server.git.GarbageCollection;
|
import com.google.gerrit.server.git.GarbageCollection;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
|
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
|
||||||
|
import com.google.gerrit.server.git.WorkQueue;
|
||||||
import com.google.gerrit.server.project.GarbageCollect.Input;
|
import com.google.gerrit.server.project.GarbageCollect.Input;
|
||||||
|
import com.google.gerrit.server.util.IdGenerator;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -42,22 +48,61 @@ public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
|
|||||||
public static class Input {
|
public static class Input {
|
||||||
public boolean showProgress;
|
public boolean showProgress;
|
||||||
public boolean aggressive;
|
public boolean aggressive;
|
||||||
|
public boolean async;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final boolean canGC;
|
private final boolean canGC;
|
||||||
private GarbageCollection.Factory garbageCollectionFactory;
|
private final GarbageCollection.Factory garbageCollectionFactory;
|
||||||
|
private final WorkQueue workQueue;
|
||||||
|
private final Provider<String> canonicalUrl;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GarbageCollect(
|
GarbageCollect(GitRepositoryManager repoManager,
|
||||||
GitRepositoryManager repoManager,
|
GarbageCollection.Factory garbageCollectionFactory, WorkQueue workQueue,
|
||||||
GarbageCollection.Factory garbageCollectionFactory) {
|
@CanonicalWebUrl Provider<String> canonicalUrl) {
|
||||||
|
this.workQueue = workQueue;
|
||||||
|
this.canonicalUrl = canonicalUrl;
|
||||||
this.canGC = repoManager instanceof LocalDiskRepositoryManager;
|
this.canGC = repoManager instanceof LocalDiskRepositoryManager;
|
||||||
this.garbageCollectionFactory = garbageCollectionFactory;
|
this.garbageCollectionFactory = garbageCollectionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
@Override
|
@Override
|
||||||
public BinaryResult apply(final ProjectResource rsrc, final Input input) {
|
public Object apply(ProjectResource rsrc, Input input) {
|
||||||
|
Project.NameKey project = rsrc.getNameKey();
|
||||||
|
if (input.async) {
|
||||||
|
return applyAsync(project, input);
|
||||||
|
} else {
|
||||||
|
return applySync(project, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response.Accepted applyAsync(final Project.NameKey project, final Input input) {
|
||||||
|
Runnable job = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
runGC(project, input, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Run " + (input.aggressive ? "aggressive " : "")
|
||||||
|
+ "garbage collection on project " + project.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
WorkQueue.Task<Void> task =
|
||||||
|
(WorkQueue.Task<Void>) workQueue.getDefaultQueue().submit(job);
|
||||||
|
|
||||||
|
String location = canonicalUrl.get() + "a/config/server/tasks/"
|
||||||
|
+ IdGenerator.format(task.getTaskId());
|
||||||
|
|
||||||
|
return Response.accepted(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
private BinaryResult applySync(final Project.NameKey project,
|
||||||
|
final Input input) {
|
||||||
return new BinaryResult() {
|
return new BinaryResult() {
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(OutputStream out) throws IOException {
|
public void writeTo(OutputStream out) throws IOException {
|
||||||
@ -69,10 +114,8 @@ public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
GarbageCollectionResult result =
|
PrintWriter progressWriter = input.showProgress ? writer : null;
|
||||||
garbageCollectionFactory.create().run(
|
GarbageCollectionResult result = runGC(project, input, progressWriter);
|
||||||
Collections.singletonList(rsrc.getNameKey()), input.aggressive,
|
|
||||||
input.showProgress ? writer : null);
|
|
||||||
String msg = "Garbage collection completed successfully.";
|
String msg = "Garbage collection completed successfully.";
|
||||||
if (result.hasErrors()) {
|
if (result.hasErrors()) {
|
||||||
for (GarbageCollectionResult.Error e : result.getErrors()) {
|
for (GarbageCollectionResult.Error e : result.getErrors()) {
|
||||||
@ -104,6 +147,13 @@ public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
|
|||||||
.disableGzip();
|
.disableGzip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GarbageCollectionResult runGC(Project.NameKey project,
|
||||||
|
Input input, PrintWriter progressWriter) {
|
||||||
|
return garbageCollectionFactory.create().run(
|
||||||
|
Collections.singletonList(project), input.aggressive,
|
||||||
|
progressWriter);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UiAction.Description getDescription(ProjectResource rsrc) {
|
public UiAction.Description getDescription(ProjectResource rsrc) {
|
||||||
return new UiAction.Description()
|
return new UiAction.Description()
|
||||||
|
Loading…
Reference in New Issue
Block a user