Add cache for external ids

Introducing a cache for external ids is a preparation step for moving
the external ids from ReviewDb into NoteDb. For NoteDb it is planned
to store the external ids in a git note branch, where the note keys
are the sha1's of the external ids and the note values contain the
external id, the account id and optionally email and password. With
this format we can easily lookup external ids by the external id, but
listing all external ids of an account requires parsing all external
ids. Looking up the external ids of an account is possible from the
account index, however for reindexing an account we would still need
to lookup all external ids of the account from git. Having a cache for
the external ids ensures that the external ids are only loaded once
from git. If there is any update to external ids, we take care to
update the cache as well. For this change it means that all code that
modifies external ids must do an extra call to update the external id
cache. This is not optimal since updating the cache can be easily
forgotten. This is why the follow-up change cleans this up by
introducing a dedicated class that handles all external id updates and
then this is the only class that must take care to update the external
id cache.

Change-Id: I9ea979c646cddb9b39e723de5c061a70a2ce6fd6
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2016-12-21 14:34:32 +01:00
parent 57e0378dab
commit 2869caaf70
19 changed files with 395 additions and 62 deletions

View File

@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.index.account.AccountIndexer;
@@ -56,6 +57,7 @@ public class AccountCreator {
private final AccountCache accountCache;
private final AccountByEmailCache byEmailCache;
private final AccountIndexer indexer;
private final ExternalIdCache externalIdCache;
@Inject
AccountCreator(SchemaFactory<ReviewDb> schema,
@@ -64,7 +66,8 @@ public class AccountCreator {
SshKeyCache sshKeyCache,
AccountCache accountCache,
AccountByEmailCache byEmailCache,
AccountIndexer indexer) {
AccountIndexer indexer,
ExternalIdCache externalIdCache) {
accounts = new HashMap<>();
reviewDbProvider = schema;
this.authorizedKeys = authorizedKeys;
@@ -73,6 +76,7 @@ public class AccountCreator {
this.accountCache = accountCache;
this.byEmailCache = byEmailCache;
this.indexer = indexer;
this.externalIdCache = externalIdCache;
}
public synchronized TestAccount create(String username, String email,
@@ -90,11 +94,13 @@ public class AccountCreator {
String httpPass = "http-pass";
extUser.setPassword(httpPass);
db.accountExternalIds().insert(Collections.singleton(extUser));
externalIdCache.onCreate(extUser);
if (email != null) {
AccountExternalId extMailto = new AccountExternalId(id, getEmailKey(email));
extMailto.setEmailAddress(email);
db.accountExternalIds().insert(Collections.singleton(extMailto));
externalIdCache.onCreate(extMailto);
}
Account a = new Account(id, TimeUtil.nowTs());

View File

@@ -61,6 +61,7 @@ import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.config.AllUsersName;
@@ -112,6 +113,9 @@ public class AccountIT extends AbstractDaemonTest {
@Inject
private AllUsersName allUsers;
@Inject
private ExternalIdCache externalIdCache;
private List<AccountExternalId> savedExternalIds;
@Before
@@ -127,10 +131,18 @@ public class AccountIT extends AbstractDaemonTest {
// savedExternalIds is null when we don't run SSH tests and the assume in
// @Before in AbstractDaemonTest prevents this class' @Before method from
// being executed.
db.accountExternalIds().delete(getExternalIds(admin));
db.accountExternalIds().delete(getExternalIds(user));
Collection<AccountExternalId> adminExtIds = getExternalIds(admin);
db.accountExternalIds().delete(adminExtIds);
externalIdCache.onRemove(adminExtIds);
Collection<AccountExternalId> userExtIds = getExternalIds(user);
db.accountExternalIds().delete(userExtIds);
externalIdCache.onRemove(userExtIds);
db.accountExternalIds().insert(savedExternalIds);
externalIdCache.onCreate(savedExternalIds);
}
accountCache.evict(admin.getId());
accountCache.evict(user.getId());
}
@@ -595,6 +607,7 @@ public class AccountIT extends AbstractDaemonTest {
user.getId(), new AccountExternalId.Key("foo:myId"));
db.accountExternalIds().insert(Collections.singleton(extId));
externalIdCache.onCreate(extId);
accountCache.evict(user.getId());
TestKey key = validKeyWithSecondUserId();
@@ -791,8 +804,9 @@ public class AccountIT extends AbstractDaemonTest {
Account.Id currAccountId = atrScope.get().getUser().getAccountId();
Iterable<String> expectedFps = expected.transform(
k -> BaseEncoding.base16().encode(k.getPublicKey().getFingerprint()));
Iterable<String> actualFps = GpgKeys.getGpgExtIds(db, currAccountId)
.transform(AccountExternalId::getSchemeRest);
Iterable<String> actualFps =
GpgKeys.getGpgExtIds(externalIdCache, currAccountId)
.transform(AccountExternalId::getSchemeRest);
assertThat(actualFps)
.named("external IDs in database")
.containsExactlyElementsIn(expectedFps);
@@ -825,6 +839,7 @@ public class AccountIT extends AbstractDaemonTest {
account.getId(), new AccountExternalId.Key(name("test"), email));
extId.setEmailAddress(email);
db.accountExternalIds().insert(Collections.singleton(extId));
externalIdCache.onCreate(extId);
// Clear saved AccountState and AccountExternalIds.
accountCache.evict(account.getId());
setApiUser(account);

View File

@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -47,16 +48,19 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
private final Provider<ReviewDb> db;
private final Provider<PublicKeyStore> storeProvider;
private final AccountCache accountCache;
private final ExternalIdCache externalIdCache;
@Inject
DeleteGpgKey(@GerritPersonIdent Provider<PersonIdent> serverIdent,
Provider<ReviewDb> db,
Provider<PublicKeyStore> storeProvider,
AccountCache accountCache) {
AccountCache accountCache,
ExternalIdCache externalIdCache) {
this.serverIdent = serverIdent;
this.db = db;
this.storeProvider = storeProvider;
this.accountCache = accountCache;
this.externalIdCache = externalIdCache;
}
@Override
@@ -68,6 +72,7 @@ public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
AccountExternalId.SCHEME_GPGKEY,
BaseEncoding.base16().encode(key.getFingerprint()));
db.get().accountExternalIds().deleteKeys(Collections.singleton(extIdKey));
externalIdCache.onRemove(rsrc.getUser().getAccountId(), extIdKey);
accountCache.evict(rsrc.getUser().getAccountId());
try (PublicKeyStore store = storeProvider.get()) {

View File

@@ -38,9 +38,9 @@ import com.google.gerrit.gpg.PublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -69,22 +69,22 @@ public class GpgKeys implements
public static String MIME_TYPE = "application/pgp-keys";
private final DynamicMap<RestView<GpgKey>> views;
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> self;
private final Provider<PublicKeyStore> storeProvider;
private final GerritPublicKeyChecker.Factory checkerFactory;
private final ExternalIdCache externalIdCache;
@Inject
GpgKeys(DynamicMap<RestView<GpgKey>> views,
Provider<ReviewDb> db,
Provider<CurrentUser> self,
Provider<PublicKeyStore> storeProvider,
GerritPublicKeyChecker.Factory checkerFactory) {
GerritPublicKeyChecker.Factory checkerFactory,
ExternalIdCache externalIdCache) {
this.views = views;
this.db = db;
this.self = self;
this.storeProvider = storeProvider;
this.checkerFactory = checkerFactory;
this.externalIdCache = externalIdCache;
}
@Override
@@ -208,15 +208,14 @@ public class GpgKeys implements
}
@VisibleForTesting
public static FluentIterable<AccountExternalId> getGpgExtIds(ReviewDb db,
Account.Id accountId) throws OrmException {
return FluentIterable.from(db.accountExternalIds().byAccount(accountId))
public static FluentIterable<AccountExternalId> getGpgExtIds(
ExternalIdCache externalIdCache, Account.Id accountId) {
return FluentIterable.from(externalIdCache.byAccount(accountId))
.filter(in -> in.isScheme(SCHEME_GPGKEY));
}
private Iterable<AccountExternalId> getGpgExtIds(AccountResource rsrc)
throws OrmException {
return getGpgExtIds(db.get(), rsrc.getUser().getAccountId());
private Iterable<AccountExternalId> getGpgExtIds(AccountResource rsrc) {
return getGpgExtIds(externalIdCache, rsrc.getUser().getAccountId());
}
private static long keyId(byte[] fp) {

View File

@@ -17,11 +17,11 @@ package com.google.gerrit.gpg.server;
import static com.google.gerrit.gpg.PublicKeyStore.keyIdToString;
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -47,6 +47,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gerrit.server.mail.send.AddKeySender;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gwtorm.server.OrmException;
@@ -90,6 +91,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
private final AddKeySender.Factory addKeyFactory;
private final AccountCache accountCache;
private final Provider<InternalAccountQuery> accountQueryProvider;
private final ExternalIdCache externalIdCache;
@Inject
PostGpgKeys(@GerritPersonIdent Provider<PersonIdent> serverIdent,
@@ -99,7 +101,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
GerritPublicKeyChecker.Factory checkerFactory,
AddKeySender.Factory addKeyFactory,
AccountCache accountCache,
Provider<InternalAccountQuery> accountQueryProvider) {
Provider<InternalAccountQuery> accountQueryProvider,
ExternalIdCache externalIdCache) {
this.serverIdent = serverIdent;
this.db = db;
this.self = self;
@@ -108,6 +111,7 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
this.addKeyFactory = addKeyFactory;
this.accountCache = accountCache;
this.accountQueryProvider = accountQueryProvider;
this.externalIdCache = externalIdCache;
}
@Override
@@ -117,7 +121,8 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
GpgKeys.checkVisible(self, rsrc);
List<AccountExternalId> existingExtIds =
GpgKeys.getGpgExtIds(db.get(), rsrc.getUser().getAccountId()).toList();
GpgKeys.getGpgExtIds(externalIdCache,
rsrc.getUser().getAccountId()).toList();
try (PublicKeyStore store = storeProvider.get()) {
Set<Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
@@ -142,9 +147,13 @@ public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
storeKeys(rsrc, newKeys, toRemove);
if (!newExtIds.isEmpty()) {
db.get().accountExternalIds().insert(newExtIds);
externalIdCache.onCreate(newExtIds);
}
db.get().accountExternalIds().deleteKeys(
Iterables.transform(toRemove, fp -> toExtIdKey(fp.get())));
List<AccountExternalId.Key> extIdKeysToRemove =
toRemove.stream().map(fp -> toExtIdKey(fp.get())).collect(toList());
db.get().accountExternalIds().deleteKeys(extIdKeysToRemove);
externalIdCache.onRemove(rsrc.getUser().getAccountId(), extIdKeysToRemove);
accountCache.evict(rsrc.getUser().getAccountId());
return toJson(newKeys, toRemove, store, rsrc.getUser());
}

View File

@@ -41,6 +41,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -69,6 +70,7 @@ import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -95,6 +97,9 @@ public class GerritPublicKeyCheckerTest {
@Inject
private ThreadLocalRequestContext requestContext;
@Inject
private ExternalIdCache externalIdCache;
private LifecycleManager lifecycle;
private ReviewDb db;
private Account.Id userId;
@@ -229,8 +234,10 @@ public class GerritPublicKeyCheckerTest {
@Test
public void noExternalIds() throws Exception {
db.accountExternalIds().delete(
db.accountExternalIds().byAccount(user.getAccountId()));
Collection<AccountExternalId> extIds =
externalIdCache.byAccount(user.getAccountId());
db.accountExternalIds().delete(extIds);
externalIdCache.onRemove(extIds);
reloadUser();
TestKey key = validKeyWithSecondUserId();
@@ -248,9 +255,10 @@ public class GerritPublicKeyCheckerTest {
checker.check(key.getPublicKey()), Status.BAD,
"Key is not associated with any users");
db.accountExternalIds().insert(Collections.singleton(
new AccountExternalId(
user.getAccountId(), toExtIdKey(key.getPublicKey()))));
AccountExternalId extId = new AccountExternalId(user.getAccountId(),
toExtIdKey(key.getPublicKey()));
db.accountExternalIds().insert(Collections.singleton(extId));
externalIdCache.onCreate(extId);
reloadUser();
assertProblems(
checker.check(key.getPublicKey()), Status.BAD,
@@ -427,6 +435,7 @@ public class GerritPublicKeyCheckerTest {
assertThat(store.save(cb)).isAnyOf(NEW, FAST_FORWARD, FORCED);
db.accountExternalIds().insert(newExtIds);
externalIdCache.onCreate(newExtIds);
accountCache.evict(user.getAccountId());
}
@@ -459,6 +468,7 @@ public class GerritPublicKeyCheckerTest {
extId.setEmailAddress(email);
}
db.accountExternalIds().insert(Collections.singleton(extId));
externalIdCache.onCreate(extId);
reloadUser();
}
}

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -42,21 +43,24 @@ class DeleteExternalIds extends Handler<Set<AccountExternalId.Key>> {
private final ExternalIdDetailFactory detailFactory;
private final AccountByEmailCache byEmailCache;
private final AccountCache accountCache;
private final ExternalIdCache externalIdCache;
private final Set<AccountExternalId.Key> keys;
@Inject
DeleteExternalIds(final ReviewDb db, final IdentifiedUser user,
final ExternalIdDetailFactory detailFactory,
final AccountByEmailCache byEmailCache, final AccountCache accountCache,
@Assisted final Set<AccountExternalId.Key> keys) {
DeleteExternalIds(ReviewDb db,
IdentifiedUser user,
ExternalIdDetailFactory detailFactory,
AccountByEmailCache byEmailCache,
AccountCache accountCache,
ExternalIdCache externalIdCache,
@Assisted Set<AccountExternalId.Key> keys) {
this.db = db;
this.user = user;
this.detailFactory = detailFactory;
this.byEmailCache = byEmailCache;
this.accountCache = accountCache;
this.externalIdCache = externalIdCache;
this.keys = keys;
}
@@ -74,6 +78,7 @@ class DeleteExternalIds extends Handler<Set<AccountExternalId.Key>> {
if (!toDelete.isEmpty()) {
db.accountExternalIds().delete(toDelete);
externalIdCache.onRemove(toDelete);
accountCache.evict(user.getAccountId());
for (AccountExternalId e : toDelete) {
byEmailCache.evict(e.getEmailAddress());

View File

@@ -20,12 +20,13 @@ import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.ExternalIdCache;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -34,25 +35,27 @@ class ExternalIdDetailFactory extends Handler<List<AccountExternalId>> {
ExternalIdDetailFactory create();
}
private final ReviewDb db;
private final IdentifiedUser user;
private final AuthConfig authConfig;
private final DynamicItem<WebSession> session;
private final ExternalIdCache externalIdCache;
@Inject
ExternalIdDetailFactory(final ReviewDb db, final IdentifiedUser user,
final AuthConfig authConfig, final DynamicItem<WebSession> session) {
this.db = db;
ExternalIdDetailFactory(IdentifiedUser user,
AuthConfig authConfig,
DynamicItem<WebSession> session,
ExternalIdCache externalIdCache) {
this.user = user;
this.authConfig = authConfig;
this.session = session;
this.externalIdCache = externalIdCache;
}
@Override
public List<AccountExternalId> call() throws OrmException {
final AccountExternalId.Key last = session.get().getLastLoginExternalId();
final List<AccountExternalId> ids =
db.accountExternalIds().byAccount(user.getAccountId()).toList();
AccountExternalId.Key last = session.get().getLastLoginExternalId();
List<AccountExternalId> ids =
new ArrayList<>(externalIdCache.byAccount(user.getAccountId()));
for (final AccountExternalId e : ids) {
e.setTrusted(authConfig.isIdentityTrustable(Collections.singleton(e)));

View File

@@ -33,6 +33,7 @@ import com.google.gerrit.server.account.AccountVisibility;
import com.google.gerrit.server.account.AccountVisibilityProvider;
import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.ExternalIdCacheImpl;
import com.google.gerrit.server.account.FakeRealm;
import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.GroupIncludeCacheImpl;
@@ -153,6 +154,7 @@ public class BatchProgramModule extends FactoryModule {
install(new PrologModule());
install(AccountByEmailCacheImpl.module());
install(AccountCacheImpl.module());
install(ExternalIdCacheImpl.module());
install(GroupCacheImpl.module());
install(GroupIncludeCacheImpl.module());
install(ProjectCacheImpl.module());

View File

@@ -18,6 +18,8 @@ import com.google.gerrit.extensions.client.AuthType;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.StringKey;
import java.util.Objects;
/** Association of an external account identifier to a local {@link Account}. */
public final class AccountExternalId {
/**
@@ -167,4 +169,21 @@ public final class AccountExternalId {
public void setCanDelete(final boolean t) {
canDelete = t;
}
@Override
public boolean equals(Object o) {
if (o instanceof AccountExternalId) {
AccountExternalId extId = (AccountExternalId) o;
return Objects.equals(key, extId.key)
&& Objects.equals(accountId, extId.accountId)
&& Objects.equals(emailAddress, extId.emailAddress)
&& Objects.equals(password, extId.password);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(key, accountId, emailAddress, password);
}
}

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.reviewdb.server;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gwtorm.server.Access;
import com.google.gwtorm.server.OrmException;
@@ -28,9 +27,6 @@ public interface AccountExternalIdAccess extends
@PrimaryKey("key")
AccountExternalId get(AccountExternalId.Key key) throws OrmException;
@Query("WHERE accountId = ?")
ResultSet<AccountExternalId> byAccount(Account.Id id) throws OrmException;
@Query
ResultSet<AccountExternalId> all() throws OrmException;
}

View File

@@ -155,6 +155,7 @@ public class AccountCacheImpl implements AccountCache {
private final GeneralPreferencesLoader loader;
private final LoadingCache<String, Optional<Account.Id>> byName;
private final Provider<WatchConfig.Accessor> watchConfig;
private final ExternalIdCache externalIdCache;
@Inject
ByIdLoader(SchemaFactory<ReviewDb> sf,
@@ -162,12 +163,14 @@ public class AccountCacheImpl implements AccountCache {
GeneralPreferencesLoader loader,
@Named(BYUSER_NAME) LoadingCache<String,
Optional<Account.Id>> byUsername,
Provider<WatchConfig.Accessor> watchConfig) {
Provider<WatchConfig.Accessor> watchConfig,
ExternalIdCache externalIdCache) {
this.schema = sf;
this.groupCache = groupCache;
this.loader = loader;
this.byName = byUsername;
this.watchConfig = watchConfig;
this.externalIdCache = externalIdCache;
}
@Override
@@ -191,8 +194,7 @@ public class AccountCacheImpl implements AccountCache {
}
Collection<AccountExternalId> externalIds =
Collections.unmodifiableCollection(
db.accountExternalIds().byAccount(who).toList());
externalIdCache.byAccount(who);
Set<AccountGroup.UUID> internalGroups = new HashSet<>();
for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {

View File

@@ -31,7 +31,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -42,6 +41,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -63,6 +63,7 @@ public class AccountManager {
private final AtomicBoolean awaitsFirstAccountCheck;
private final AuditService auditService;
private final Provider<InternalAccountQuery> accountQueryProvider;
private final ExternalIdCache externalIdCache;
@Inject
AccountManager(SchemaFactory<ReviewDb> schema,
@@ -73,7 +74,8 @@ public class AccountManager {
ChangeUserName.Factory changeUserNameFactory,
ProjectCache projectCache,
AuditService auditService,
Provider<InternalAccountQuery> accountQueryProvider) {
Provider<InternalAccountQuery> accountQueryProvider,
ExternalIdCache externalIdCache) {
this.schema = schema;
this.byIdCache = byIdCache;
this.byEmailCache = byEmailCache;
@@ -84,6 +86,7 @@ public class AccountManager {
this.awaitsFirstAccountCheck = new AtomicBoolean(true);
this.auditService = auditService;
this.accountQueryProvider = accountQueryProvider;
this.externalIdCache = externalIdCache;
}
/**
@@ -171,6 +174,7 @@ public class AccountManager {
extId.setEmailAddress(newEmail);
db.accountExternalIds().update(Collections.singleton(extId));
externalIdCache.onUpdate(extId);
}
if (!realm.allowsEdit(AccountFieldName.FULL_NAME)
@@ -242,6 +246,7 @@ public class AccountManager {
+ "\" to account " + newId + "; external ID already in use.");
}
db.accountExternalIds().upsert(Collections.singleton(extId));
externalIdCache.onUpdate(extId);
} finally {
// If adding the account failed, it may be that it actually was the
// first account. So we reset the 'check for first account'-guard, as
@@ -335,6 +340,7 @@ public class AccountManager {
// the database
db.accounts().delete(Collections.singleton(account));
db.accountExternalIds().delete(Collections.singleton(extId));
externalIdCache.onRemove(extId);
throw new AccountUserNameException(errorMessage, e);
}
}
@@ -367,6 +373,7 @@ public class AccountManager {
extId = createId(to, who);
extId.setEmailAddress(who.getEmailAddress());
db.accountExternalIds().insert(Collections.singleton(extId));
externalIdCache.onCreate(extId);
if (who.getEmailAddress() != null) {
Account a = db.accounts().get(to);
@@ -405,12 +412,12 @@ public class AccountManager {
try (ReviewDb db = schema.open()) {
AccountExternalId.Key key = id(who);
List<AccountExternalId.Key> filteredKeysByScheme =
filterKeysByScheme(key.getScheme(), db.accountExternalIds()
.byAccount(to));
filterKeysByScheme(key.getScheme(), externalIdCache.byAccount(to));
if (!filteredKeysByScheme.isEmpty()
&& (filteredKeysByScheme.size() > 1 || !filteredKeysByScheme
.contains(key))) {
db.accountExternalIds().deleteKeys(filteredKeysByScheme);
externalIdCache.onRemove(to, filteredKeysByScheme);
}
byIdCache.evict(to);
return link(to, who);
@@ -418,7 +425,7 @@ public class AccountManager {
}
private List<AccountExternalId.Key> filterKeysByScheme(
String keyScheme, ResultSet<AccountExternalId> externalIds) {
String keyScheme, Collection<AccountExternalId> externalIds) {
List<AccountExternalId.Key> filteredExternalIds = new ArrayList<>();
for (AccountExternalId accountExternalId : externalIds) {
if (accountExternalId.isScheme(keyScheme)) {
@@ -448,6 +455,7 @@ public class AccountManager {
"Identity '" + key.get() + "' in use by another account");
}
db.accountExternalIds().delete(Collections.singleton(extId));
externalIdCache.onRemove(extId);
if (who.getEmailAddress() != null) {
Account a = db.accounts().get(from);

View File

@@ -51,19 +51,22 @@ public class ChangeUserName implements Callable<VoidResult> {
private final AccountCache accountCache;
private final SshKeyCache sshKeyCache;
private final ExternalIdCache externalIdCache;
private final ReviewDb db;
private final IdentifiedUser user;
private final String newUsername;
@Inject
ChangeUserName(final AccountCache accountCache,
final SshKeyCache sshKeyCache,
@Assisted final ReviewDb db, @Assisted final IdentifiedUser user,
@Nullable @Assisted final String newUsername) {
ChangeUserName(AccountCache accountCache,
SshKeyCache sshKeyCache,
ExternalIdCache externalIdCache,
@Assisted ReviewDb db,
@Assisted IdentifiedUser user,
@Nullable @Assisted String newUsername) {
this.accountCache = accountCache;
this.sshKeyCache = sshKeyCache;
this.externalIdCache = externalIdCache;
this.db = db;
this.user = user;
@@ -96,6 +99,7 @@ public class ChangeUserName implements Callable<VoidResult> {
}
db.accountExternalIds().insert(Collections.singleton(id));
externalIdCache.onCreate(id);
} catch (OrmDuplicateKeyException dupeErr) {
// If we are using this identity, don't report the exception.
//
@@ -113,6 +117,7 @@ public class ChangeUserName implements Callable<VoidResult> {
// If we have any older user names, remove them.
//
db.accountExternalIds().delete(old);
externalIdCache.onRemove(old);
for (AccountExternalId i : old) {
sshKeyCache.evict(i.getSchemeRest());
accountCache.evictByUsername(i.getSchemeRest());
@@ -124,9 +129,9 @@ public class ChangeUserName implements Callable<VoidResult> {
return VoidResult.INSTANCE;
}
private Collection<AccountExternalId> old() throws OrmException {
private Collection<AccountExternalId> old() {
final Collection<AccountExternalId> r = new ArrayList<>(1);
for (AccountExternalId i : db.accountExternalIds().byAccount(
for (AccountExternalId i : externalIdCache.byAccount(
user.getAccountId())) {
if (i.isScheme(SCHEME_USERNAME)) {
r.add(i);

View File

@@ -73,6 +73,7 @@ public class CreateAccount
private final AccountLoader.Factory infoLoader;
private final DynamicSet<AccountExternalIdCreator> externalIdCreators;
private final AuditService auditService;
private final ExternalIdCache externalIdCache;
private final String username;
@Inject
@@ -87,6 +88,7 @@ public class CreateAccount
AccountLoader.Factory infoLoader,
DynamicSet<AccountExternalIdCreator> externalIdCreators,
AuditService auditService,
ExternalIdCache externalIdCache,
@Assisted String username) {
this.db = db;
this.currentUser = currentUser;
@@ -99,6 +101,7 @@ public class CreateAccount
this.infoLoader = infoLoader;
this.externalIdCreators = externalIdCreators;
this.auditService = auditService;
this.externalIdCache = externalIdCache;
this.username = username;
}
@@ -153,6 +156,7 @@ public class CreateAccount
try {
db.accountExternalIds().insert(externalIds);
externalIdCache.onCreate(externalIds);
} catch (OrmDuplicateKeyException duplicateKey) {
throw new ResourceConflictException(
"username '" + username + "' already exists");
@@ -164,9 +168,11 @@ public class CreateAccount
extMailto.setEmailAddress(input.email);
try {
db.accountExternalIds().insert(Collections.singleton(extMailto));
externalIdCache.onCreate(extMailto);
} catch (OrmDuplicateKeyException duplicateKey) {
try {
db.accountExternalIds().delete(Collections.singleton(extUser));
externalIdCache.onRemove(extUser);
} catch (OrmException cleanupError) {
// Ignored
}

View File

@@ -0,0 +1,43 @@
// Copyright (C) 2016 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;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import java.util.Collection;
import java.util.Collections;
/** Caches external ids of all accounts */
public interface ExternalIdCache {
void onCreate(Iterable<AccountExternalId> extId);
void onRemove(Iterable<AccountExternalId> extId);
void onRemove(Account.Id accountId,
Iterable<AccountExternalId.Key> extIdKeys);
void onUpdate(AccountExternalId extId);
Collection<AccountExternalId> byAccount(Account.Id accountId);
default void onCreate(AccountExternalId extId) {
onCreate(Collections.singleton(extId));
}
default void onRemove(AccountExternalId extId) {
onRemove(Collections.singleton(extId));
}
default void onRemove(Account.Id accountId, AccountExternalId.Key extIdKey) {
onRemove(accountId, Collections.singleton(extIdKey));
}
}

View File

@@ -0,0 +1,193 @@
// Copyright (C) 2016 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;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Singleton
public class ExternalIdCacheImpl implements ExternalIdCache {
private static final Logger log =
LoggerFactory.getLogger(ExternalIdCacheImpl.class);
private static final String CACHE_NAME = "external_ids_map";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
cache(CACHE_NAME, AllKey.class,
new TypeLiteral<ImmutableSetMultimap<Account.Id, AccountExternalId>>() {})
.maximumWeight(1).loader(Loader.class);
bind(ExternalIdCacheImpl.class);
bind(ExternalIdCache.class).to(ExternalIdCacheImpl.class);
}
};
}
private final LoadingCache<AllKey,
ImmutableSetMultimap<Account.Id, AccountExternalId>> extIdsByAccount;
private final Lock lock;
@Inject
ExternalIdCacheImpl(
@Named(CACHE_NAME) LoadingCache<AllKey,
ImmutableSetMultimap<Account.Id, AccountExternalId>> extIdsByAccount) {
this.extIdsByAccount = extIdsByAccount;
this.lock = new ReentrantLock(true /* fair */);
}
@Override
public void onCreate(Iterable<AccountExternalId> extIds) {
lock.lock();
try {
Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
.arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
for (AccountExternalId extId : extIds) {
n.put(extId.getAccountId(), extId);
}
extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
} catch (ExecutionException e) {
log.warn("Cannot list external IDs", e);
} finally {
lock.unlock();
}
}
@Override
public void onRemove(Iterable<AccountExternalId> extIds) {
lock.lock();
try {
Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
.arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
for (AccountExternalId extId : extIds) {
n.remove(extId.getAccountId(), extId);
}
extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
} catch (ExecutionException e) {
log.warn("Cannot list external IDs", e);
} finally {
lock.unlock();
}
}
@Override
public void onRemove(Account.Id accountId,
Iterable<AccountExternalId.Key> extIdKeys) {
lock.lock();
try {
Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
.arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
for (AccountExternalId extId : byAccount(accountId)) {
for (AccountExternalId.Key extIdKey : extIdKeys) {
if (extIdKey.equals(extId.getKey())) {
n.remove(accountId, extId);
break;
}
}
}
extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
} catch (ExecutionException e) {
log.warn("Cannot list external IDs", e);
} finally {
lock.unlock();
}
}
@Override
public void onUpdate(AccountExternalId updatedExtId) {
lock.lock();
try {
Multimap<Account.Id, AccountExternalId> n = MultimapBuilder.hashKeys()
.arrayListValues().build(extIdsByAccount.get(AllKey.ALL));
for (AccountExternalId extId : byAccount(updatedExtId.getAccountId())) {
if (updatedExtId.getKey().equals(extId.getKey())) {
n.remove(updatedExtId.getAccountId(), extId);
break;
}
}
n.put(updatedExtId.getAccountId(), updatedExtId);
extIdsByAccount.put(AllKey.ALL, ImmutableSetMultimap.copyOf(n));
} catch (ExecutionException e) {
log.warn("Cannot list external IDs", e);
} finally {
lock.unlock();
}
}
@Override
public Collection<AccountExternalId> byAccount(Account.Id accountId) {
try {
return extIdsByAccount.get(AllKey.ALL).get(accountId);
} catch (ExecutionException e) {
log.warn("Cannot list external ids", e);
return Collections.emptySet();
}
}
static class AllKey {
static final AllKey ALL = new AllKey();
private AllKey() {
}
}
static class Loader
extends CacheLoader<AllKey,
ImmutableSetMultimap<Account.Id, AccountExternalId>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
Loader(SchemaFactory<ReviewDb> schema) {
this.schema = schema;
}
@Override
public ImmutableSetMultimap<Account.Id, AccountExternalId> load(AllKey key)
throws Exception {
try (ReviewDb db = schema.open()) {
Multimap<Account.Id, AccountExternalId> extIdsByAccount =
MultimapBuilder.hashKeys().arrayListValues().build();
for (AccountExternalId extId : db.accountExternalIds().all()) {
extIdsByAccount.put(extId.getAccountId(), extId);
}
return ImmutableSetMultimap.copyOf(extIdsByAccount);
}
}
}
}

View File

@@ -60,13 +60,17 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
private final AccountCache accountCache;
private final ExternalIdCache externalIdCache;
@Inject
PutHttpPassword(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
AccountCache accountCache) {
PutHttpPassword(Provider<CurrentUser> self,
Provider<ReviewDb> dbProvider,
AccountCache accountCache,
ExternalIdCache externalIdCache) {
this.self = self;
this.dbProvider = dbProvider;
this.accountCache = accountCache;
this.externalIdCache = externalIdCache;
}
@Override
@@ -117,6 +121,7 @@ public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
}
id.setPassword(newPassword);
dbProvider.get().accountExternalIds().update(Collections.singleton(id));
externalIdCache.onUpdate(id);
accountCache.evict(user.getAccountId());
return Strings.isNullOrEmpty(newPassword)

View File

@@ -86,6 +86,7 @@ import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.ExternalIdCacheImpl;
import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupDetailFactory;
@@ -217,6 +218,7 @@ public class GerritGlobalModule extends FactoryModule {
install(AccountCacheImpl.module());
install(ChangeKindCacheImpl.module());
install(ConflictsCacheImpl.module());
install(ExternalIdCacheImpl.module());
install(GroupCacheImpl.module());
install(GroupIncludeCacheImpl.module());
install(MergeabilityCacheImpl.module());