Line data Source code
1 : // Copyright (C) 2018 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.group; 16 : 17 : import static com.google.common.collect.ImmutableSet.toImmutableSet; 18 : 19 : import com.google.common.collect.ImmutableSet; 20 : import com.google.common.collect.Sets; 21 : import com.google.common.flogger.FluentLogger; 22 : import com.google.gerrit.entities.AccountGroup; 23 : import com.google.gerrit.entities.GroupReference; 24 : import com.google.gerrit.extensions.events.LifecycleListener; 25 : import com.google.gerrit.lifecycle.LifecycleModule; 26 : import com.google.gerrit.server.config.AllUsersName; 27 : import com.google.gerrit.server.config.GerritServerConfig; 28 : import com.google.gerrit.server.config.ScheduleConfig; 29 : import com.google.gerrit.server.config.ScheduleConfig.Schedule; 30 : import com.google.gerrit.server.git.GitRepositoryManager; 31 : import com.google.gerrit.server.git.WorkQueue; 32 : import com.google.gerrit.server.group.db.GroupNameNotes; 33 : import com.google.gerrit.server.index.group.GroupIndexer; 34 : import com.google.inject.Inject; 35 : import com.google.inject.Provider; 36 : import java.util.concurrent.TimeUnit; 37 : import org.eclipse.jgit.lib.Config; 38 : import org.eclipse.jgit.lib.Repository; 39 : 40 : /** 41 : * Runnable to schedule periodic group reindexing. 42 : * 43 : * <p>Periodic group indexing is intended to run only on slaves. Replication to slaves happens on 44 : * Git level so that Gerrit is not aware of incoming replication events. But slaves need an updated 45 : * group index to resolve memberships of users for ACL validation. To keep the group index in slaves 46 : * up-to-date this class periodically scans the group refs in the All-Users repository to reindex 47 : * groups if they are stale. The ref states of the group refs are cached so that on each run deleted 48 : * groups can be detected and reindexed. This means callers of slaves may observe outdated group 49 : * information until the next indexing happens. The interval on which group indexing is done is 50 : * configurable by setting {@code index.scheduledIndexer.interval} in {@code gerrit.config}. By 51 : * default group indexing is done every 5 minutes. 52 : * 53 : * <p>This class is not able to detect group deletions that were replicated while the slave was 54 : * offline. This means if group refs are deleted while the slave is offline these groups are not 55 : * removed from the group index when the slave is started. However since group deletion is not 56 : * supported this should never happen and one can always do an offline reindex before starting the 57 : * slave. 58 : */ 59 : public class PeriodicGroupIndexer implements Runnable { 60 4 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 61 : 62 4 : public static class PeriodicGroupIndexerModule extends LifecycleModule { 63 : @Override 64 : protected void configure() { 65 4 : listener().to(Lifecycle.class); 66 4 : } 67 : } 68 : 69 : private static class Lifecycle implements LifecycleListener { 70 : private final Config cfg; 71 : private final WorkQueue queue; 72 : private final PeriodicGroupIndexer runner; 73 : 74 : @Inject 75 4 : Lifecycle(@GerritServerConfig Config cfg, WorkQueue queue, PeriodicGroupIndexer runner) { 76 4 : this.cfg = cfg; 77 4 : this.queue = queue; 78 4 : this.runner = runner; 79 4 : } 80 : 81 : @Override 82 : public void start() { 83 4 : boolean runOnStartup = cfg.getBoolean("index", "scheduledIndexer", "runOnStartup", true); 84 4 : if (runOnStartup) { 85 4 : runner.run(); 86 : } 87 : 88 4 : boolean isEnabled = cfg.getBoolean("index", "scheduledIndexer", "enabled", true); 89 4 : if (!isEnabled) { 90 1 : logger.atWarning().log("index.scheduledIndexer is disabled"); 91 1 : return; 92 : } 93 : 94 4 : Schedule schedule = 95 4 : ScheduleConfig.builder(cfg, "index") 96 4 : .setSubsection("scheduledIndexer") 97 4 : .buildSchedule() 98 4 : .orElseGet(() -> Schedule.createOrFail(TimeUnit.MINUTES.toMillis(5), "00:00")); 99 4 : queue.scheduleAtFixedRate(runner, schedule); 100 4 : } 101 : 102 : @Override 103 : public void stop() { 104 : // handled by WorkQueue.stop() already 105 4 : } 106 : } 107 : 108 : private final AllUsersName allUsersName; 109 : private final GitRepositoryManager repoManager; 110 : private final Provider<GroupIndexer> groupIndexerProvider; 111 : 112 : private ImmutableSet<AccountGroup.UUID> groupUuids; 113 : 114 : @Inject 115 : PeriodicGroupIndexer( 116 : AllUsersName allUsersName, 117 : GitRepositoryManager repoManager, 118 4 : Provider<GroupIndexer> groupIndexerProvider) { 119 4 : this.allUsersName = allUsersName; 120 4 : this.repoManager = repoManager; 121 4 : this.groupIndexerProvider = groupIndexerProvider; 122 4 : } 123 : 124 : @Override 125 : public synchronized void run() { 126 4 : try (Repository allUsers = repoManager.openRepository(allUsersName)) { 127 4 : ImmutableSet<AccountGroup.UUID> newGroupUuids = 128 4 : GroupNameNotes.loadAllGroups(allUsers).stream() 129 4 : .map(GroupReference::getUUID) 130 4 : .collect(toImmutableSet()); 131 4 : GroupIndexer groupIndexer = groupIndexerProvider.get(); 132 4 : int reindexCounter = 0; 133 4 : for (AccountGroup.UUID groupUuid : newGroupUuids) { 134 4 : if (groupIndexer.reindexIfStale(groupUuid)) { 135 4 : reindexCounter++; 136 : } 137 4 : } 138 4 : if (groupUuids != null) { 139 : // Check if any group was deleted since the last run and if yes remove these groups from the 140 : // index. 141 1 : for (AccountGroup.UUID groupUuid : Sets.difference(groupUuids, newGroupUuids)) { 142 0 : groupIndexer.index(groupUuid); 143 0 : reindexCounter++; 144 0 : } 145 : } 146 4 : groupUuids = newGroupUuids; 147 4 : logger.atInfo().log("Run group indexer, %s groups reindexed", reindexCounter); 148 0 : } catch (Exception t) { 149 0 : logger.atSevere().withCause(t).log("Failed to reindex groups"); 150 4 : } 151 4 : } 152 : }