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 : }