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