LCOV - code coverage report
Current view: top level - server/account - GroupIncludeCacheImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 78 87 89.7 %
Date: 2022-11-19 15:00:39 Functions: 26 26 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2011 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.common.collect.ImmutableList.toImmutableList;
      18             : import static com.google.common.collect.ImmutableSet.toImmutableSet;
      19             : 
      20             : import com.google.common.cache.Cache;
      21             : import com.google.common.cache.CacheLoader;
      22             : import com.google.common.cache.LoadingCache;
      23             : import com.google.common.collect.ImmutableList;
      24             : import com.google.common.collect.ImmutableSet;
      25             : import com.google.common.flogger.FluentLogger;
      26             : import com.google.gerrit.entities.Account;
      27             : import com.google.gerrit.entities.AccountGroup;
      28             : import com.google.gerrit.entities.InternalGroup;
      29             : import com.google.gerrit.proto.Protos;
      30             : import com.google.gerrit.server.cache.CacheModule;
      31             : import com.google.gerrit.server.cache.proto.Cache.AllExternalGroupsProto;
      32             : import com.google.gerrit.server.cache.proto.Cache.AllExternalGroupsProto.ExternalGroupProto;
      33             : import com.google.gerrit.server.cache.serialize.CacheSerializer;
      34             : import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
      35             : import com.google.gerrit.server.group.db.Groups;
      36             : import com.google.gerrit.server.logging.Metadata;
      37             : import com.google.gerrit.server.logging.TraceContext;
      38             : import com.google.gerrit.server.logging.TraceContext.TraceTimer;
      39             : import com.google.gerrit.server.query.group.InternalGroupQuery;
      40             : import com.google.inject.Inject;
      41             : import com.google.inject.Module;
      42             : import com.google.inject.Provider;
      43             : import com.google.inject.Singleton;
      44             : import com.google.inject.TypeLiteral;
      45             : import com.google.inject.name.Named;
      46             : import java.util.Collection;
      47             : import java.util.Collections;
      48             : import java.util.concurrent.ExecutionException;
      49             : 
      50             : /** Tracks group inclusions in memory for efficient access. */
      51             : @Singleton
      52             : public class GroupIncludeCacheImpl implements GroupIncludeCache {
      53         152 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      54             : 
      55             :   private static final String PARENT_GROUPS_NAME = "groups_bysubgroup";
      56             :   private static final String GROUPS_WITH_MEMBER_NAME = "groups_bymember";
      57             :   private static final String EXTERNAL_NAME = "groups_external";
      58             :   private static final String PERSISTED_EXTERNAL_NAME = "groups_external_persisted";
      59             : 
      60             :   public static Module module() {
      61         152 :     return new CacheModule() {
      62             :       @Override
      63             :       protected void configure() {
      64         152 :         cache(
      65             :                 GROUPS_WITH_MEMBER_NAME,
      66             :                 Account.Id.class,
      67         152 :                 new TypeLiteral<ImmutableSet<AccountGroup.UUID>>() {})
      68         152 :             .loader(GroupsWithMemberLoader.class);
      69             : 
      70         152 :         cache(
      71             :                 PARENT_GROUPS_NAME,
      72             :                 AccountGroup.UUID.class,
      73         152 :                 new TypeLiteral<ImmutableList<AccountGroup.UUID>>() {})
      74         152 :             .loader(ParentGroupsLoader.class);
      75             : 
      76             :         /**
      77             :          * Splitting the groups external cache into 2 caches: The first one is in memory, used to
      78             :          * serve the callers and has a single constant key "EXTERNAL_NAME". The second one is
      79             :          * persisted, its key represents the groups' state in NoteDb. The in-memory cache is used on
      80             :          * top of the persisted cache to enhance performance because the cache's value is used on
      81             :          * every request to Gerrit, potentially many times per request and the key computation can
      82             :          * become expensive.
      83             :          */
      84         152 :         cache(EXTERNAL_NAME, String.class, new TypeLiteral<ImmutableList<AccountGroup.UUID>>() {})
      85         152 :             .loader(AllExternalInMemoryLoader.class);
      86             : 
      87         152 :         persist(
      88             :                 PERSISTED_EXTERNAL_NAME,
      89             :                 String.class,
      90         152 :                 new TypeLiteral<ImmutableList<AccountGroup.UUID>>() {})
      91         152 :             .diskLimit(-1)
      92         152 :             .version(1)
      93         152 :             .maximumWeight(0)
      94         152 :             .keySerializer(StringCacheSerializer.INSTANCE)
      95         152 :             .valueSerializer(ExternalGroupsSerializer.INSTANCE);
      96             : 
      97         152 :         bind(GroupIncludeCacheImpl.class);
      98         152 :         bind(GroupIncludeCache.class).to(GroupIncludeCacheImpl.class);
      99         152 :       }
     100             :     };
     101             :   }
     102             : 
     103             :   private final LoadingCache<Account.Id, ImmutableSet<AccountGroup.UUID>> groupsWithMember;
     104             :   private final LoadingCache<AccountGroup.UUID, ImmutableList<AccountGroup.UUID>> parentGroups;
     105             :   private final LoadingCache<String, ImmutableList<AccountGroup.UUID>> external;
     106             : 
     107             :   @Inject
     108             :   GroupIncludeCacheImpl(
     109             :       @Named(GROUPS_WITH_MEMBER_NAME)
     110             :           LoadingCache<Account.Id, ImmutableSet<AccountGroup.UUID>> groupsWithMember,
     111             :       @Named(PARENT_GROUPS_NAME)
     112             :           LoadingCache<AccountGroup.UUID, ImmutableList<AccountGroup.UUID>> parentGroups,
     113         151 :       @Named(EXTERNAL_NAME) LoadingCache<String, ImmutableList<AccountGroup.UUID>> external) {
     114         151 :     this.groupsWithMember = groupsWithMember;
     115         151 :     this.parentGroups = parentGroups;
     116         151 :     this.external = external;
     117         151 :   }
     118             : 
     119             :   @Override
     120             :   public Collection<AccountGroup.UUID> getGroupsWithMember(Account.Id memberId) {
     121             :     try {
     122          20 :       return groupsWithMember.get(memberId);
     123           0 :     } catch (ExecutionException e) {
     124           0 :       logger.atWarning().withCause(e).log("Cannot load groups containing %s as member", memberId);
     125           0 :       return ImmutableSet.of();
     126             :     }
     127             :   }
     128             : 
     129             :   @Override
     130             :   public Collection<AccountGroup.UUID> parentGroupsOf(AccountGroup.UUID groupId) {
     131             :     try {
     132          18 :       return parentGroups.get(groupId);
     133           0 :     } catch (ExecutionException e) {
     134           0 :       logger.atWarning().withCause(e).log("Cannot load included groups");
     135           0 :       return Collections.emptySet();
     136             :     }
     137             :   }
     138             : 
     139             :   @Override
     140             :   public void evictGroupsWithMember(Account.Id memberId) {
     141         151 :     if (memberId != null) {
     142         151 :       logger.atFine().log("Evict groups with member %d", memberId.get());
     143         151 :       groupsWithMember.invalidate(memberId);
     144             :     }
     145         151 :   }
     146             : 
     147             :   @Override
     148             :   public void evictParentGroupsOf(AccountGroup.UUID groupId) {
     149          33 :     if (groupId != null) {
     150          33 :       logger.atFine().log("Evict parent groups of %s", groupId.get());
     151          33 :       parentGroups.invalidate(groupId);
     152             : 
     153          33 :       if (!groupId.isInternalGroup()) {
     154           4 :         logger.atFine().log("Evict external group %s", groupId.get());
     155             :         /**
     156             :          * No need to invalidate the persistent cache, because this eviction will change the state
     157             :          * of NoteDb causing the persistent cache's loader to use a new key that doesn't exist in
     158             :          * its cache.n
     159             :          */
     160           4 :         external.invalidate(EXTERNAL_NAME);
     161             :       }
     162             :     }
     163          33 :   }
     164             : 
     165             :   @Override
     166             :   public Collection<AccountGroup.UUID> allExternalMembers() {
     167             :     try {
     168          20 :       return external.get(EXTERNAL_NAME);
     169           0 :     } catch (ExecutionException e) {
     170           0 :       logger.atWarning().withCause(e).log("Cannot load set of non-internal groups");
     171           0 :       return ImmutableList.of();
     172             :     }
     173             :   }
     174             : 
     175             :   static class GroupsWithMemberLoader
     176             :       extends CacheLoader<Account.Id, ImmutableSet<AccountGroup.UUID>> {
     177             :     private final Provider<InternalGroupQuery> groupQueryProvider;
     178             : 
     179             :     @Inject
     180         152 :     GroupsWithMemberLoader(Provider<InternalGroupQuery> groupQueryProvider) {
     181         152 :       this.groupQueryProvider = groupQueryProvider;
     182         152 :     }
     183             : 
     184             :     @Override
     185             :     public ImmutableSet<AccountGroup.UUID> load(Account.Id memberId) {
     186          20 :       try (TraceTimer timer =
     187          20 :           TraceContext.newTimer(
     188          20 :               "Loading groups with member", Metadata.builder().accountId(memberId.get()).build())) {
     189          20 :         return groupQueryProvider.get().byMember(memberId).stream()
     190          20 :             .map(InternalGroup::getGroupUUID)
     191          20 :             .collect(toImmutableSet());
     192             :       }
     193             :     }
     194             :   }
     195             : 
     196             :   static class ParentGroupsLoader
     197             :       extends CacheLoader<AccountGroup.UUID, ImmutableList<AccountGroup.UUID>> {
     198             :     private final Provider<InternalGroupQuery> groupQueryProvider;
     199             : 
     200             :     @Inject
     201         152 :     ParentGroupsLoader(Provider<InternalGroupQuery> groupQueryProvider) {
     202         152 :       this.groupQueryProvider = groupQueryProvider;
     203         152 :     }
     204             : 
     205             :     @Override
     206             :     public ImmutableList<AccountGroup.UUID> load(AccountGroup.UUID key) {
     207          18 :       try (TraceTimer timer =
     208          18 :           TraceContext.newTimer(
     209          18 :               "Loading parent groups", Metadata.builder().groupUuid(key.get()).build())) {
     210          18 :         return groupQueryProvider.get().bySubgroup(key).stream()
     211          18 :             .map(InternalGroup::getGroupUUID)
     212          18 :             .collect(toImmutableList());
     213             :       }
     214             :     }
     215             :   }
     216             : 
     217             :   static class AllExternalInMemoryLoader
     218             :       extends CacheLoader<String, ImmutableList<AccountGroup.UUID>> {
     219             :     private final Cache<String, ImmutableList<AccountGroup.UUID>> persisted;
     220             :     private final GroupsSnapshotReader snapshotReader;
     221             :     private final Groups groups;
     222             : 
     223             :     @Inject
     224             :     AllExternalInMemoryLoader(
     225             :         @Named(PERSISTED_EXTERNAL_NAME) Cache<String, ImmutableList<AccountGroup.UUID>> persisted,
     226             :         GroupsSnapshotReader snapshotReader,
     227         152 :         Groups groups) {
     228         152 :       this.persisted = persisted;
     229         152 :       this.snapshotReader = snapshotReader;
     230         152 :       this.groups = groups;
     231         152 :     }
     232             : 
     233             :     @Override
     234             :     public ImmutableList<AccountGroup.UUID> load(String key) throws Exception {
     235          20 :       GroupsSnapshotReader.Snapshot snapshot = snapshotReader.getSnapshot();
     236          20 :       return persisted.get(
     237          20 :           snapshot.hash(),
     238             :           () -> {
     239          20 :             try (TraceTimer timer = TraceContext.newTimer("Loading all external groups")) {
     240          20 :               return groups.getExternalGroups(snapshot.groupsRefs()).collect(toImmutableList());
     241             :             }
     242             :           });
     243             :     }
     244             :   }
     245             : 
     246         153 :   public enum ExternalGroupsSerializer
     247             :       implements CacheSerializer<ImmutableList<AccountGroup.UUID>> {
     248         153 :     INSTANCE;
     249             : 
     250             :     @Override
     251             :     public byte[] serialize(ImmutableList<AccountGroup.UUID> object) {
     252           1 :       AllExternalGroupsProto.Builder allBuilder = AllExternalGroupsProto.newBuilder();
     253           1 :       object.stream()
     254           1 :           .map(group -> ExternalGroupProto.newBuilder().setGroupUuid(group.get()).build())
     255           1 :           .forEach(allBuilder::addExternalGroup);
     256           1 :       return Protos.toByteArray(allBuilder.build());
     257             :     }
     258             : 
     259             :     @Override
     260             :     public ImmutableList<AccountGroup.UUID> deserialize(byte[] in) {
     261           1 :       return Protos.parseUnchecked(AllExternalGroupsProto.parser(), in).getExternalGroupList()
     262           1 :           .stream()
     263           1 :           .map(groupProto -> AccountGroup.UUID.parse(groupProto.getGroupUuid()))
     264           1 :           .collect(toImmutableList());
     265             :     }
     266             :   }
     267             : }

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