blob: 087e804f66e73594a26fb2751860d836f743afcf [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.AutoAdjustCachesCommand.CONFIG_HEADER;
import static com.googlesource.gerrit.modules.cache.chroniclemap.AutoAdjustCachesCommand.TUNED_INFIX;
import static com.googlesource.gerrit.modules.cache.chroniclemap.ChronicleMapCacheConfig.Defaults.maxBloatFactorFor;
import static com.googlesource.gerrit.modules.cache.chroniclemap.ChronicleMapCacheConfig.Defaults.maxEntriesFor;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
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.config.GerritConfig;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.ModuleImpl;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.io.File;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;
@Sandboxed
@UseLocalDisk
@UseSsh
@TestPlugin(
name = "cache-chroniclemap",
sshModule = "com.googlesource.gerrit.modules.cache.chroniclemap.SSHCommandModule",
httpModule = "com.googlesource.gerrit.modules.cache.chroniclemap.HttpModule")
public class AutoAdjustCachesIT extends LightweightPluginDaemonTest {
private static final String SSH_CMD = "cache-chroniclemap auto-adjust-caches";
private static final String REST_CMD = "/plugins/cache-chroniclemap/auto-adjust-caches";
private static final String GROUPS_BYUUID_PERSISTED = "groups_byuuid_persisted";
private static final String GERRIT_FILE_DIFF = "gerrit_file_diff";
private static final String GIT_FILE_DIFF = "git_file_diff";
private static final String DIFF_SUMMARY = "diff_summary";
private static final String ACCOUNTS = "accounts";
private static final String PERSISTED_PROJECTS = "persisted_projects";
private static final String TEST_CACHE_NAME = "test_cache";
private static final int TEST_CACHE_VERSION = 1;
private static final String TEST_CACHE_FILENAME_TUNED =
TEST_CACHE_NAME + "_" + TEST_CACHE_VERSION + AutoAdjustCaches.TUNED_INFIX;
private static final String TEST_CACHE_KEY_100_CHARS = new String(new char[100]);
private static final Function<String, Boolean> MATCH_ALL = (n) -> true;
private static final ImmutableList<String> EXPECTED_CACHES =
ImmutableList.of(
GROUPS_BYUUID_PERSISTED,
GERRIT_FILE_DIFF,
GIT_FILE_DIFF,
DIFF_SUMMARY,
ACCOUNTS,
PERSISTED_PROJECTS);
@Inject private SitePaths sitePaths;
@Inject
@Named(TEST_CACHE_NAME)
LoadingCache<String, String> testCache;
@ModuleImpl(name = CacheModule.PERSISTENT_MODULE)
public static class TestPersistentCacheModule extends CacheModule {
@Override
protected void configure() {
persist(TEST_CACHE_NAME, String.class, String.class)
.loader(TestCacheLoader.class)
.version(TEST_CACHE_VERSION);
install(new ChronicleMapCacheModule());
}
}
public static class TestCacheLoader extends CacheLoader<String, String> {
@Override
public String load(String key) throws Exception {
return key;
}
}
@Override
public com.google.inject.Module createModule() {
return new TestPersistentCacheModule();
}
@Test
public void shouldUseDefaultsWhenCachesAreNotConfigured() throws Exception {
createChange();
String result = adminSshSession.exec(SSH_CMD);
adminSshSession.assertSuccess();
Config configResult = configResult(result, CONFIG_HEADER);
for (String cache : EXPECTED_CACHES) {
assertThat(configResult.getLong("cache", cache, "maxEntries", 0))
.isEqualTo(maxEntriesFor(cache));
assertThat(configResult.getLong("cache", cache, "maxBloatFactor", 0))
.isEqualTo(maxBloatFactorFor(cache));
}
}
@Test
public void shouldCreateNewCacheFiles() throws Exception {
createChange();
adminSshSession.exec(SSH_CMD);
adminSshSession.assertSuccess();
Set<String> tunedCaches =
tunedFileNamesSet(n -> n.matches(".*(" + String.join("|", EXPECTED_CACHES) + ").*"));
assertThat(tunedCaches.size()).isEqualTo(EXPECTED_CACHES.size());
}
@Test
@GerritConfig(name = "cache.test_cache.avgKeySize", value = "207")
@GerritConfig(name = "cache.test_cache.avgValueSize", value = "207")
public void shouldNotRecreateTestCacheFileWhenAlreadyTuned() throws Exception {
testCache.get(TEST_CACHE_KEY_100_CHARS);
String tuneResult = adminSshSession.exec(SSH_CMD);
adminSshSession.assertSuccess();
assertThat(configResult(tuneResult, CONFIG_HEADER).getSubsections("cache"))
.doesNotContain(TEST_CACHE_NAME);
assertThat(Joiner.on('\n').join(tunedFileNamesSet(MATCH_ALL)))
.doesNotContain(TEST_CACHE_FILENAME_TUNED);
}
@Test
public void shouldCreateTestCacheTuned() throws Exception {
testCache.get(TEST_CACHE_KEY_100_CHARS);
String tuneResult = adminSshSession.exec(SSH_CMD);
adminSshSession.assertSuccess();
assertThat(configResult(tuneResult, CONFIG_HEADER).getSubsections("cache"))
.contains(TEST_CACHE_NAME);
assertThat(
Joiner.on('\n').join(tunedFileNamesSet((n) -> n.contains(TEST_CACHE_FILENAME_TUNED))))
.isNotEmpty();
}
@Test
public void shouldDenyAccessOverSshToCreateNewCacheFiles() throws Exception {
userSshSession.exec(SSH_CMD);
userSshSession.assertFailure("not permitted");
}
@Test
public void shouldDenyAccessOverRestToCreateNewCacheFiles() throws Exception {
userRestSession.put(REST_CMD).assertForbidden();
}
@Test
public void shouldAllowTuningOverRestForAdmin() throws Exception {
RestResponse resp = adminRestSession.put(REST_CMD);
resp.assertCreated();
assertThat(configResult(resp.getEntityContent(), null).getSubsections("cache")).isNotEmpty();
assertThat(tunedFileNamesSet(MATCH_ALL)).isNotEmpty();
}
private Config configResult(String result, @Nullable String configHeader)
throws ConfigInvalidException {
Config configResult = new Config();
configResult.fromText(configHeader == null ? result : result.split(configHeader)[1]);
return configResult;
}
private Set<String> tunedFileNamesSet(Function<String, Boolean> fileNameFilter) {
Path cachePath = sitePaths.resolve(cfg.getString("cache", null, "directory"));
return Stream.of(Objects.requireNonNull(cachePath.toFile().listFiles()))
.filter(file -> !file.isDirectory())
.map(File::getName)
.filter(n -> n.contains(TUNED_INFIX) && fileNameFilter.apply(n))
.collect(Collectors.toSet());
}
}