LCOV - code coverage report
Current view: top level - server/index/account - StalenessChecker.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 67 72 93.1 %
Date: 2022-11-19 15:00:39 Functions: 4 4 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2017 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.index.account;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : import static java.nio.charset.StandardCharsets.UTF_8;
      19             : import static java.util.Objects.requireNonNull;
      20             : 
      21             : import com.google.common.base.Splitter;
      22             : import com.google.common.collect.ImmutableSet;
      23             : import com.google.common.collect.ListMultimap;
      24             : import com.google.common.collect.MultimapBuilder;
      25             : import com.google.gerrit.entities.Account;
      26             : import com.google.gerrit.entities.Project;
      27             : import com.google.gerrit.entities.RefNames;
      28             : import com.google.gerrit.index.IndexConfig;
      29             : import com.google.gerrit.index.QueryOptions;
      30             : import com.google.gerrit.index.RefState;
      31             : import com.google.gerrit.index.query.FieldBundle;
      32             : import com.google.gerrit.server.account.externalids.ExternalId;
      33             : import com.google.gerrit.server.account.externalids.ExternalIds;
      34             : import com.google.gerrit.server.config.AllUsersName;
      35             : import com.google.gerrit.server.config.AllUsersNameProvider;
      36             : import com.google.gerrit.server.git.GitRepositoryManager;
      37             : import com.google.gerrit.server.index.IndexUtils;
      38             : import com.google.gerrit.server.index.StalenessCheckResult;
      39             : import com.google.inject.Inject;
      40             : import com.google.inject.Singleton;
      41             : import java.io.IOException;
      42             : import java.util.List;
      43             : import java.util.Map;
      44             : import java.util.Optional;
      45             : import java.util.Set;
      46             : import org.eclipse.jgit.lib.ObjectId;
      47             : import org.eclipse.jgit.lib.Ref;
      48             : import org.eclipse.jgit.lib.Repository;
      49             : 
      50             : /**
      51             :  * Checks if documents in the account index are stale.
      52             :  *
      53             :  * <p>An index document is considered stale if the stored ref state differs from the SHA1 of the
      54             :  * user branch or if the stored external ID states don't match with the external IDs of the account
      55             :  * from the refs/meta/external-ids branch.
      56             :  */
      57             : @Singleton
      58             : public class StalenessChecker {
      59         151 :   public static final ImmutableSet<String> FIELDS =
      60         151 :       ImmutableSet.of(
      61         151 :           AccountField.ID_FIELD_SPEC.getName(),
      62         151 :           AccountField.REF_STATE_SPEC.getName(),
      63         151 :           AccountField.EXTERNAL_ID_STATE_SPEC.getName());
      64             : 
      65         151 :   public static final ImmutableSet<String> FIELDS2 =
      66         151 :       ImmutableSet.of(
      67         151 :           AccountField.ID_STR_FIELD_SPEC.getName(),
      68         151 :           AccountField.REF_STATE_SPEC.getName(),
      69         151 :           AccountField.EXTERNAL_ID_STATE_SPEC.getName());
      70             : 
      71             :   private final AccountIndexCollection indexes;
      72             :   private final GitRepositoryManager repoManager;
      73             :   private final AllUsersName allUsersName;
      74             :   private final ExternalIds externalIds;
      75             :   private final IndexConfig indexConfig;
      76             : 
      77             :   @Inject
      78             :   StalenessChecker(
      79             :       AccountIndexCollection indexes,
      80             :       GitRepositoryManager repoManager,
      81             :       AllUsersName allUsersName,
      82             :       ExternalIds externalIds,
      83         151 :       IndexConfig indexConfig) {
      84         151 :     this.indexes = indexes;
      85         151 :     this.repoManager = repoManager;
      86         151 :     this.allUsersName = allUsersName;
      87         151 :     this.externalIds = externalIds;
      88         151 :     this.indexConfig = indexConfig;
      89         151 :   }
      90             : 
      91             :   public StalenessCheckResult check(Account.Id id) throws IOException {
      92           1 :     AccountIndex i = indexes.getSearchIndex();
      93           1 :     if (i == null) {
      94             :       // No index; caller couldn't do anything if it is stale.
      95           0 :       return StalenessCheckResult.notStale();
      96             :     }
      97           1 :     if (!i.getSchema().hasField(AccountField.REF_STATE_SPEC)
      98           1 :         || !i.getSchema().hasField(AccountField.EXTERNAL_ID_STATE_SPEC)) {
      99             :       // Index version not new enough for this check.
     100           0 :       return StalenessCheckResult.notStale();
     101             :     }
     102             : 
     103           1 :     boolean useLegacyNumericFields = i.getSchema().hasField(AccountField.ID_FIELD_SPEC);
     104           1 :     ImmutableSet<String> fields = useLegacyNumericFields ? FIELDS : FIELDS2;
     105           1 :     Optional<FieldBundle> result =
     106           1 :         i.getRaw(
     107             :             id,
     108           1 :             QueryOptions.create(
     109           1 :                 indexConfig, 0, 1, IndexUtils.accountFields(fields, useLegacyNumericFields)));
     110           1 :     if (!result.isPresent()) {
     111             :       // The document is missing in the index.
     112           1 :       try (Repository repo = repoManager.openRepository(allUsersName)) {
     113           1 :         Ref ref = repo.exactRef(RefNames.refsUsers(id));
     114             : 
     115             :         // Stale if the account actually exists.
     116           1 :         if (ref == null) {
     117           1 :           return StalenessCheckResult.notStale();
     118             :         }
     119           0 :         return StalenessCheckResult.stale(
     120             :             "Document missing in index, but found %s in the repo", ref);
     121           1 :       }
     122             :     }
     123             : 
     124           1 :     Iterable<byte[]> refStates =
     125           1 :         result.get().<Iterable<byte[]>>getValue(AccountField.REF_STATE_SPEC);
     126           1 :     for (Map.Entry<Project.NameKey, RefState> e : RefState.parseStates(refStates).entries()) {
     127             :       // Custom All-Users repository names are not indexed. Instead, the default name is used.
     128             :       // Therefore, defer to the currently configured All-Users name.
     129             :       Project.NameKey repoName =
     130           1 :           e.getKey().get().equals(AllUsersNameProvider.DEFAULT) ? allUsersName : e.getKey();
     131           1 :       try (Repository repo = repoManager.openRepository(repoName)) {
     132           1 :         if (!e.getValue().match(repo)) {
     133           1 :           return StalenessCheckResult.stale(
     134             :               "Ref was modified since the account was indexed (%s != %s)",
     135           1 :               e.getValue(), repo.exactRef(e.getValue().ref()));
     136             :         }
     137           1 :       }
     138           1 :     }
     139             : 
     140           1 :     Set<ExternalId> extIds = externalIds.byAccount(id);
     141             : 
     142           1 :     ListMultimap<ObjectId, ObjectId> extIdStates =
     143           1 :         parseExternalIdStates(
     144           1 :             result.get().<Iterable<byte[]>>getValue(AccountField.EXTERNAL_ID_STATE_SPEC));
     145           1 :     if (extIdStates.size() != extIds.size()) {
     146           1 :       return StalenessCheckResult.stale(
     147             :           "External IDs of the account were modified since the account was indexed. (%s != %s)",
     148           1 :           extIdStates.size(), extIds.size());
     149             :     }
     150           1 :     for (ExternalId extId : extIds) {
     151           1 :       if (!extIdStates.containsKey(extId.key().sha1())) {
     152           0 :         return StalenessCheckResult.stale("External ID missing: %s", extId.key().sha1());
     153             :       }
     154           1 :       if (!extIdStates.containsEntry(extId.key().sha1(), extId.blobId())) {
     155           1 :         return StalenessCheckResult.stale(
     156             :             "External ID has unexpected value. (%s != %s)",
     157           1 :             extIdStates.get(extId.key().sha1()), extId.blobId());
     158             :       }
     159           1 :     }
     160             : 
     161           1 :     return StalenessCheckResult.notStale();
     162             :   }
     163             : 
     164             :   public static ListMultimap<ObjectId, ObjectId> parseExternalIdStates(
     165             :       Iterable<byte[]> extIdStates) {
     166           1 :     ListMultimap<ObjectId, ObjectId> result = MultimapBuilder.hashKeys().arrayListValues().build();
     167             : 
     168           1 :     if (extIdStates == null) {
     169           0 :       return result;
     170             :     }
     171             : 
     172           1 :     for (byte[] b : extIdStates) {
     173           1 :       requireNonNull(b, "invalid external ID state");
     174           1 :       String s = new String(b, UTF_8);
     175           1 :       List<String> parts = Splitter.on(':').splitToList(s);
     176           1 :       checkState(parts.size() == 2, "invalid external ID state: %s", s);
     177           1 :       result.put(ObjectId.fromString(parts.get(0)), ObjectId.fromString(parts.get(1)));
     178           1 :     }
     179           1 :     return result;
     180             :   }
     181             : }

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