diff options
Diffstat (limited to 'java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java')
-rw-r--r-- | java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java b/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java new file mode 100644 index 0000000000..3afb793566 --- /dev/null +++ b/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java @@ -0,0 +1,164 @@ +// 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 com.google.inject.Inject; +import com.google.inject.Singleton; +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 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; + } +} |