summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/group/PeriodicGroupIndexer.java
blob: 2a9538d07ad8f888b6f0af8f1174d79acb468de4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright (C) 2018 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.group;

import static com.google.common.collect.ImmutableSet.toImmutableSet;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.ScheduleConfig;
import com.google.gerrit.server.config.ScheduleConfig.Schedule;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.group.db.GroupNameNotes;
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;

/**
 * Runnable to schedule periodic group reindexing.
 *
 * <p>Periodic group indexing is intended to run only on slaves. Replication to slaves happens on
 * Git level so that Gerrit is not aware of incoming replication events. But slaves need an updated
 * group index to resolve memberships of users for ACL validation. To keep the group index in slaves
 * up-to-date this class periodically scans the group refs in the All-Users repository to reindex
 * groups if they are stale. The ref states of the group refs are cached so that on each run deleted
 * groups can be detected and reindexed. This means callers of slaves may observe outdated group
 * information until the next indexing happens. The interval on which group indexing is done is
 * configurable by setting {@code index.scheduledIndexer.interval} in {@code gerrit.config}. By
 * default group indexing is done every 5 minutes.
 *
 * <p>This class is not able to detect group deletions that were replicated while the slave was
 * offline. This means if group refs are deleted while the slave is offline these groups are not
 * removed from the group index when the slave is started. However since group deletion is not
 * supported this should never happen and one can always do an offline reindex before starting the
 * slave.
 */
public class PeriodicGroupIndexer implements Runnable {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  public static class Module extends LifecycleModule {
    @Override
    protected void configure() {
      listener().to(Lifecycle.class);
    }
  }

  private static class Lifecycle implements LifecycleListener {
    private final Config cfg;
    private final WorkQueue queue;
    private final PeriodicGroupIndexer runner;

    @Inject
    Lifecycle(@GerritServerConfig Config cfg, WorkQueue queue, PeriodicGroupIndexer runner) {
      this.cfg = cfg;
      this.queue = queue;
      this.runner = runner;
    }

    @Override
    public void start() {
      boolean runOnStartup = cfg.getBoolean("index", "scheduledIndexer", "runOnStartup", true);
      if (runOnStartup) {
        runner.run();
      }

      boolean isEnabled = cfg.getBoolean("index", "scheduledIndexer", "enabled", true);
      if (!isEnabled) {
        logger.atWarning().log("index.scheduledIndexer is disabled");
        return;
      }

      Schedule schedule =
          ScheduleConfig.builder(cfg, "index")
              .setSubsection("scheduledIndexer")
              .buildSchedule()
              .orElseGet(() -> Schedule.createOrFail(TimeUnit.MINUTES.toMillis(5), "00:00"));
      queue.scheduleAtFixedRate(runner, schedule);
    }

    @Override
    public void stop() {
      // handled by WorkQueue.stop() already
    }
  }

  private final AllUsersName allUsersName;
  private final GitRepositoryManager repoManager;
  private final Provider<GroupIndexer> groupIndexerProvider;

  private ImmutableSet<AccountGroup.UUID> groupUuids;

  @Inject
  PeriodicGroupIndexer(
      AllUsersName allUsersName,
      GitRepositoryManager repoManager,
      Provider<GroupIndexer> groupIndexerProvider) {
    this.allUsersName = allUsersName;
    this.repoManager = repoManager;
    this.groupIndexerProvider = groupIndexerProvider;
  }

  @Override
  public synchronized void run() {
    try (Repository allUsers = repoManager.openRepository(allUsersName)) {
      ImmutableSet<AccountGroup.UUID> newGroupUuids =
          GroupNameNotes.loadAllGroups(allUsers).stream()
              .map(GroupReference::getUUID)
              .collect(toImmutableSet());
      GroupIndexer groupIndexer = groupIndexerProvider.get();
      int reindexCounter = 0;
      for (AccountGroup.UUID groupUuid : newGroupUuids) {
        if (groupIndexer.reindexIfStale(groupUuid)) {
          reindexCounter++;
        }
      }
      if (groupUuids != null) {
        // Check if any group was deleted since the last run and if yes remove these groups from the
        // index.
        for (AccountGroup.UUID groupUuid : Sets.difference(groupUuids, newGroupUuids)) {
          groupIndexer.index(groupUuid);
          reindexCounter++;
        }
      }
      groupUuids = newGroupUuids;
      logger.atInfo().log("Run group indexer, %s groups reindexed", reindexCounter);
    } catch (Throwable t) {
      logger.atSevere().withCause(t).log("Failed to reindex groups");
    }
  }
}