LCOV - code coverage report
Current view: top level - server/account - UniversalGroupBackend.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 133 143 93.0 %
Date: 2022-11-19 15:00:39 Functions: 23 29 79.3 %

          Line data    Source code
       1             : // Copyright (C) 2012 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.account;
      16             : 
      17             : import static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMPARATOR;
      18             : import static java.util.stream.Collectors.joining;
      19             : 
      20             : import com.google.common.collect.ImmutableMap;
      21             : import com.google.common.collect.Iterables;
      22             : import com.google.common.collect.ListMultimap;
      23             : import com.google.common.collect.MultimapBuilder;
      24             : import com.google.common.collect.Sets;
      25             : import com.google.common.flogger.FluentLogger;
      26             : import com.google.gerrit.common.Nullable;
      27             : import com.google.gerrit.entities.AccountGroup;
      28             : import com.google.gerrit.entities.GroupDescription;
      29             : import com.google.gerrit.entities.GroupReference;
      30             : import com.google.gerrit.metrics.Counter1;
      31             : import com.google.gerrit.metrics.Counter2;
      32             : import com.google.gerrit.metrics.Description;
      33             : import com.google.gerrit.metrics.Field;
      34             : import com.google.gerrit.metrics.MetricMaker;
      35             : import com.google.gerrit.server.CurrentUser;
      36             : import com.google.gerrit.server.StartupCheck;
      37             : import com.google.gerrit.server.StartupException;
      38             : import com.google.gerrit.server.config.GerritServerConfig;
      39             : import com.google.gerrit.server.logging.Metadata;
      40             : import com.google.gerrit.server.plugincontext.PluginSetContext;
      41             : import com.google.gerrit.server.plugincontext.PluginSetEntryContext;
      42             : import com.google.gerrit.server.project.ProjectState;
      43             : import com.google.inject.Inject;
      44             : import com.google.inject.Singleton;
      45             : import java.util.Collection;
      46             : import java.util.HashSet;
      47             : import java.util.Map;
      48             : import java.util.Set;
      49             : import org.eclipse.jgit.lib.Config;
      50             : 
      51             : /**
      52             :  * Universal implementation of the GroupBackend that works with the injected set of GroupBackends.
      53             :  */
      54             : @Singleton
      55             : public class UniversalGroupBackend implements GroupBackend {
      56         152 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      57             : 
      58         152 :   private static final Field<String> SYSTEM_FIELD =
      59         152 :       Field.ofString("system", Metadata.Builder::groupSystem).build();
      60             : 
      61             :   private final PluginSetContext<GroupBackend> backends;
      62             :   private final Counter1<String> handlesCount;
      63             :   private final Counter1<String> getCount;
      64             :   private final Counter2<String, Integer> suggestCount;
      65             :   private final Counter2<String, Boolean> containsCount;
      66             :   private final Counter2<String, Boolean> containsAnyCount;
      67             :   private final Counter2<String, Integer> intersectionCount;
      68             :   private final Counter2<String, Integer> knownGroupsCount;
      69             : 
      70             :   @Inject
      71         152 :   UniversalGroupBackend(PluginSetContext<GroupBackend> backends, MetricMaker metricMaker) {
      72         152 :     this.backends = backends;
      73         152 :     this.handlesCount =
      74         152 :         metricMaker.newCounter(
      75             :             "group/handles_count", new Description("Calls to GroupBackend.handles"), SYSTEM_FIELD);
      76         152 :     this.getCount =
      77         152 :         metricMaker.newCounter(
      78             :             "group/get_count", new Description("Calls to GroupBackend.get"), SYSTEM_FIELD);
      79         152 :     this.suggestCount =
      80         152 :         metricMaker.newCounter(
      81             :             "group/suggest_count",
      82             :             new Description("Calls to GroupBackend.suggest"),
      83             :             SYSTEM_FIELD,
      84         152 :             Field.ofInteger("num_suggested", (meta, value) -> {}).build());
      85         152 :     this.containsCount =
      86         152 :         metricMaker.newCounter(
      87             :             "group/contains_count",
      88             :             new Description("Calls to GroupMemberships.contains"),
      89             :             SYSTEM_FIELD,
      90         152 :             Field.ofBoolean("contains", (meta, value) -> {}).build());
      91         152 :     this.containsAnyCount =
      92         152 :         metricMaker.newCounter(
      93             :             "group/contains_any_of_count",
      94             :             new Description("Calls to GroupMemberships.containsAnyOf"),
      95             :             SYSTEM_FIELD,
      96         152 :             Field.ofBoolean("contains_any_of", (meta, value) -> {}).build());
      97         152 :     this.intersectionCount =
      98         152 :         metricMaker.newCounter(
      99             :             "group/intersection_count",
     100             :             new Description("Calls to GroupMemberships.intersection"),
     101             :             SYSTEM_FIELD,
     102         152 :             Field.ofInteger("num_intersection", (meta, value) -> {}).build());
     103         152 :     this.knownGroupsCount =
     104         152 :         metricMaker.newCounter(
     105             :             "group/known_groups_count",
     106             :             new Description("Calls to GroupMemberships.getKnownGroups"),
     107             :             SYSTEM_FIELD,
     108         152 :             Field.ofInteger("num_known_groups", (meta, value) -> {}).build());
     109         152 :   }
     110             : 
     111             :   @Nullable
     112             :   private GroupBackend backend(AccountGroup.UUID uuid) {
     113          67 :     if (uuid != null) {
     114          67 :       for (PluginSetEntryContext<GroupBackend> c : backends) {
     115          67 :         if (Boolean.TRUE.equals(c.call(b -> b.handles(uuid)))) {
     116          59 :           return c.get();
     117             :         }
     118          65 :       }
     119             :     }
     120          41 :     return null;
     121             :   }
     122             : 
     123             :   @Override
     124             :   public boolean handles(AccountGroup.UUID uuid) {
     125          56 :     GroupBackend b = backend(uuid);
     126          56 :     if (b == null) {
     127          41 :       return false;
     128             :     }
     129          42 :     handlesCount.increment(name(b));
     130          42 :     return true;
     131             :   }
     132             : 
     133             :   @Nullable
     134             :   @Override
     135             :   public GroupDescription.Basic get(AccountGroup.UUID uuid) {
     136          59 :     if (uuid == null) {
     137           0 :       return null;
     138             :     }
     139          59 :     GroupBackend b = backend(uuid);
     140          59 :     if (b == null) {
     141           3 :       logger.atFine().log("Unknown GroupBackend for UUID: %s", uuid);
     142           3 :       return null;
     143             :     }
     144          59 :     getCount.increment(name(b));
     145          59 :     return b.get(uuid);
     146             :   }
     147             : 
     148             :   @Override
     149             :   public Collection<GroupReference> suggest(String name, ProjectState project) {
     150          43 :     Set<GroupReference> groups = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR);
     151          43 :     backends.runEach(
     152             :         g -> {
     153          43 :           Collection<GroupReference> suggestions = g.suggest(name, project);
     154          43 :           suggestCount.increment(name(g), suggestions.size());
     155          43 :           groups.addAll(suggestions);
     156          43 :         });
     157          43 :     return groups;
     158             :   }
     159             : 
     160             :   @Override
     161             :   public GroupMembership membershipsOf(CurrentUser user) {
     162         150 :     return new UniversalGroupMembership(user);
     163             :   }
     164             : 
     165             :   private class UniversalGroupMembership implements GroupMembership {
     166             :     private final Map<GroupBackend, GroupMembership> memberships;
     167             : 
     168         150 :     private UniversalGroupMembership(CurrentUser user) {
     169         150 :       ImmutableMap.Builder<GroupBackend, GroupMembership> builder = ImmutableMap.builder();
     170         150 :       backends.runEach(g -> builder.put(g, g.membershipsOf(user)));
     171         150 :       this.memberships = builder.build();
     172         150 :     }
     173             : 
     174             :     @Nullable
     175             :     private Map.Entry<GroupBackend, GroupMembership> membership(AccountGroup.UUID uuid) {
     176         150 :       if (uuid != null) {
     177         150 :         for (Map.Entry<GroupBackend, GroupMembership> m : memberships.entrySet()) {
     178         150 :           if (m.getKey().handles(uuid)) {
     179         150 :             return m;
     180             :           }
     181         150 :         }
     182             :       }
     183           2 :       logger.atFine().log("Unknown GroupMembership for UUID: %s", uuid);
     184           2 :       return null;
     185             :     }
     186             : 
     187             :     @Override
     188             :     public boolean contains(AccountGroup.UUID uuid) {
     189         150 :       if (uuid == null) {
     190           0 :         return false;
     191             :       }
     192         150 :       Map.Entry<GroupBackend, GroupMembership> m = membership(uuid);
     193         150 :       if (m == null) {
     194           2 :         return false;
     195             :       }
     196         150 :       boolean contains = m.getValue().contains(uuid);
     197         150 :       containsCount.increment(name(m.getKey()), contains);
     198         150 :       return contains;
     199             :     }
     200             : 
     201             :     @Override
     202             :     public boolean containsAnyOf(Iterable<AccountGroup.UUID> uuids) {
     203             :       ListMultimap<Map.Entry<GroupBackend, GroupMembership>, AccountGroup.UUID> lookups =
     204         150 :           MultimapBuilder.hashKeys().arrayListValues().build();
     205         150 :       for (AccountGroup.UUID uuid : uuids) {
     206         150 :         if (uuid == null) {
     207           0 :           continue;
     208             :         }
     209         150 :         Map.Entry<GroupBackend, GroupMembership> m = membership(uuid);
     210         150 :         if (m == null) {
     211           1 :           continue;
     212             :         }
     213         150 :         lookups.put(m, uuid);
     214         150 :       }
     215         150 :       for (Map.Entry<GroupBackend, GroupMembership> groupBackends : lookups.asMap().keySet()) {
     216             : 
     217         150 :         GroupMembership m = groupBackends.getValue();
     218         150 :         Collection<AccountGroup.UUID> ids = lookups.asMap().get(groupBackends);
     219         150 :         if (ids.size() == 1) {
     220         150 :           if (m.contains(Iterables.getOnlyElement(ids))) {
     221         150 :             containsAnyCount.increment(name(groupBackends.getKey()), true);
     222         150 :             return true;
     223             :           }
     224           4 :         } else if (m.containsAnyOf(ids)) {
     225           2 :           containsAnyCount.increment(name(groupBackends.getKey()), true);
     226           2 :           return true;
     227             :         }
     228             :         // We would have returned if contains was true.
     229          85 :         containsAnyCount.increment(name(groupBackends.getKey()), false);
     230          85 :       }
     231         150 :       return false;
     232             :     }
     233             : 
     234             :     @Override
     235             :     public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> uuids) {
     236             :       ListMultimap<Map.Entry<GroupBackend, GroupMembership>, AccountGroup.UUID> lookups =
     237          20 :           MultimapBuilder.hashKeys().arrayListValues().build();
     238          20 :       for (AccountGroup.UUID uuid : uuids) {
     239           1 :         if (uuid == null) {
     240           0 :           continue;
     241             :         }
     242           1 :         Map.Entry<GroupBackend, GroupMembership> m = membership(uuid);
     243           1 :         if (m == null) {
     244           0 :           logger.atFine().log("Unknown GroupMembership for UUID: %s", uuid);
     245           0 :           continue;
     246             :         }
     247           1 :         lookups.put(m, uuid);
     248           1 :       }
     249          20 :       Set<AccountGroup.UUID> groups = new HashSet<>();
     250          20 :       for (Map.Entry<GroupBackend, GroupMembership> groupBackend : lookups.asMap().keySet()) {
     251           1 :         Set<AccountGroup.UUID> intersection =
     252           1 :             groupBackend.getValue().intersection(lookups.asMap().get(groupBackend));
     253           1 :         intersectionCount.increment(name(groupBackend.getKey()), intersection.size());
     254           1 :         groups.addAll(intersection);
     255           1 :       }
     256          20 :       return groups;
     257             :     }
     258             : 
     259             :     @Override
     260             :     public Set<AccountGroup.UUID> getKnownGroups() {
     261          21 :       Set<AccountGroup.UUID> groups = new HashSet<>();
     262          21 :       for (Map.Entry<GroupBackend, GroupMembership> entry : memberships.entrySet()) {
     263          21 :         Set<AccountGroup.UUID> knownGroups = entry.getValue().getKnownGroups();
     264          21 :         knownGroupsCount.increment(name(entry.getKey()), knownGroups.size());
     265          21 :         groups.addAll(knownGroups);
     266          21 :       }
     267          21 :       return groups;
     268             :     }
     269             :   }
     270             : 
     271             :   @Override
     272             :   public boolean isVisibleToAll(AccountGroup.UUID uuid) {
     273          37 :     for (PluginSetEntryContext<GroupBackend> c : backends) {
     274          37 :       if (Boolean.TRUE.equals(c.call(b -> b.handles(uuid)))) {
     275          37 :         return c.call(b -> b.isVisibleToAll(uuid));
     276             :       }
     277          37 :     }
     278           0 :     return false;
     279             :   }
     280             : 
     281             :   private static String name(GroupBackend backend) {
     282         150 :     if (backend == null) {
     283           0 :       return "none";
     284             :     }
     285         150 :     return backend.getClass().getSimpleName();
     286             :   }
     287             : 
     288             :   public static class ConfigCheck implements StartupCheck {
     289             :     private final Config cfg;
     290             :     private final UniversalGroupBackend universalGroupBackend;
     291             : 
     292             :     @Inject
     293         138 :     ConfigCheck(@GerritServerConfig Config cfg, UniversalGroupBackend groupBackend) {
     294         138 :       this.cfg = cfg;
     295         138 :       this.universalGroupBackend = groupBackend;
     296         138 :     }
     297             : 
     298             :     @Override
     299             :     public void check() throws StartupException {
     300         138 :       String invalid =
     301         138 :           cfg.getSubsections("groups").stream()
     302         138 :               .filter(
     303             :                   sub -> {
     304           1 :                     AccountGroup.UUID uuid = AccountGroup.uuid(sub);
     305           1 :                     GroupBackend groupBackend = universalGroupBackend.backend(uuid);
     306           1 :                     return groupBackend == null || groupBackend.get(uuid) == null;
     307             :                   })
     308         138 :               .map(u -> "'" + u + "'")
     309         138 :               .collect(joining(","));
     310             : 
     311         138 :       if (!invalid.isEmpty()) {
     312           0 :         throw new StartupException(
     313           0 :             String.format(
     314             :                 "Subsections for 'groups' in gerrit.config must be valid group"
     315             :                     + " UUIDs. The following group UUIDs could not be resolved: "
     316             :                     + invalid
     317             :                     + " Please remove/fix these 'groups' subsections in"
     318             :                     + " gerrit.config."));
     319             :       }
     320         138 :     }
     321             :   }
     322             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750