diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt index b077b67aae..3639551840 100644 --- a/Documentation/rest-api-config.txt +++ b/Documentation/rest-api-config.txt @@ -302,6 +302,34 @@ As result a link:#cache-info[CacheInfo] entity is returned. } ---- +[[flush-cache]] +=== Flush Cache +-- +'POST /config/server/caches/link:#cache-name[\{cache-name\}]/flush' +-- + +Flushes a cache. + +The caller must be a member of a group that is granted the +link:access-control.html#capability_flushCaches[Flush Caches] capability +or the link:access-control.html#capability_administrateServer[ +Administrate Server] capability. + +The "web_sessions" cache can only be flushed if the caller is member of +a group that is granted the +link:access-control.html#capability_administrateServer[Administrate +Server] capability. + +.Request +---- + POST /config/server/caches/projects/flush HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK +---- + [[list-capabilities]] === List Capabilities -- diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java new file mode 100644 index 0000000000..aa8d7ba5b0 --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java @@ -0,0 +1,116 @@ +// Copyright (C) 2014 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.config; + +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; +import static com.google.gerrit.server.project.Util.allow; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.RestResponse; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gerrit.server.config.ListCaches.CacheInfo; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; +import com.google.gerrit.server.group.SystemGroupBackend; +import com.google.gerrit.server.project.ProjectCache; +import com.google.inject.Inject; + +import org.apache.http.HttpStatus; +import org.junit.Test; + +import java.io.IOException; + +public class FlushCacheIT extends AbstractDaemonTest { + + @Inject + private ProjectCache projectCache; + + @Inject + private AllProjectsName allProjects; + + @Inject + private MetaDataUpdate.Server metaDataUpdateFactory; + + @Test + public void flushCache() throws IOException { + RestResponse r = adminSession.get("/config/server/caches/groups"); + CacheInfo result = newGson().fromJson(r.getReader(), CacheInfo.class); + assertTrue(result.entries.mem.longValue() > 0); + + r = adminSession.post("/config/server/caches/groups/flush"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + r.consume(); + + r = adminSession.get("/config/server/caches/groups"); + result = newGson().fromJson(r.getReader(), CacheInfo.class); + assertNull(result.entries.mem); + } + + @Test + public void flushCache_Forbidden() throws IOException { + RestResponse r = userSession.post("/config/server/caches/accounts/flush"); + assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode()); + } + + @Test + public void flushCache_NotFound() throws IOException { + RestResponse r = adminSession.post("/config/server/caches/nonExisting/flush"); + assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode()); + } + + @Test + public void flushCacheWithGerritPrefix() throws IOException { + RestResponse r = adminSession.post("/config/server/caches/gerrit-accounts/flush"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + } + + @Test + public void flushWebSessionsCache() throws IOException { + RestResponse r = adminSession.post("/config/server/caches/web_sessions/flush"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + } + + @Test + public void flushWebSessionsCache_Forbidden() throws IOException { + ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig(); + AccountGroup.UUID registeredUsers = + SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID(); + allow(cfg, GlobalCapability.VIEW_CACHES, registeredUsers); + allow(cfg, GlobalCapability.FLUSH_CACHES, registeredUsers); + saveProjectConfig(cfg); + + RestResponse r = userSession.post("/config/server/caches/accounts/flush"); + assertEquals(HttpStatus.SC_OK, r.getStatusCode()); + r.consume(); + + r = userSession.post("/config/server/caches/web_sessions/flush"); + assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode()); + } + + private void saveProjectConfig(ProjectConfig cfg) throws IOException { + MetaDataUpdate md = metaDataUpdateFactory.create(allProjects); + try { + cfg.commit(md); + } finally { + md.close(); + } + projectCache.evict(allProjects); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java new file mode 100644 index 0000000000..076be5d44a --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java @@ -0,0 +1,55 @@ +// Copyright (C) 2014 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.config; + +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.extensions.annotations.RequiresCapability; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.Response; +import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.config.FlushCache.Input; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +@RequiresCapability(GlobalCapability.FLUSH_CACHES) +@Singleton +public class FlushCache implements RestModifyView { + public static class Input { + } + + private static final String WEB_SESSIONS = "web_sessions"; + + private final Provider self; + + @Inject + public FlushCache(Provider self) { + this.self = self; + } + + @Override + public Response apply(CacheResource rsrc, Input input) + throws AuthException { + if (WEB_SESSIONS.equals(rsrc.getName()) + && !self.get().getCapabilities().canAdministrateServer()) { + throw new AuthException(String.format( + "only site administrators can flush %s", WEB_SESSIONS)); + } + + rsrc.getCache().invalidateAll(); + return Response.ok(""); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/RestCacheAdminModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/RestCacheAdminModule.java index cb815e7bbb..ba2da83b04 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/RestCacheAdminModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/RestCacheAdminModule.java @@ -27,5 +27,6 @@ public class RestCacheAdminModule extends RestApiModule { DynamicMap.mapOf(binder(), CACHE_KIND); child(CONFIG_KIND, "caches").to(CachesCollection.class); get(CACHE_KIND).to(GetCache.class); + post(CACHE_KIND, "flush").to(FlushCache.class); } } diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java index 3a7f09dbab..c0f35ba1c9 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java @@ -20,6 +20,7 @@ import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; +import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.common.data.LabelValue; @@ -125,6 +126,24 @@ public class Util { return grant(project, permissionName, newRule(project, group), ref); } + public static PermissionRule allow(ProjectConfig project, + String capabilityName, AccountGroup.UUID group) { + PermissionRule rule = newRule(project, group); + project.getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true) + .getPermission(capabilityName, true) + .add(rule); + return rule; + } + + public static PermissionRule block(ProjectConfig project, + String capabilityName, AccountGroup.UUID group) { + PermissionRule rule = newRule(project, group); + project.getAccessSection(AccessSection.GLOBAL_CAPABILITIES, true) + .getPermission(capabilityName, true) + .add(rule); + return rule; + } + public static PermissionRule block(ProjectConfig project, String permissionName, AccountGroup.UUID group, String ref) { PermissionRule r = grant(project, permissionName, newRule(project, group), ref); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java index 1de463626c..18f7046174 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java @@ -20,8 +20,9 @@ import com.google.common.cache.Cache; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.registration.DynamicMap; -import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.sshd.BaseCommand; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.server.config.CacheResource; +import com.google.gerrit.server.config.FlushCache; import com.google.gerrit.sshd.CommandMetaData; import com.google.inject.Inject; @@ -48,18 +49,10 @@ final class FlushCaches extends CacheCommand { private boolean list; @Inject - IdentifiedUser currentUser; + private FlushCache flushCache; @Override protected void run() throws Failure { - if (caches.contains(WEB_SESSIONS) - && !currentUser.getCapabilities().canAdministrateServer()) { - String msg = String.format( - "fatal: only site administrators can flush %s", - WEB_SESSIONS); - throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); - } - if (list) { if (all || caches.size() > 0) { throw error("error: cannot use --list with --all or --cache"); @@ -104,9 +97,12 @@ final class FlushCaches extends CacheCommand { String n = cacheNameOf(e.getPluginName(), e.getExportName()); if (flush(n)) { try { - e.getProvider().get().invalidateAll(); - } catch (Throwable err) { - stderr.println("error: cannot flush cache \"" + n + "\": " + err); + flushCache.apply( + new CacheResource(e.getPluginName(), e.getExportName(), + e.getProvider()), null); + } catch (RestApiException err) { + stderr.println("error: cannot flush cache \"" + n + "\": " + + err.getMessage()); } } }