blob: 89fe5f6fa9d741dacc4dead2d71e9e1c1acc406a [file] [log] [blame]
// Copyright (C) 2021 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.googlesource.gerrit.modules.cache.chroniclemap;
import static com.google.common.truth.Truth.assertThat;
import static com.googlesource.gerrit.modules.cache.chroniclemap.H2CacheSshCommand.H2_SUFFIX;
import static com.googlesource.gerrit.modules.cache.chroniclemap.MigrateH2Caches.DEFAULT_MAX_BLOAT_FACTOR;
import static com.googlesource.gerrit.modules.cache.chroniclemap.MigrateH2Caches.DEFAULT_SIZE_MULTIPLIER;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.UseSsh;
import com.google.gerrit.acceptance.WaitUtil;
import com.google.gerrit.entities.CachedProjectConfig;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.metrics.DisabledMetricMaker;
import com.google.gerrit.server.account.CachedAccountDetails;
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.h2.H2CacheImpl;
import com.google.gerrit.server.cache.proto.Cache;
import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.sshd.BaseCommand;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Key;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
@Sandboxed
@UseSsh
@TestPlugin(
name = "cache-chroniclemap",
sshModule = "com.googlesource.gerrit.modules.cache.chroniclemap.SSHCommandModule")
public class MigrateH2CachesIT extends LightweightPluginDaemonTest {
private final Duration LOAD_CACHE_WAIT_TIMEOUT = Duration.ofSeconds(4);
private String ACCOUNTS_CACHE_NAME = "accounts";
private String PERSISTED_PROJECTS_CACHE_NAME = "persisted_projects";
@Inject protected GitRepositoryManager repoManager;
@Inject private SitePaths sitePaths;
private ChronicleMapCacheConfig.Factory chronicleMapCacheConfigFactory;
private String cmd = Joiner.on(" ").join("cache-chroniclemap", "migrate-h2-caches");
@Before
public void setUp() {
chronicleMapCacheConfigFactory =
plugin.getSshInjector().getInstance(ChronicleMapCacheConfig.Factory.class);
}
@Test
@UseLocalDisk
public void shouldRunAndCompleteSuccessfullyWhenCacheDirectoryIsDefined() throws Exception {
String result = adminSshSession.exec(cmd);
adminSshSession.assertSuccess();
assertThat(result).contains("Complete");
}
@Test
@UseLocalDisk
public void shouldOutputChronicleMapBloatedConfiguration() throws Exception {
waitForCacheToLoad(ACCOUNTS_CACHE_NAME);
waitForCacheToLoad(PERSISTED_PROJECTS_CACHE_NAME);
String result = adminSshSession.exec(cmd);
adminSshSession.assertSuccess();
assertThat(result)
.contains(
"[cache \""
+ ACCOUNTS_CACHE_NAME
+ "\"]\n"
+ "\tmaxEntries = "
+ H2CacheFor(ACCOUNTS_CACHE_NAME).diskStats().size() * DEFAULT_SIZE_MULTIPLIER);
assertThat(result)
.contains(
"[cache \""
+ PERSISTED_PROJECTS_CACHE_NAME
+ "\"]\n"
+ "\tmaxEntries = "
+ H2CacheFor(PERSISTED_PROJECTS_CACHE_NAME).diskStats().size()
* DEFAULT_SIZE_MULTIPLIER);
}
@Test
public void shouldFailWhenCacheDirectoryIsNotDefined() throws Exception {
adminSshSession.exec(cmd);
adminSshSession.assertFailure("fatal: Cannot run migration, cache directory is not configured");
}
@Test
public void shouldFailWhenUserHasNoAdminServerCapability() throws Exception {
userSshSession.exec(cmd);
userSshSession.assertFailure("administrateServer for plugin cache-chroniclemap not permitted");
}
@Test
@UseLocalDisk
public void shouldMigrateAccountsCache() throws Exception {
waitForCacheToLoad(ACCOUNTS_CACHE_NAME);
adminSshSession.exec(cmd);
adminSshSession.assertSuccess();
ChronicleMapCacheImpl<CachedAccountDetails.Key, CachedAccountDetails> chronicleMapCache =
chronicleCacheFor(ACCOUNTS_CACHE_NAME);
H2CacheImpl<CachedAccountDetails.Key, CachedAccountDetails> h2Cache =
H2CacheFor(ACCOUNTS_CACHE_NAME);
assertThat(chronicleMapCache.diskStats().size()).isEqualTo(h2Cache.diskStats().size());
}
@Test
@UseLocalDisk
public void shouldMigratePersistentProjects() throws Exception {
waitForCacheToLoad(PERSISTED_PROJECTS_CACHE_NAME);
adminSshSession.exec(cmd);
adminSshSession.assertSuccess();
H2CacheImpl<Cache.ProjectCacheKeyProto, CachedProjectConfig> h2Cache =
H2CacheFor(PERSISTED_PROJECTS_CACHE_NAME);
ChronicleMapCacheImpl<Cache.ProjectCacheKeyProto, CachedProjectConfig> chronicleMapCache =
chronicleCacheFor(PERSISTED_PROJECTS_CACHE_NAME);
Cache.ProjectCacheKeyProto allUsersProto = projectCacheKey(allUsers);
Cache.ProjectCacheKeyProto allProjectsProto = projectCacheKey(allProjects);
assertThat(chronicleMapCache.get(allUsersProto)).isEqualTo(h2Cache.get(allUsersProto));
assertThat(chronicleMapCache.get(allProjectsProto)).isEqualTo(h2Cache.get(allProjectsProto));
}
private Cache.ProjectCacheKeyProto projectCacheKey(Project.NameKey key) throws IOException {
try (Repository git = repoManager.openRepository(key)) {
return Cache.ProjectCacheKeyProto.newBuilder()
.setProject(key.get())
.setRevision(
ObjectIdConverter.create()
.toByteString(git.exactRef(RefNames.REFS_CONFIG).getObjectId()))
.build();
}
}
@SuppressWarnings("unchecked")
private <K, V> PersistentCacheDef<K, V> getPersistentCacheDef(String named) {
return findClassBoundWithName(PersistentCacheDef.class, named);
}
@SuppressWarnings("unchecked")
private <K, V> H2CacheImpl<K, V> H2CacheFor(String named) {
return (H2CacheImpl<K, V>) findClassBoundWithName(LoadingCache.class, named);
}
@SuppressWarnings("unchecked")
private <K, V> CacheLoader<K, V> cacheLoaderFor(String named) {
return findClassBoundWithName(CacheLoader.class, named);
}
private <T> T findClassBoundWithName(Class<T> clazz, String named) {
return plugin.getSysInjector().getAllBindings().entrySet().stream()
.filter(entry -> isClassBoundWithName(entry, clazz.getSimpleName(), named))
.findFirst()
.map(entry -> clazz.cast(entry.getValue().getProvider().get()))
.get();
}
private boolean isClassBoundWithName(
Map.Entry<Key<?>, Binding<?>> entry, String classNameMatch, String named) {
String className = entry.getKey().getTypeLiteral().getRawType().getSimpleName();
Annotation annotation = entry.getKey().getAnnotation();
return className.equals(classNameMatch)
&& annotation != null
&& annotation.toString().endsWith(String.format("Named(value=\"%s\")", named));
}
private <K, V> ChronicleMapCacheImpl<K, V> chronicleCacheFor(String cacheName)
throws BaseCommand.UnloggedFailure, IOException {
Path cacheDirectory = sitePaths.resolve(cfg.getString("cache", null, "directory"));
PersistentCacheDef<K, V> persistentDef = getPersistentCacheDef(cacheName);
ChronicleMapCacheConfig config =
MigrateH2Caches.makeChronicleMapConfig(
chronicleMapCacheConfigFactory,
cacheDirectory,
persistentDef,
H2CacheSshCommand.getStats(
cacheDirectory.resolve(String.format("%s.%s", cacheName, H2_SUFFIX))),
DEFAULT_SIZE_MULTIPLIER,
DEFAULT_MAX_BLOAT_FACTOR);
return new ChronicleMapCacheImpl<>(
persistentDef, config, cacheLoaderFor(cacheName), new DisabledMetricMaker());
}
private void waitForCacheToLoad(String cacheName) throws InterruptedException {
WaitUtil.waitUntil(() -> H2CacheFor(cacheName).diskStats().size() > 0, LOAD_CACHE_WAIT_TIMEOUT);
}
}