Groups have been migrated to NoteDb. Hence we no longer need to be able to read groups from ReviewDb. Change-Id: Ie87c1c8e604cf1344af5291f0b369cd24af8387d Signed-off-by: Edwin Kempin <ekempin@google.com>
165 lines
5.7 KiB
Java
165 lines
5.7 KiB
Java
// Copyright (C) 2017 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.db;
|
|
|
|
import static com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo.error;
|
|
import static com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo.warning;
|
|
|
|
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
|
|
import com.google.gerrit.reviewdb.client.Account;
|
|
import com.google.gerrit.reviewdb.client.AccountGroup;
|
|
import com.google.gerrit.server.account.AccountState;
|
|
import com.google.gerrit.server.account.Accounts;
|
|
import com.google.gerrit.server.account.GroupBackend;
|
|
import com.google.gerrit.server.config.AllUsersName;
|
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
|
import com.google.gerrit.server.group.InternalGroup;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import javax.inject.Inject;
|
|
import javax.inject.Singleton;
|
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
|
import org.eclipse.jgit.lib.Repository;
|
|
|
|
/**
|
|
* Checks individual groups for oddities, such as cycles, non-existent subgroups, etc. Only works if
|
|
* we are writing to NoteDb.
|
|
*/
|
|
@Singleton
|
|
public class GroupsConsistencyChecker {
|
|
private final AllUsersName allUsersName;
|
|
private final GroupBackend groupBackend;
|
|
private final Accounts accounts;
|
|
private final GitRepositoryManager repoManager;
|
|
private final GroupsNoteDbConsistencyChecker globalChecker;
|
|
|
|
@Inject
|
|
GroupsConsistencyChecker(
|
|
AllUsersName allUsersName,
|
|
GroupBackend groupBackend,
|
|
Accounts accounts,
|
|
GitRepositoryManager repositoryManager,
|
|
GroupsNoteDbConsistencyChecker globalChecker) {
|
|
this.allUsersName = allUsersName;
|
|
this.groupBackend = groupBackend;
|
|
this.accounts = accounts;
|
|
this.repoManager = repositoryManager;
|
|
this.globalChecker = globalChecker;
|
|
}
|
|
|
|
/** Checks that all internal group references exist, and that no groups have cycles. */
|
|
public List<ConsistencyProblemInfo> check() throws IOException {
|
|
try (Repository repo = repoManager.openRepository(allUsersName)) {
|
|
GroupsNoteDbConsistencyChecker.Result result = globalChecker.check(repo);
|
|
if (!result.problems.isEmpty()) {
|
|
return result.problems;
|
|
}
|
|
|
|
for (InternalGroup g : result.uuidToGroupMap.values()) {
|
|
result.problems.addAll(checkGroup(g, result.uuidToGroupMap));
|
|
}
|
|
|
|
return result.problems;
|
|
}
|
|
}
|
|
|
|
/** Checks the metadata for a single group for problems. */
|
|
private List<ConsistencyProblemInfo> checkGroup(
|
|
InternalGroup g, Map<AccountGroup.UUID, InternalGroup> byUUID) throws IOException {
|
|
List<ConsistencyProblemInfo> problems = new ArrayList<>();
|
|
|
|
problems.addAll(checkCycle(g, byUUID));
|
|
|
|
if (byUUID.get(g.getOwnerGroupUUID()) == null
|
|
&& groupBackend.get(g.getOwnerGroupUUID()) == null) {
|
|
problems.add(
|
|
error(
|
|
"group %s (%s) has nonexistent owner group %s",
|
|
g.getName(), g.getGroupUUID(), g.getOwnerGroupUUID()));
|
|
}
|
|
|
|
for (AccountGroup.UUID subUuid : g.getSubgroups()) {
|
|
if (byUUID.get(subUuid) == null && groupBackend.get(subUuid) == null) {
|
|
problems.add(
|
|
error(
|
|
"group %s (%s) has nonexistent subgroup %s",
|
|
g.getName(), g.getGroupUUID(), subUuid));
|
|
}
|
|
}
|
|
|
|
for (Account.Id id : g.getMembers().asList()) {
|
|
Optional<AccountState> account;
|
|
try {
|
|
account = accounts.get(id);
|
|
} catch (ConfigInvalidException e) {
|
|
problems.add(
|
|
error(
|
|
"group %s (%s) has member %s with invalid configuration: %s",
|
|
g.getName(), g.getGroupUUID(), id, e.getMessage()));
|
|
continue;
|
|
}
|
|
if (!account.isPresent()) {
|
|
problems.add(
|
|
error("group %s (%s) has nonexistent member %s", g.getName(), g.getGroupUUID(), id));
|
|
}
|
|
}
|
|
return problems;
|
|
}
|
|
|
|
/** checkCycle walks through root's subgroups recursively, and checks for cycles. */
|
|
private List<ConsistencyProblemInfo> checkCycle(
|
|
InternalGroup root, Map<AccountGroup.UUID, InternalGroup> byUUID) {
|
|
List<ConsistencyProblemInfo> problems = new ArrayList<>();
|
|
Set<InternalGroup> todo = new LinkedHashSet<>();
|
|
Set<InternalGroup> seen = new HashSet<>();
|
|
|
|
todo.add(root);
|
|
while (!todo.isEmpty()) {
|
|
InternalGroup t = todo.iterator().next();
|
|
todo.remove(t);
|
|
|
|
if (seen.contains(t)) {
|
|
continue;
|
|
}
|
|
seen.add(t);
|
|
|
|
// We don't check for owner cycles, since those are normal in self-administered groups.
|
|
for (AccountGroup.UUID subUuid : t.getSubgroups()) {
|
|
InternalGroup g = byUUID.get(subUuid);
|
|
if (g == null) {
|
|
continue;
|
|
}
|
|
|
|
if (Objects.equals(g, root)) {
|
|
problems.add(
|
|
warning(
|
|
"group %s (%s) contains a cycle: %s (%s) points to it as subgroup.",
|
|
root.getName(), root.getGroupUUID(), t.getName(), t.getGroupUUID()));
|
|
}
|
|
|
|
todo.add(g);
|
|
}
|
|
}
|
|
return problems;
|
|
}
|
|
}
|