Merge "Optionally persist ExternalIdCache"
This commit is contained in:
		@@ -786,6 +786,7 @@ Default is 128 MiB per cache, except:
 | 
			
		||||
+
 | 
			
		||||
* `"change_notes"`: disk storage is disabled by default
 | 
			
		||||
* `"diff_summary"`: default is `1g` (1 GiB of disk space)
 | 
			
		||||
* `"external_ids_map"`: disk storage is disabled by default
 | 
			
		||||
 | 
			
		||||
+
 | 
			
		||||
If 0 or negative, disk storage for the cache is disabled.
 | 
			
		||||
@@ -862,8 +863,10 @@ of link:config-accounts.html#external-ids[all current external IDs]. The
 | 
			
		||||
cache may temporarily contain 2 entries, but the second one is promptly
 | 
			
		||||
expired.
 | 
			
		||||
+
 | 
			
		||||
It is not recommended to change the attributes of this cache away from
 | 
			
		||||
the defaults.
 | 
			
		||||
It is not recommended to change the in-memory attributes of this cache
 | 
			
		||||
away from the defaults. The cache may be persisted by setting
 | 
			
		||||
`diskLimit`, which is only recommended if cold start performance is
 | 
			
		||||
problematic.
 | 
			
		||||
 | 
			
		||||
cache `"git_tags"`::
 | 
			
		||||
+
 | 
			
		||||
 
 | 
			
		||||
@@ -15,12 +15,18 @@
 | 
			
		||||
package com.google.gerrit.server.account.externalids;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
 | 
			
		||||
import static java.util.stream.Collectors.toList;
 | 
			
		||||
 | 
			
		||||
import com.google.auto.value.AutoValue;
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.collect.ImmutableSetMultimap;
 | 
			
		||||
import com.google.common.collect.SetMultimap;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto;
 | 
			
		||||
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto.ExternalIdProto;
 | 
			
		||||
import com.google.gerrit.server.cache.serialize.CacheSerializer;
 | 
			
		||||
import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers;
 | 
			
		||||
import com.google.gerrit.server.cache.serialize.ProtoCacheSerializers.ObjectIdConverter;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
 | 
			
		||||
/** Cache value containing all external IDs. */
 | 
			
		||||
@@ -48,4 +54,59 @@ public abstract class AllExternalIds {
 | 
			
		||||
  public abstract ImmutableSetMultimap<Account.Id, ExternalId> byAccount();
 | 
			
		||||
 | 
			
		||||
  public abstract ImmutableSetMultimap<String, ExternalId> byEmail();
 | 
			
		||||
 | 
			
		||||
  enum Serializer implements CacheSerializer<AllExternalIds> {
 | 
			
		||||
    INSTANCE;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] serialize(AllExternalIds object) {
 | 
			
		||||
      ObjectIdConverter idConverter = ObjectIdConverter.create();
 | 
			
		||||
      AllExternalIdsProto.Builder allBuilder = AllExternalIdsProto.newBuilder();
 | 
			
		||||
      object
 | 
			
		||||
          .byAccount()
 | 
			
		||||
          .values()
 | 
			
		||||
          .stream()
 | 
			
		||||
          .map(extId -> toProto(idConverter, extId))
 | 
			
		||||
          .forEach(allBuilder::addExternalId);
 | 
			
		||||
      return ProtoCacheSerializers.toByteArray(allBuilder.build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ExternalIdProto toProto(ObjectIdConverter idConverter, ExternalId externalId) {
 | 
			
		||||
      ExternalIdProto.Builder b =
 | 
			
		||||
          ExternalIdProto.newBuilder()
 | 
			
		||||
              .setKey(externalId.key().get())
 | 
			
		||||
              .setAccountId(externalId.accountId().get());
 | 
			
		||||
      if (externalId.email() != null) {
 | 
			
		||||
        b.setEmail(externalId.email());
 | 
			
		||||
      }
 | 
			
		||||
      if (externalId.password() != null) {
 | 
			
		||||
        b.setPassword(externalId.password());
 | 
			
		||||
      }
 | 
			
		||||
      if (externalId.blobId() != null) {
 | 
			
		||||
        b.setBlobId(idConverter.toByteString(externalId.blobId()));
 | 
			
		||||
      }
 | 
			
		||||
      return b.build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AllExternalIds deserialize(byte[] in) {
 | 
			
		||||
      ObjectIdConverter idConverter = ObjectIdConverter.create();
 | 
			
		||||
      return create(
 | 
			
		||||
          ProtoCacheSerializers.parseUnchecked(AllExternalIdsProto.parser(), in)
 | 
			
		||||
              .getExternalIdList()
 | 
			
		||||
              .stream()
 | 
			
		||||
              .map(proto -> toExternalId(idConverter, proto))
 | 
			
		||||
              .collect(toList()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ExternalId toExternalId(ObjectIdConverter idConverter, ExternalIdProto proto) {
 | 
			
		||||
      return ExternalId.create(
 | 
			
		||||
          ExternalId.Key.parse(proto.getKey()),
 | 
			
		||||
          new Account.Id(proto.getAccountId()),
 | 
			
		||||
          // ExternalId treats null and empty strings the same, so no need to distinguish here.
 | 
			
		||||
          proto.getEmail(),
 | 
			
		||||
          proto.getPassword(),
 | 
			
		||||
          !proto.getBlobId().isEmpty() ? idConverter.fromByteString(proto.getBlobId()) : null);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server.account.externalids;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.server.account.externalids.ExternalIdCacheImpl.Loader;
 | 
			
		||||
import com.google.gerrit.server.cache.CacheModule;
 | 
			
		||||
import com.google.gerrit.server.cache.serialize.ObjectIdCacheSerializer;
 | 
			
		||||
import com.google.inject.TypeLiteral;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
@@ -23,7 +24,7 @@ import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
public class ExternalIdModule extends CacheModule {
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void configure() {
 | 
			
		||||
    cache(ExternalIdCacheImpl.CACHE_NAME, ObjectId.class, new TypeLiteral<AllExternalIds>() {})
 | 
			
		||||
    persist(ExternalIdCacheImpl.CACHE_NAME, ObjectId.class, new TypeLiteral<AllExternalIds>() {})
 | 
			
		||||
        // The cached data is potentially pretty large and we are always only interested
 | 
			
		||||
        // in the latest value. However, due to a race condition, it is possible for different
 | 
			
		||||
        // threads to observe different values of the meta ref, and hence request different keys
 | 
			
		||||
@@ -32,7 +33,11 @@ public class ExternalIdModule extends CacheModule {
 | 
			
		||||
        // memory.
 | 
			
		||||
        .maximumWeight(2)
 | 
			
		||||
        .expireFromMemoryAfterAccess(Duration.ofMinutes(1))
 | 
			
		||||
        .loader(Loader.class);
 | 
			
		||||
        .loader(Loader.class)
 | 
			
		||||
        .diskLimit(-1)
 | 
			
		||||
        .version(1)
 | 
			
		||||
        .keySerializer(ObjectIdCacheSerializer.INSTANCE)
 | 
			
		||||
        .valueSerializer(AllExternalIds.Serializer.INSTANCE);
 | 
			
		||||
 | 
			
		||||
    bind(ExternalIdCacheImpl.class);
 | 
			
		||||
    bind(ExternalIdCache.class).to(ExternalIdCacheImpl.class);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,143 @@
 | 
			
		||||
// Copyright (C) 2018 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.server.account.externalids;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.truth.Truth.assertThat;
 | 
			
		||||
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
 | 
			
		||||
import static com.google.gerrit.server.cache.testing.CacheSerializerTestUtil.byteString;
 | 
			
		||||
import static com.google.gerrit.server.cache.testing.SerializedClassSubject.assertThatSerializedClass;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
import com.google.common.collect.ImmutableSetMultimap;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.server.account.externalids.AllExternalIds.Serializer;
 | 
			
		||||
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto;
 | 
			
		||||
import com.google.gerrit.server.cache.proto.Cache.AllExternalIdsProto.ExternalIdProto;
 | 
			
		||||
import com.google.inject.TypeLiteral;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
public class AllExternalIdsTest {
 | 
			
		||||
  @Test
 | 
			
		||||
  public void serializeEmptyExternalIds() throws Exception {
 | 
			
		||||
    assertRoundTrip(allExternalIds(), AllExternalIdsProto.getDefaultInstance());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void serializeMultipleExternalIds() throws Exception {
 | 
			
		||||
    Account.Id accountId1 = new Account.Id(1001);
 | 
			
		||||
    Account.Id accountId2 = new Account.Id(1002);
 | 
			
		||||
    assertRoundTrip(
 | 
			
		||||
        allExternalIds(
 | 
			
		||||
            ExternalId.create("scheme1", "id1", accountId1),
 | 
			
		||||
            ExternalId.create("scheme2", "id2", accountId1),
 | 
			
		||||
            ExternalId.create("scheme2", "id3", accountId2),
 | 
			
		||||
            ExternalId.create("scheme3", "id4", accountId2)),
 | 
			
		||||
        AllExternalIdsProto.newBuilder()
 | 
			
		||||
            .addExternalId(
 | 
			
		||||
                ExternalIdProto.newBuilder().setKey("scheme1:id1").setAccountId(1001).build())
 | 
			
		||||
            .addExternalId(
 | 
			
		||||
                ExternalIdProto.newBuilder().setKey("scheme2:id2").setAccountId(1001).build())
 | 
			
		||||
            .addExternalId(
 | 
			
		||||
                ExternalIdProto.newBuilder().setKey("scheme2:id3").setAccountId(1002).build())
 | 
			
		||||
            .addExternalId(
 | 
			
		||||
                ExternalIdProto.newBuilder().setKey("scheme3:id4").setAccountId(1002).build())
 | 
			
		||||
            .build());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void serializeExternalIdWithEmail() throws Exception {
 | 
			
		||||
    assertRoundTrip(
 | 
			
		||||
        allExternalIds(ExternalId.createEmail(new Account.Id(1001), "foo@example.com")),
 | 
			
		||||
        AllExternalIdsProto.newBuilder()
 | 
			
		||||
            .addExternalId(
 | 
			
		||||
                ExternalIdProto.newBuilder()
 | 
			
		||||
                    .setKey("mailto:foo@example.com")
 | 
			
		||||
                    .setAccountId(1001)
 | 
			
		||||
                    .setEmail("foo@example.com"))
 | 
			
		||||
            .build());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void serializeExternalIdWithPassword() throws Exception {
 | 
			
		||||
    assertRoundTrip(
 | 
			
		||||
        allExternalIds(
 | 
			
		||||
            ExternalId.create("scheme", "id", new Account.Id(1001), null, "hashed password")),
 | 
			
		||||
        AllExternalIdsProto.newBuilder()
 | 
			
		||||
            .addExternalId(
 | 
			
		||||
                ExternalIdProto.newBuilder()
 | 
			
		||||
                    .setKey("scheme:id")
 | 
			
		||||
                    .setAccountId(1001)
 | 
			
		||||
                    .setPassword("hashed password"))
 | 
			
		||||
            .build());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void serializeExternalIdWithBlobId() throws Exception {
 | 
			
		||||
    assertRoundTrip(
 | 
			
		||||
        allExternalIds(
 | 
			
		||||
            ExternalId.create(
 | 
			
		||||
                ExternalId.create("scheme", "id", new Account.Id(1001)),
 | 
			
		||||
                ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"))),
 | 
			
		||||
        AllExternalIdsProto.newBuilder()
 | 
			
		||||
            .addExternalId(
 | 
			
		||||
                ExternalIdProto.newBuilder()
 | 
			
		||||
                    .setKey("scheme:id")
 | 
			
		||||
                    .setAccountId(1001)
 | 
			
		||||
                    .setBlobId(
 | 
			
		||||
                        byteString(
 | 
			
		||||
                            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
 | 
			
		||||
                            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef)))
 | 
			
		||||
            .build());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void allExternalIdsMethods() {
 | 
			
		||||
    assertThatSerializedClass(AllExternalIds.class)
 | 
			
		||||
        .hasAutoValueMethods(
 | 
			
		||||
            ImmutableMap.of(
 | 
			
		||||
                "byAccount",
 | 
			
		||||
                    new TypeLiteral<ImmutableSetMultimap<Account.Id, ExternalId>>() {}.getType(),
 | 
			
		||||
                "byEmail",
 | 
			
		||||
                    new TypeLiteral<ImmutableSetMultimap<String, ExternalId>>() {}.getType()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void externalIdMethods() {
 | 
			
		||||
    assertThatSerializedClass(ExternalId.class)
 | 
			
		||||
        .hasAutoValueMethods(
 | 
			
		||||
            ImmutableMap.of(
 | 
			
		||||
                "key", ExternalId.Key.class,
 | 
			
		||||
                "accountId", Account.Id.class,
 | 
			
		||||
                "email", String.class,
 | 
			
		||||
                "password", String.class,
 | 
			
		||||
                "blobId", ObjectId.class));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static AllExternalIds allExternalIds(ExternalId... externalIds) {
 | 
			
		||||
    return AllExternalIds.create(Arrays.asList(externalIds));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void assertRoundTrip(
 | 
			
		||||
      AllExternalIds allExternalIds, AllExternalIdsProto expectedProto) throws Exception {
 | 
			
		||||
    AllExternalIdsProto actualProto =
 | 
			
		||||
        AllExternalIdsProto.parseFrom(Serializer.INSTANCE.serialize(allExternalIds));
 | 
			
		||||
    assertThat(actualProto).ignoringRepeatedFieldOrder().isEqualTo(expectedProto);
 | 
			
		||||
    AllExternalIds actual =
 | 
			
		||||
        Serializer.INSTANCE.deserialize(Serializer.INSTANCE.serialize(allExternalIds));
 | 
			
		||||
    assertThat(actual).isEqualTo(allExternalIds);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -219,3 +219,18 @@ message TagSetHolderProto {
 | 
			
		||||
  }
 | 
			
		||||
  TagSetProto tags = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Serialized form of
 | 
			
		||||
// com.google.gerrit.server.account.externalids.AllExternalIds.
 | 
			
		||||
// Next ID: 2
 | 
			
		||||
message AllExternalIdsProto {
 | 
			
		||||
  // Next ID: 6
 | 
			
		||||
  message ExternalIdProto {
 | 
			
		||||
    string key = 1;
 | 
			
		||||
    int32 accountId = 2;
 | 
			
		||||
    string email = 3;
 | 
			
		||||
    string password = 4;
 | 
			
		||||
    bytes blobId = 5;
 | 
			
		||||
  }
 | 
			
		||||
  repeated ExternalIdProto external_id = 1;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user