Merge "Support flushing of all caches via REST"

This commit is contained in:
Edwin Kempin
2014-07-04 08:35:02 +00:00
committed by Gerrit Code Review
6 changed files with 231 additions and 45 deletions

View File

@@ -339,6 +339,33 @@ E.g. this could be used to flush all caches:
done done
---- ----
[cache-operations]]
=== Cache Operations
--
'POST /config/server/caches/'
--
Executes a cache operation that is specified in the request body in a
link:#cache-operation-input[CacheOperationInput] entity.
[[flush-all-caches]]
==== Flush All Caches
.Request
----
POST /config/server/caches/ HTTP/1.0
Content-Type: application/json;charset=UTF-8
{
"operation": "FLUSH_ALL"
}
----
.Response
----
HTTP/1.1 200 OK
----
[[get-cache]] [[get-cache]]
=== Get Cache === Get Cache
-- --
@@ -589,6 +616,20 @@ The `CapabilityInfo` entity contains information about a capability.
|`name` |capability name |`name` |capability name
|================================= |=================================
[[cache-operation-input]]
=== CacheOperationInput
The `CacheOperationInput` entity contains information about an
operation that should be executed on caches.
[options="header",width="50%",cols="1,6"]
|=================================
|Field Name |Description
|`operation` |
The cache operation that should be executed:
`FLUSH_ALL`: Flushes all caches, except the `web_sessions` cache.
|=================================
[[entries-info]] [[entries-info]]
=== EntriesInfo === EntriesInfo
The `EntriesInfo` entity contains information about the entries in a The `EntriesInfo` entity contains information about the entries in a

View File

@@ -0,0 +1,56 @@
// 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.config.PostCaches.Operation.FLUSH_ALL;
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.server.config.ListCaches.CacheInfo;
import com.google.gerrit.server.config.PostCaches;
import org.apache.http.HttpStatus;
import org.junit.Test;
import java.io.IOException;
public class CacheOperationsIT extends AbstractDaemonTest {
@Test
public void flushAll() throws IOException {
RestResponse r = adminSession.get("/config/server/caches/project_list");
CacheInfo cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
assertTrue(cacheInfo.entries.mem.longValue() > 0);
r = adminSession.post("/config/server/caches/", new PostCaches.Input(FLUSH_ALL));
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
r.consume();
r = adminSession.get("/config/server/caches/project_list");
cacheInfo = newGson().fromJson(r.getReader(), CacheInfo.class);
assertNull(cacheInfo.entries.mem);
}
@Test
public void flushAll_Forbidden() throws IOException {
RestResponse r = userSession.post("/config/server/caches/",
new PostCaches.Input(FLUSH_ALL));
assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
}
}

View File

@@ -18,10 +18,12 @@ import com.google.common.cache.Cache;
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.registration.DynamicMap; import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection; import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString; import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestView; import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.AnonymousUser; import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
@@ -32,26 +34,29 @@ import com.google.inject.Singleton;
@RequiresCapability(GlobalCapability.VIEW_CACHES) @RequiresCapability(GlobalCapability.VIEW_CACHES)
@Singleton @Singleton
public class CachesCollection implements public class CachesCollection implements
ChildCollection<ConfigResource, CacheResource> { ChildCollection<ConfigResource, CacheResource>, AcceptsPost<ConfigResource> {
private final DynamicMap<RestView<CacheResource>> views; private final DynamicMap<RestView<CacheResource>> views;
private final ListCaches list; private final Provider<ListCaches> list;
private final Provider<CurrentUser> self; private final Provider<CurrentUser> self;
private final DynamicMap<Cache<?, ?>> cacheMap; private final DynamicMap<Cache<?, ?>> cacheMap;
private final PostCaches postCaches;
@Inject @Inject
CachesCollection(DynamicMap<RestView<CacheResource>> views, CachesCollection(DynamicMap<RestView<CacheResource>> views,
ListCaches list, Provider<CurrentUser> self, Provider<ListCaches> list, Provider<CurrentUser> self,
DynamicMap<Cache<?, ?>> cacheMap) { DynamicMap<Cache<?, ?>> cacheMap,
PostCaches postCaches) {
this.views = views; this.views = views;
this.list = list; this.list = list;
this.self = self; this.self = self;
this.cacheMap = cacheMap; this.cacheMap = cacheMap;
this.postCaches = postCaches;
} }
@Override @Override
public RestView<ConfigResource> list() { public RestView<ConfigResource> list() {
return list; return list.get();
} }
@Override @Override
@@ -85,4 +90,10 @@ public class CachesCollection implements
public DynamicMap<RestView<CacheResource>> views() { public DynamicMap<RestView<CacheResource>> views() {
return views; return views;
} }
@SuppressWarnings("unchecked")
@Override
public PostCaches post(ConfigResource parent) throws RestApiException {
return postCaches;
}
} }

View File

@@ -31,7 +31,7 @@ public class FlushCache implements RestModifyView<CacheResource, Input> {
public static class Input { public static class Input {
} }
private static final String WEB_SESSIONS = "web_sessions"; public static final String WEB_SESSIONS = "web_sessions";
private final Provider<CurrentUser> self; private final Provider<CurrentUser> self;

View File

@@ -0,0 +1,81 @@
// 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.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.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.config.PostCaches.Input;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@RequiresCapability(GlobalCapability.FLUSH_CACHES)
@Singleton
public class PostCaches implements RestModifyView<ConfigResource, Input> {
public static class Input {
public Operation operation;
public Input() {
}
public Input(Operation op) {
operation = op;
}
}
public static enum Operation {
FLUSH_ALL;
}
private final DynamicMap<Cache<?, ?>> cacheMap;
private final FlushCache flushCache;
@Inject
public PostCaches(DynamicMap<Cache<?, ?>> cacheMap,
FlushCache flushCache) {
this.cacheMap = cacheMap;
this.flushCache = flushCache;
}
@Override
public Object apply(ConfigResource rsrc, Input input) throws AuthException,
ResourceNotFoundException, BadRequestException {
if (input == null || input.operation == null) {
throw new BadRequestException("operation must be specified");
}
switch (input.operation) {
case FLUSH_ALL:
for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
CacheResource cacheResource =
new CacheResource(e.getPluginName(), e.getExportName(),
e.getProvider());
if (FlushCache.WEB_SESSIONS.equals(cacheResource.getName())) {
continue;
}
flushCache.apply(cacheResource, null);
}
return Response.ok("");
default:
throw new BadRequestException("unsupported operation: " + input.operation);
}
}
}

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.sshd.commands; package com.google.gerrit.sshd.commands;
import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH_ALL;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE; import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
@@ -26,6 +27,7 @@ import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.FlushCache; import com.google.gerrit.server.config.FlushCache;
import com.google.gerrit.server.config.ListCaches; import com.google.gerrit.server.config.ListCaches;
import com.google.gerrit.server.config.ListCaches.OutputFormat; import com.google.gerrit.server.config.ListCaches.OutputFormat;
import com.google.gerrit.server.config.PostCaches;
import com.google.gerrit.sshd.CommandMetaData; import com.google.gerrit.sshd.CommandMetaData;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -40,8 +42,6 @@ import java.util.List;
@CommandMetaData(name = "flush-caches", description = "Flush some/all server caches from memory", @CommandMetaData(name = "flush-caches", description = "Flush some/all server caches from memory",
runsAt = MASTER_OR_SLAVE) runsAt = MASTER_OR_SLAVE)
final class FlushCaches extends CacheCommand { final class FlushCaches extends CacheCommand {
private static final String WEB_SESSIONS = "web_sessions";
@Option(name = "--cache", usage = "flush named cache", metaVar = "NAME") @Option(name = "--cache", usage = "flush named cache", metaVar = "NAME")
private List<String> caches = new ArrayList<>(); private List<String> caches = new ArrayList<>();
@@ -57,32 +57,44 @@ final class FlushCaches extends CacheCommand {
@Inject @Inject
private Provider<ListCaches> listCaches; private Provider<ListCaches> listCaches;
@Inject
private PostCaches postCaches;
@Override @Override
protected void run() throws Failure { protected void run() throws Failure {
if (list) { try {
if (all || caches.size() > 0) { if (list) {
throw error("error: cannot use --list with --all or --cache"); if (all || caches.size() > 0) {
throw error("error: cannot use --list with --all or --cache");
}
doList();
return;
} }
doList();
return;
}
if (all && caches.size() > 0) { if (all && caches.size() > 0) {
throw error("error: cannot combine --all and --cache"); throw error("error: cannot combine --all and --cache");
} else if (!all && caches.size() == 1 && caches.contains("all")) { } else if (!all && caches.size() == 1 && caches.contains("all")) {
caches.clear(); caches.clear();
all = true; all = true;
} else if (!all && caches.isEmpty()) { } else if (!all && caches.isEmpty()) {
all = true; all = true;
}
List<String> names = cacheNames();
for (String n : caches) {
if (!names.contains(n)) {
throw error("error: cache \"" + n + "\" not recognized");
} }
if (all) {
postCaches.apply(new ConfigResource(),
new PostCaches.Input(FLUSH_ALL));
} else {
List<String> names = cacheNames();
for (String n : caches) {
if (!names.contains(n)) {
throw error("error: cache \"" + n + "\" not recognized");
}
}
doBulkFlush();
}
} catch (RestApiException e) {
throw die(e.getMessage());
} }
doBulkFlush();
} }
private static UnloggedFailure error(final String msg) { private static UnloggedFailure error(final String msg) {
@@ -95,7 +107,7 @@ final class FlushCaches extends CacheCommand {
.apply(new ConfigResource()); .apply(new ConfigResource());
} }
private void doList() throws UnloggedFailure { private void doList() throws RestApiException {
for (String name : cacheNames()) { for (String name : cacheNames()) {
stderr.print(name); stderr.print(name);
stderr.print('\n'); stderr.print('\n');
@@ -107,7 +119,7 @@ final class FlushCaches extends CacheCommand {
try { try {
for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) { for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
String n = cacheNameOf(e.getPluginName(), e.getExportName()); String n = cacheNameOf(e.getPluginName(), e.getExportName());
if (flush(n)) { if (caches.contains(n)) {
try { try {
flushCache.apply( flushCache.apply(
new CacheResource(e.getPluginName(), e.getExportName(), new CacheResource(e.getPluginName(), e.getExportName(),
@@ -122,19 +134,4 @@ final class FlushCaches extends CacheCommand {
stderr.flush(); stderr.flush();
} }
} }
private boolean flush(final String cacheName) {
if (caches.contains(cacheName)) {
return true;
} else if (all) {
if (WEB_SESSIONS.equals(cacheName)) {
return false;
}
return true;
} else {
return false;
}
}
} }