Support triggering GC for one project via REST
The Git garbage collection for one project can now be triggered by POST on '/projects/*/gc'. Change-Id: I6d45f33196f3f356648c817d9a2d8465de49bb5c Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
parent
19ea9b9733
commit
ef3542fb35
@ -408,6 +408,45 @@ link:#repository-statistics-info[RepositoryStatisticsInfo] entity.
|
||||
}
|
||||
----
|
||||
|
||||
[[run-gc]]
|
||||
Run GC
|
||||
~~~~~~
|
||||
[verse]
|
||||
'POST /projects/link:#project-name[\{project-name\}]/gc'
|
||||
|
||||
Run the Git garbage collection for the repository of a project.
|
||||
|
||||
.Request
|
||||
----
|
||||
POST /projects/plugins%2Freplication/gc HTTP/1.0
|
||||
----
|
||||
|
||||
The response is the streamed output of the garbage collection.
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: text/plain;charset=UTF-8
|
||||
|
||||
collecting garbage for "plugins/replication":
|
||||
Pack refs: 100% (21/21)
|
||||
Counting objects: 20
|
||||
Finding sources: 100% (20/20)
|
||||
Getting sizes: 100% (13/13)
|
||||
Compressing objects: 83% (5/6)
|
||||
Writing objects: 100% (20/20)
|
||||
Selecting commits: 100% (7/7)
|
||||
Building bitmaps: 100% (7/7)
|
||||
Finding sources: 100% (41/41)
|
||||
Getting sizes: 100% (25/25)
|
||||
Compressing objects: 52% (12/23)
|
||||
Writing objects: 100% (41/41)
|
||||
Prune loose objects also found in pack files: 100% (36/36)
|
||||
Prune loose, unreferenced objects: 100% (36/36)
|
||||
done.
|
||||
----
|
||||
|
||||
[[dashboard-endpoints]]
|
||||
Dashboard Endpoints
|
||||
-------------------
|
||||
|
@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2013 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;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GcAssert {
|
||||
|
||||
private final GitRepositoryManager repoManager;
|
||||
|
||||
@Inject
|
||||
public GcAssert(GitRepositoryManager repoManager) {
|
||||
this.repoManager = repoManager;
|
||||
}
|
||||
|
||||
public void assertHasPackFile(Project.NameKey... projects)
|
||||
throws RepositoryNotFoundException, IOException {
|
||||
for (Project.NameKey p : projects) {
|
||||
assertTrue("Project " + p.get() + " has no pack files.",
|
||||
getPackFiles(p).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void assertHasNoPackFile(Project.NameKey... projects)
|
||||
throws RepositoryNotFoundException, IOException {
|
||||
for (Project.NameKey p : projects) {
|
||||
assertTrue("Project " + p.get() + " has pack files.",
|
||||
getPackFiles(p).length == 0);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getPackFiles(Project.NameKey p)
|
||||
throws RepositoryNotFoundException, IOException {
|
||||
Repository repo = repoManager.openRepository(p);
|
||||
try {
|
||||
File packDir = new File(repo.getDirectory(), "objects/pack");
|
||||
return packDir.list(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.endsWith(".pack");
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
repo.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -56,6 +56,10 @@ public class RestSession {
|
||||
return new RestResponse(getClient().execute(put));
|
||||
}
|
||||
|
||||
public RestResponse post(String endPoint) throws IOException {
|
||||
return post(endPoint, null);
|
||||
}
|
||||
|
||||
public RestResponse post(String endPoint, Object content) throws IOException {
|
||||
HttpPost post = new HttpPost("http://localhost:8080/a" + endPoint);
|
||||
if (content != null) {
|
||||
|
@ -0,0 +1,102 @@
|
||||
// Copyright (C) 2013 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.rest.project;
|
||||
|
||||
import static com.google.gerrit.acceptance.git.GitUtil.createProject;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.AccountCreator;
|
||||
import com.google.gerrit.acceptance.GcAssert;
|
||||
import com.google.gerrit.acceptance.RestResponse;
|
||||
import com.google.gerrit.acceptance.RestSession;
|
||||
import com.google.gerrit.acceptance.SshSession;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.config.AllProjectsName;
|
||||
import com.google.gerrit.server.git.GarbageCollectionQueue;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.jcraft.jsch.JSchException;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GarbageCollectionIT extends AbstractDaemonTest {
|
||||
|
||||
@Inject
|
||||
private AccountCreator accounts;
|
||||
|
||||
@Inject
|
||||
private AllProjectsName allProjects;
|
||||
|
||||
@Inject
|
||||
private GarbageCollectionQueue gcQueue;
|
||||
|
||||
@Inject
|
||||
private GcAssert gcAssert;
|
||||
|
||||
private TestAccount admin;
|
||||
private RestSession session;
|
||||
private Project.NameKey project1;
|
||||
private Project.NameKey project2;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
admin =
|
||||
accounts.create("admin", "admin@example.com", "Administrator",
|
||||
"Administrators");
|
||||
|
||||
SshSession sshSession = new SshSession(admin);
|
||||
|
||||
project1 = new Project.NameKey("p1");
|
||||
createProject(sshSession, project1.get());
|
||||
|
||||
project2 = new Project.NameKey("p2");
|
||||
createProject(sshSession, project2.get());
|
||||
|
||||
session = new RestSession(admin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGcNonExistingProject_NotFound() throws IOException {
|
||||
assertEquals(HttpStatus.SC_NOT_FOUND, POST("/projects/non-existing/gc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGcNotAllowed_Forbidden() throws IOException, OrmException, JSchException {
|
||||
assertEquals(HttpStatus.SC_FORBIDDEN,
|
||||
new RestSession(accounts.create("user", "user@example.com", "User"))
|
||||
.post("/projects/" + allProjects.get() + "/gc").getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGcOneProject() throws JSchException, IOException {
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, POST("/projects/" + allProjects.get() + "/gc"));
|
||||
gcAssert.assertHasPackFile(allProjects);
|
||||
gcAssert.assertHasNoPackFile(project1, project2);
|
||||
}
|
||||
|
||||
private int POST(String endPoint) throws IOException {
|
||||
RestResponse r = session.post(endPoint);
|
||||
r.consume();
|
||||
return r.getStatusCode();
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.AccountCreator;
|
||||
import com.google.gerrit.acceptance.GcAssert;
|
||||
import com.google.gerrit.acceptance.SshSession;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.common.data.GarbageCollectionResult;
|
||||
@ -28,19 +29,14 @@ import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.config.AllProjectsName;
|
||||
import com.google.gerrit.server.git.GarbageCollection;
|
||||
import com.google.gerrit.server.git.GarbageCollectionQueue;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.jcraft.jsch.JSchException;
|
||||
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
@ -50,9 +46,6 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
|
||||
@Inject
|
||||
private AccountCreator accounts;
|
||||
|
||||
@Inject
|
||||
private GitRepositoryManager repoManager;
|
||||
|
||||
@Inject
|
||||
private AllProjectsName allProjects;
|
||||
|
||||
@ -62,6 +55,9 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
|
||||
@Inject
|
||||
private GarbageCollectionQueue gcQueue;
|
||||
|
||||
@Inject
|
||||
private GcAssert gcAssert;
|
||||
|
||||
private TestAccount admin;
|
||||
private SshSession sshSession;
|
||||
private Project.NameKey project1;
|
||||
@ -93,8 +89,8 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
|
||||
+ project2.get() + "\"");
|
||||
assertFalse(sshSession.hasError());
|
||||
assertNoError(response);
|
||||
assertHasPackFile(project1, project2);
|
||||
assertHasNoPackFile(allProjects, project3);
|
||||
gcAssert.assertHasPackFile(project1, project2);
|
||||
gcAssert.assertHasNoPackFile(allProjects, project3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -102,7 +98,7 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
|
||||
String response = sshSession.exec("gerrit gc --all");
|
||||
assertFalse(sshSession.hasError());
|
||||
assertNoError(response);
|
||||
assertHasPackFile(allProjects, project1, project2, project3);
|
||||
gcAssert.assertHasPackFile(allProjects, project1, project2, project3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -132,36 +128,4 @@ public class GarbageCollectionIT extends AbstractDaemonTest {
|
||||
private void assertNoError(String response) {
|
||||
assertFalse(response, response.toLowerCase(Locale.US).contains("error"));
|
||||
}
|
||||
|
||||
private void assertHasPackFile(Project.NameKey... projects)
|
||||
throws RepositoryNotFoundException, IOException {
|
||||
for (Project.NameKey p : projects) {
|
||||
assertTrue("Project " + p.get() + "has no pack files.",
|
||||
getPackFiles(p).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertHasNoPackFile(Project.NameKey... projects)
|
||||
throws RepositoryNotFoundException, IOException {
|
||||
for (Project.NameKey p : projects) {
|
||||
assertTrue("Project " + p.get() + "has pack files.",
|
||||
getPackFiles(p).length == 0);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getPackFiles(Project.NameKey p)
|
||||
throws RepositoryNotFoundException, IOException {
|
||||
Repository repo = repoManager.openRepository(p);
|
||||
try {
|
||||
File packDir = new File(repo.getDirectory(), "objects/pack");
|
||||
return packDir.list(new FilenameFilter() {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.endsWith(".pack");
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
repo.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2013 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.extensions.restapi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public interface StreamingResponse {
|
||||
public String getContentType();
|
||||
public void stream(OutputStream out) throws IOException;
|
||||
}
|
@ -61,6 +61,7 @@ import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||
import com.google.gerrit.extensions.restapi.RestResource;
|
||||
import com.google.gerrit.extensions.restapi.RestView;
|
||||
import com.google.gerrit.extensions.restapi.StreamingResponse;
|
||||
import com.google.gerrit.extensions.restapi.TopLevelResource;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.httpd.WebSession;
|
||||
@ -301,7 +302,11 @@ public class RestApiServlet extends HttpServlet {
|
||||
}
|
||||
res.setStatus(status);
|
||||
|
||||
if (result != Response.none()) {
|
||||
if (result instanceof StreamingResponse) {
|
||||
StreamingResponse r = (StreamingResponse) result;
|
||||
res.setContentType(r.getContentType());
|
||||
r.stream(res.getOutputStream());
|
||||
} else if (result != Response.none()) {
|
||||
result = Response.unwrap(result);
|
||||
if (result instanceof BinaryResult) {
|
||||
replyBinaryResult(req, res, (BinaryResult) result);
|
||||
|
@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2013 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.project;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.gerrit.common.data.GarbageCollectionResult;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.extensions.annotations.RequiresCapability;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.extensions.restapi.StreamingResponse;
|
||||
import com.google.gerrit.server.git.GarbageCollection;
|
||||
import com.google.gerrit.server.project.GarbageCollect.Input;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
|
||||
@RequiresCapability(GlobalCapability.RUN_GC)
|
||||
public class GarbageCollect implements RestModifyView<ProjectResource, Input> {
|
||||
public static class Input {
|
||||
}
|
||||
|
||||
private GarbageCollection.Factory garbageCollectionFactory;
|
||||
|
||||
@Inject
|
||||
GarbageCollect(GarbageCollection.Factory garbageCollectionFactory) {
|
||||
this.garbageCollectionFactory = garbageCollectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamingResponse apply(final ProjectResource rsrc, Input input) {
|
||||
return new StreamingResponse() {
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "text/plain;charset=UTF-8";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stream(OutputStream out) throws IOException {
|
||||
PrintWriter writer = new PrintWriter(
|
||||
new OutputStreamWriter(out, Charsets.UTF_8)) {
|
||||
@Override
|
||||
public void println() {
|
||||
write('\n');
|
||||
}
|
||||
};
|
||||
try {
|
||||
GarbageCollectionResult result = garbageCollectionFactory.create().run(
|
||||
Collections.singletonList(rsrc.getNameKey()), writer);
|
||||
if (result.hasErrors()) {
|
||||
for (GarbageCollectionResult.Error e : result.getErrors()) {
|
||||
String msg;
|
||||
switch (e.getType()) {
|
||||
case REPOSITORY_NOT_FOUND:
|
||||
msg = "error: project \"" + e.getProjectName() + "\" not found";
|
||||
break;
|
||||
case GC_ALREADY_SCHEDULED:
|
||||
msg = "error: garbage collection for project \""
|
||||
+ e.getProjectName() + "\" was already scheduled";
|
||||
break;
|
||||
case GC_FAILED:
|
||||
msg = "error: garbage collection for project \"" + e.getProjectName()
|
||||
+ "\" failed";
|
||||
break;
|
||||
default:
|
||||
msg = "error: garbage collection for project \"" + e.getProjectName()
|
||||
+ "\" failed: " + e.getType();
|
||||
}
|
||||
writer.println(msg);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -44,6 +44,7 @@ public class Module extends RestApiModule {
|
||||
put(PROJECT_KIND, "HEAD").to(SetHead.class);
|
||||
|
||||
get(PROJECT_KIND, "statistics.git").to(GetStatistics.class);
|
||||
post(PROJECT_KIND, "gc").to(GarbageCollect.class);
|
||||
|
||||
child(PROJECT_KIND, "dashboards").to(DashboardsCollection.class);
|
||||
get(DASHBOARD_KIND).to(GetDashboard.class);
|
||||
|
Loading…
Reference in New Issue
Block a user