Fix issue with disabling ref-database
The multi-site plugin allows to disable ref verification against
ref-database. Before this change multi-site failed to start when
ref-database was disabled because the replication status
functionality required access to ref-database.
Add optional binding for ProjectVersionRefUpdate to allow
skipping the replication status calculation when ref-database
is disabled.
Bug: Issue 15440
Change-Id: I56aea0140cacba5d6bcbf04b1d6a2b946fd2414c
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
index 87e9d22..38cddd5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
@@ -14,6 +14,7 @@
package com.googlesource.gerrit.plugins.multisite;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.multisite.validation.ValidationModule;
@@ -28,6 +29,8 @@
@Override
protected void configure() {
+ bind(SharedRefDbConfiguration.class).toInstance(config.getSharedRefDbConfiguration());
+ bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
if (config.getSharedRefDbConfiguration().getSharedRefDb().isEnabled()) {
install(new ValidationModule(config));
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
index f44f4f7..91a927b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -66,7 +66,7 @@
install(new CacheModule());
}
if (config.event().synchronize()) {
- install(new EventModule());
+ install(new EventModule(config));
}
if (config.index().synchronize()) {
install(new IndexModule());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
index c45dfa7..7360b8f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatus.java
@@ -25,7 +25,6 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.Module;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
@@ -60,14 +59,14 @@
private final Map<String, Long> localVersionPerProject = new HashMap<>();
private final Cache<String, Long> cache;
- private final Provider<ProjectVersionRefUpdate> projectVersionRefUpdate;
+ private final Optional<ProjectVersionRefUpdate> projectVersionRefUpdate;
private final ProjectVersionLogger verLogger;
private final ProjectCache projectCache;
@Inject
public ReplicationStatus(
@Named(REPLICATION_STATUS_CACHE) Cache<String, Long> cache,
- Provider<ProjectVersionRefUpdate> projectVersionRefUpdate,
+ Optional<ProjectVersionRefUpdate> projectVersionRefUpdate,
ProjectVersionLogger verLogger,
ProjectCache projectCache) {
this.cache = cache;
@@ -98,9 +97,11 @@
public void updateReplicationLag(Project.NameKey projectName) {
Optional<Long> remoteVersion =
- projectVersionRefUpdate.get().getProjectRemoteVersion(projectName.get());
+ projectVersionRefUpdate.flatMap(
+ refUpdate -> refUpdate.getProjectRemoteVersion(projectName.get()));
Optional<Long> localVersion =
- projectVersionRefUpdate.get().getProjectLocalVersion(projectName.get());
+ projectVersionRefUpdate.flatMap(
+ refUpdate -> refUpdate.getProjectLocalVersion(projectName.get()));
if (remoteVersion.isPresent() && localVersion.isPresent()) {
long lag = remoteVersion.get() - localVersion.get();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
index 1c0c644..5f16210 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
@@ -17,16 +17,34 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.events.EventListener;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.OptionalBinder;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdateImpl;
import java.util.concurrent.Executor;
public class EventModule extends LifecycleModule {
+ private final Configuration config;
+
+ public EventModule(Configuration config) {
+ this.config = config;
+ }
+
@Override
protected void configure() {
bind(Executor.class).annotatedWith(EventExecutor.class).toProvider(EventExecutorProvider.class);
listener().to(EventExecutorProvider.class);
DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
- DynamicSet.bind(binder(), EventListener.class).to(ProjectVersionRefUpdate.class);
+ OptionalBinder<ProjectVersionRefUpdate> projectVersionRefUpdateBinder =
+ OptionalBinder.newOptionalBinder(binder(), ProjectVersionRefUpdate.class);
+ if (config.getSharedRefDbConfiguration().getSharedRefDb().isEnabled()) {
+ DynamicSet.bind(binder(), EventListener.class).to(ProjectVersionRefUpdateImpl.class);
+ projectVersionRefUpdateBinder
+ .setBinding()
+ .to(ProjectVersionRefUpdateImpl.class)
+ .in(Scopes.SINGLETON);
+ }
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
index 28eddbb..1526059 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2020 The Android Open Source Project
+// Copyright (C) 2022 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.
@@ -14,297 +14,19 @@
package com.googlesource.gerrit.plugins.multisite.validation;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
-import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
-import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.RefNames;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.EventListener;
-import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.IntBlob;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
-import java.io.IOException;
import java.util.Optional;
-import java.util.Set;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-@Singleton
-public class ProjectVersionRefUpdate implements EventListener {
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private static final Set<RefUpdate.Result> SUCCESSFUL_RESULTS =
- ImmutableSet.of(RefUpdate.Result.NEW, RefUpdate.Result.FORCED, RefUpdate.Result.NO_CHANGE);
+public interface ProjectVersionRefUpdate {
- public static final String MULTI_SITE_VERSIONING_REF = "refs/multi-site/version";
- public static final String MULTI_SITE_VERSIONING_VALUE_REF = "refs/multi-site/version/value";
- public static final Ref NULL_PROJECT_VERSION_REF =
+ String MULTI_SITE_VERSIONING_REF = "refs/multi-site/version";
+ String MULTI_SITE_VERSIONING_VALUE_REF = "refs/multi-site/version/value";
+ Ref NULL_PROJECT_VERSION_REF =
new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId());
- private final GitRepositoryManager gitRepositoryManager;
- private final GitReferenceUpdated gitReferenceUpdated;
- private final ProjectVersionLogger verLogger;
- private final ProjectsFilter projectsFilter;
+ Optional<Long> getProjectLocalVersion(String projectName);
- protected final SharedRefDatabaseWrapper sharedRefDb;
-
- @Inject
- public ProjectVersionRefUpdate(
- GitRepositoryManager gitRepositoryManager,
- SharedRefDatabaseWrapper sharedRefDb,
- GitReferenceUpdated gitReferenceUpdated,
- ProjectVersionLogger verLogger,
- ProjectsFilter projectsFilter) {
- this.gitRepositoryManager = gitRepositoryManager;
- this.sharedRefDb = sharedRefDb;
- this.gitReferenceUpdated = gitReferenceUpdated;
- this.verLogger = verLogger;
- this.projectsFilter = projectsFilter;
- }
-
- @Override
- public void onEvent(Event event) {
- logger.atFine().log("Processing event type: " + event.type);
- // Producer of the Event use RefUpdatedEvent to trigger the version update
- if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) {
- if (projectsFilter.matches(event)) {
- updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
- }
- }
- }
-
- private boolean isSpecialRefName(String refName) {
- return refName.startsWith(RefNames.REFS_SEQUENCES)
- || refName.startsWith(RefNames.REFS_STARRED_CHANGES)
- || refName.equals(MULTI_SITE_VERSIONING_REF);
- }
-
- private void updateProducerProjectVersionUpdate(RefUpdatedEvent refUpdatedEvent) {
- String refName = refUpdatedEvent.getRefName();
-
- if (isSpecialRefName(refName)) {
- logger.atFine().log(
- "Found a special ref name %s, skipping update for %s",
- refName, refUpdatedEvent.getProjectNameKey().get());
- return;
- }
- try {
- Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey();
- long newVersion = getCurrentGlobalVersionNumber();
-
- Optional<RefUpdate> newProjectVersionRefUpdate =
- updateLocalProjectVersion(projectNameKey, newVersion);
-
- if (newProjectVersionRefUpdate.isPresent()) {
- verLogger.log(projectNameKey, newVersion, 0L);
-
- if (updateSharedProjectVersion(
- projectNameKey, newProjectVersionRefUpdate.get().getNewObjectId(), newVersion)) {
- gitReferenceUpdated.fire(projectNameKey, newProjectVersionRefUpdate.get(), null);
- }
- } else {
- logger.atWarning().log(
- "Ref %s not found on projet %s: skipping project version update",
- refUpdatedEvent.getRefName(), projectNameKey);
- }
- } catch (LocalProjectVersionUpdateException | SharedProjectVersionUpdateException e) {
- logger.atSevere().withCause(e).log(
- "Issue encountered when updating version for project "
- + refUpdatedEvent.getProjectNameKey());
- }
- }
-
- private RefUpdate getProjectVersionRefUpdate(Repository repository, Long version)
- throws IOException {
- RefUpdate refUpdate = repository.getRefDatabase().newUpdate(MULTI_SITE_VERSIONING_REF, false);
- refUpdate.setNewObjectId(getNewId(repository, version));
- refUpdate.setForceUpdate(true);
- return refUpdate;
- }
-
- private ObjectId getNewId(Repository repository, Long version) throws IOException {
- ObjectInserter ins = repository.newObjectInserter();
- ObjectId newId = ins.insert(OBJ_BLOB, Long.toString(version).getBytes(UTF_8));
- ins.flush();
- return newId;
- }
-
- private boolean updateSharedProjectVersion(
- Project.NameKey projectNameKey, ObjectId newObjectId, Long newVersion)
- throws SharedProjectVersionUpdateException {
-
- Ref sharedRef =
- sharedRefDb
- .get(projectNameKey, MULTI_SITE_VERSIONING_REF, String.class)
- .map(
- (String objectId) ->
- new ObjectIdRef.Unpeeled(
- Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.fromString(objectId)))
- .orElse(
- new ObjectIdRef.Unpeeled(
- Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId()));
- Optional<Long> sharedVersion =
- sharedRefDb
- .get(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, String.class)
- .map(Long::parseLong);
-
- try {
- if (sharedVersion.isPresent() && sharedVersion.get() >= newVersion) {
- logger.atWarning().log(
- String.format(
- "NOT Updating project %s version %s (value=%d) in shared ref-db because is more recent than the local one %s (value=%d) ",
- projectNameKey.get(),
- newObjectId,
- newVersion,
- sharedRef.getObjectId().getName(),
- sharedVersion.get()));
- return false;
- }
-
- logger.atFine().log(
- String.format(
- "Updating shared project %s version to %s (value=%d)",
- projectNameKey.get(), newObjectId, newVersion));
-
- boolean success = sharedRefDb.compareAndPut(projectNameKey, sharedRef, newObjectId);
- if (!success) {
- String message =
- String.format(
- "Project version blob update failed for %s. Current value %s, new value: %s",
- projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
- logger.atSevere().log(message);
- throw new SharedProjectVersionUpdateException(message);
- }
-
- success =
- sharedRefDb.compareAndPut(
- projectNameKey,
- MULTI_SITE_VERSIONING_VALUE_REF,
- sharedVersion.map(Object::toString).orElse(null),
- newVersion.toString());
- if (!success) {
- String message =
- String.format(
- "Project version update failed for %s. Current value %s, new value: %s",
- projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
- logger.atSevere().log(message);
- throw new SharedProjectVersionUpdateException(message);
- }
-
- return true;
- } catch (GlobalRefDbSystemError refDbSystemError) {
- String message =
- String.format(
- "Error while updating shared project version for %s. Current value %s, new value: %s. Error: %s",
- projectNameKey.get(),
- sharedRef.getObjectId(),
- newObjectId,
- refDbSystemError.getMessage());
- logger.atSevere().withCause(refDbSystemError).log(message);
- throw new SharedProjectVersionUpdateException(message);
- }
- }
-
- public Optional<Long> getProjectLocalVersion(String projectName) {
- try (Repository repository =
- gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
- Optional<IntBlob> blob = IntBlob.parse(repository, MULTI_SITE_VERSIONING_REF);
- if (blob.isPresent()) {
- Long repoVersion = Integer.toUnsignedLong(blob.get().value());
- logger.atFine().log("Local project '%s' has version %d", projectName, repoVersion);
- return Optional.of(repoVersion);
- }
- } catch (RepositoryNotFoundException re) {
- logger.atFine().log("Project '%s' not found", projectName);
- } catch (IOException e) {
- logger.atSevere().withCause(e).log("Cannot read local project '%s' version", projectName);
- }
- return Optional.empty();
- }
-
- public Optional<Long> getProjectRemoteVersion(String projectName) {
- Optional<String> globalVersion =
- sharedRefDb.get(
- Project.NameKey.parse(projectName), MULTI_SITE_VERSIONING_VALUE_REF, String.class);
- return globalVersion.flatMap(longString -> getLongValueOf(longString));
- }
-
- private Object safeGetObjectId(Ref currentRef) {
- return currentRef == null ? "null" : currentRef.getObjectId();
- }
-
- private Optional<Long> getLongValueOf(String longString) {
- try {
- return Optional.ofNullable(Long.parseLong(longString));
- } catch (NumberFormatException e) {
- logger.atSevere().withCause(e).log(
- "Unable to parse timestamp value %s into Long", longString);
- return Optional.empty();
- }
- }
-
- private Optional<RefUpdate> updateLocalProjectVersion(
- Project.NameKey projectNameKey, long newVersionNumber)
- throws LocalProjectVersionUpdateException {
- logger.atFine().log(
- "Updating local version for project %s with version %d",
- projectNameKey.get(), newVersionNumber);
- try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
- RefUpdate refUpdate = getProjectVersionRefUpdate(repository, newVersionNumber);
- RefUpdate.Result result = refUpdate.update();
- if (!isSuccessful(result)) {
- String message =
- String.format(
- "RefUpdate failed with result %s for: project=%s, version=%d",
- result.name(), projectNameKey.get(), newVersionNumber);
- logger.atSevere().log(message);
- throw new LocalProjectVersionUpdateException(message);
- }
-
- return Optional.of(refUpdate);
- } catch (IOException e) {
- String message = "Cannot create versioning command for " + projectNameKey.get();
- logger.atSevere().withCause(e).log(message);
- throw new LocalProjectVersionUpdateException(message);
- }
- }
-
- private long getCurrentGlobalVersionNumber() {
- return System.currentTimeMillis() / 1000;
- }
-
- private Boolean isSuccessful(RefUpdate.Result result) {
- return SUCCESSFUL_RESULTS.contains(result);
- }
-
- public static class LocalProjectVersionUpdateException extends Exception {
- private static final long serialVersionUID = 7649956232401457023L;
-
- public LocalProjectVersionUpdateException(String projectName) {
- super("Cannot update local project version of " + projectName);
- }
- }
-
- public static class SharedProjectVersionUpdateException extends Exception {
- private static final long serialVersionUID = -9153858177700286314L;
-
- public SharedProjectVersionUpdateException(String projectName) {
- super("Cannot update shared project version of " + projectName);
- }
- }
+ Optional<Long> getProjectRemoteVersion(String projectName);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java
new file mode 100644
index 0000000..469122b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateImpl.java
@@ -0,0 +1,311 @@
+// Copyright (C) 2022 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.googlesource.gerrit.plugins.multisite.validation;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.gerritforge.gerrit.globalrefdb.validation.ProjectsFilter;
+import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.IntBlob;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+public class ProjectVersionRefUpdateImpl implements EventListener, ProjectVersionRefUpdate {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final Set<RefUpdate.Result> SUCCESSFUL_RESULTS =
+ ImmutableSet.of(RefUpdate.Result.NEW, RefUpdate.Result.FORCED, RefUpdate.Result.NO_CHANGE);
+
+ private final GitRepositoryManager gitRepositoryManager;
+ private final GitReferenceUpdated gitReferenceUpdated;
+ private final ProjectVersionLogger verLogger;
+ private final ProjectsFilter projectsFilter;
+
+ protected final SharedRefDatabaseWrapper sharedRefDb;
+
+ @Inject
+ public ProjectVersionRefUpdateImpl(
+ GitRepositoryManager gitRepositoryManager,
+ SharedRefDatabaseWrapper sharedRefDb,
+ GitReferenceUpdated gitReferenceUpdated,
+ ProjectVersionLogger verLogger,
+ ProjectsFilter projectsFilter) {
+ this.gitRepositoryManager = gitRepositoryManager;
+ this.sharedRefDb = sharedRefDb;
+ this.gitReferenceUpdated = gitReferenceUpdated;
+ this.verLogger = verLogger;
+ this.projectsFilter = projectsFilter;
+ }
+
+ @Override
+ public void onEvent(Event event) {
+ logger.atFine().log("Processing event type: " + event.type);
+ // Producer of the Event use RefUpdatedEvent to trigger the version update
+ if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) {
+ if (projectsFilter.matches(event)) {
+ updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
+ }
+ }
+ }
+
+ private boolean isSpecialRefName(String refName) {
+ return refName.startsWith(RefNames.REFS_SEQUENCES)
+ || refName.startsWith(RefNames.REFS_STARRED_CHANGES)
+ || refName.equals(MULTI_SITE_VERSIONING_REF);
+ }
+
+ private void updateProducerProjectVersionUpdate(RefUpdatedEvent refUpdatedEvent) {
+ String refName = refUpdatedEvent.getRefName();
+
+ if (isSpecialRefName(refName)) {
+ logger.atFine().log(
+ "Found a special ref name %s, skipping update for %s",
+ refName, refUpdatedEvent.getProjectNameKey().get());
+ return;
+ }
+ try {
+ Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey();
+ long newVersion = getCurrentGlobalVersionNumber();
+
+ Optional<RefUpdate> newProjectVersionRefUpdate =
+ updateLocalProjectVersion(projectNameKey, newVersion);
+
+ if (newProjectVersionRefUpdate.isPresent()) {
+ verLogger.log(projectNameKey, newVersion, 0L);
+
+ if (updateSharedProjectVersion(
+ projectNameKey, newProjectVersionRefUpdate.get().getNewObjectId(), newVersion)) {
+ gitReferenceUpdated.fire(projectNameKey, newProjectVersionRefUpdate.get(), null);
+ }
+ } else {
+ logger.atWarning().log(
+ "Ref %s not found on projet %s: skipping project version update",
+ refUpdatedEvent.getRefName(), projectNameKey);
+ }
+ } catch (LocalProjectVersionUpdateException | SharedProjectVersionUpdateException e) {
+ logger.atSevere().withCause(e).log(
+ "Issue encountered when updating version for project "
+ + refUpdatedEvent.getProjectNameKey());
+ }
+ }
+
+ private RefUpdate getProjectVersionRefUpdate(Repository repository, Long version)
+ throws IOException {
+ RefUpdate refUpdate = repository.getRefDatabase().newUpdate(MULTI_SITE_VERSIONING_REF, false);
+ refUpdate.setNewObjectId(getNewId(repository, version));
+ refUpdate.setForceUpdate(true);
+ return refUpdate;
+ }
+
+ private ObjectId getNewId(Repository repository, Long version) throws IOException {
+ ObjectInserter ins = repository.newObjectInserter();
+ ObjectId newId = ins.insert(OBJ_BLOB, Long.toString(version).getBytes(UTF_8));
+ ins.flush();
+ return newId;
+ }
+
+ private boolean updateSharedProjectVersion(
+ Project.NameKey projectNameKey, ObjectId newObjectId, Long newVersion)
+ throws SharedProjectVersionUpdateException {
+
+ Ref sharedRef =
+ sharedRefDb
+ .get(projectNameKey, MULTI_SITE_VERSIONING_REF, String.class)
+ .map(
+ (String objectId) ->
+ new ObjectIdRef.Unpeeled(
+ Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.fromString(objectId)))
+ .orElse(
+ new ObjectIdRef.Unpeeled(
+ Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId()));
+ Optional<Long> sharedVersion =
+ sharedRefDb
+ .get(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, String.class)
+ .map(Long::parseLong);
+
+ try {
+ if (sharedVersion.isPresent() && sharedVersion.get() >= newVersion) {
+ logger.atWarning().log(
+ String.format(
+ "NOT Updating project %s version %s (value=%d) in shared ref-db because is more recent than the local one %s (value=%d) ",
+ projectNameKey.get(),
+ newObjectId,
+ newVersion,
+ sharedRef.getObjectId().getName(),
+ sharedVersion.get()));
+ return false;
+ }
+
+ logger.atFine().log(
+ String.format(
+ "Updating shared project %s version to %s (value=%d)",
+ projectNameKey.get(), newObjectId, newVersion));
+
+ boolean success = sharedRefDb.compareAndPut(projectNameKey, sharedRef, newObjectId);
+ if (!success) {
+ String message =
+ String.format(
+ "Project version blob update failed for %s. Current value %s, new value: %s",
+ projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
+ logger.atSevere().log(message);
+ throw new SharedProjectVersionUpdateException(message);
+ }
+
+ success =
+ sharedRefDb.compareAndPut(
+ projectNameKey,
+ MULTI_SITE_VERSIONING_VALUE_REF,
+ sharedVersion.map(Object::toString).orElse(null),
+ newVersion.toString());
+ if (!success) {
+ String message =
+ String.format(
+ "Project version update failed for %s. Current value %s, new value: %s",
+ projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
+ logger.atSevere().log(message);
+ throw new SharedProjectVersionUpdateException(message);
+ }
+
+ return true;
+ } catch (GlobalRefDbSystemError refDbSystemError) {
+ String message =
+ String.format(
+ "Error while updating shared project version for %s. Current value %s, new value: %s. Error: %s",
+ projectNameKey.get(),
+ sharedRef.getObjectId(),
+ newObjectId,
+ refDbSystemError.getMessage());
+ logger.atSevere().withCause(refDbSystemError).log(message);
+ throw new SharedProjectVersionUpdateException(message);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate#getProjectLocalVersion(java.lang.String)
+ */
+ @Override
+ public Optional<Long> getProjectLocalVersion(String projectName) {
+ try (Repository repository =
+ gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
+ Optional<IntBlob> blob = IntBlob.parse(repository, MULTI_SITE_VERSIONING_REF);
+ if (blob.isPresent()) {
+ Long repoVersion = Integer.toUnsignedLong(blob.get().value());
+ logger.atFine().log("Local project '%s' has version %d", projectName, repoVersion);
+ return Optional.of(repoVersion);
+ }
+ } catch (RepositoryNotFoundException re) {
+ logger.atFine().log("Project '%s' not found", projectName);
+ } catch (IOException e) {
+ logger.atSevere().withCause(e).log("Cannot read local project '%s' version", projectName);
+ }
+ return Optional.empty();
+ }
+
+ /* (non-Javadoc)
+ * @see com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate#getProjectRemoteVersion(java.lang.String)
+ */
+ @Override
+ public Optional<Long> getProjectRemoteVersion(String projectName) {
+ Optional<String> globalVersion =
+ sharedRefDb.get(
+ Project.NameKey.parse(projectName), MULTI_SITE_VERSIONING_VALUE_REF, String.class);
+ return globalVersion.flatMap(longString -> getLongValueOf(longString));
+ }
+
+ private Object safeGetObjectId(Ref currentRef) {
+ return currentRef == null ? "null" : currentRef.getObjectId();
+ }
+
+ private Optional<Long> getLongValueOf(String longString) {
+ try {
+ return Optional.ofNullable(Long.parseLong(longString));
+ } catch (NumberFormatException e) {
+ logger.atSevere().withCause(e).log(
+ "Unable to parse timestamp value %s into Long", longString);
+ return Optional.empty();
+ }
+ }
+
+ private Optional<RefUpdate> updateLocalProjectVersion(
+ Project.NameKey projectNameKey, long newVersionNumber)
+ throws LocalProjectVersionUpdateException {
+ logger.atFine().log(
+ "Updating local version for project %s with version %d",
+ projectNameKey.get(), newVersionNumber);
+ try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
+ RefUpdate refUpdate = getProjectVersionRefUpdate(repository, newVersionNumber);
+ RefUpdate.Result result = refUpdate.update();
+ if (!isSuccessful(result)) {
+ String message =
+ String.format(
+ "RefUpdate failed with result %s for: project=%s, version=%d",
+ result.name(), projectNameKey.get(), newVersionNumber);
+ logger.atSevere().log(message);
+ throw new LocalProjectVersionUpdateException(message);
+ }
+
+ return Optional.of(refUpdate);
+ } catch (IOException e) {
+ String message = "Cannot create versioning command for " + projectNameKey.get();
+ logger.atSevere().withCause(e).log(message);
+ throw new LocalProjectVersionUpdateException(message);
+ }
+ }
+
+ private long getCurrentGlobalVersionNumber() {
+ return System.currentTimeMillis() / 1000;
+ }
+
+ private Boolean isSuccessful(RefUpdate.Result result) {
+ return SUCCESSFUL_RESULTS.contains(result);
+ }
+
+ public static class LocalProjectVersionUpdateException extends Exception {
+ private static final long serialVersionUID = 7649956232401457023L;
+
+ public LocalProjectVersionUpdateException(String projectName) {
+ super("Cannot update local project version of " + projectName);
+ }
+ }
+
+ public static class SharedProjectVersionUpdateException extends Exception {
+ private static final long serialVersionUID = -9153858177700286314L;
+
+ public SharedProjectVersionUpdateException(String projectName) {
+ super("Cannot update shared project version of " + projectName);
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
index fe224ae..f66bf37 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -20,7 +20,6 @@
import com.gerritforge.gerrit.globalrefdb.validation.RefUpdateValidator;
import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbBatchRefUpdate;
-import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbConfiguration;
import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbGitRepositoryManager;
import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefDatabase;
import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDbRefUpdate;
@@ -37,8 +36,6 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.Log4jProjectVersionLogger;
-import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
import com.googlesource.gerrit.plugins.replication.ReplicationExtensionPointModule;
import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
@@ -55,7 +52,6 @@
bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
- bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
factory(LockWrapper.Factory.class);
factory(SharedRefDbRepository.Factory.class);
@@ -65,7 +61,6 @@
factory(RefUpdateValidator.Factory.class);
factory(BatchRefUpdateValidator.Factory.class);
- bind(SharedRefDbConfiguration.class).toInstance(cfg.getSharedRefDbConfiguration());
bind(new TypeLiteral<ImmutableSet<String>>() {})
.annotatedWith(Names.named(SharedRefDbGitRepositoryManager.IGNORED_REFS))
.toInstance(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
index dc1a564..ce82954 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/ReplicationStatusTest.java
@@ -15,7 +15,6 @@
package com.googlesource.gerrit.plugins.multisite.consumer;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import com.google.common.cache.Cache;
@@ -23,9 +22,9 @@
import com.google.common.collect.ImmutableSortedSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,7 +36,6 @@
@Mock private ProjectVersionLogger verLogger;
@Mock private ProjectCache projectCache;
- @Mock private Provider<ProjectVersionRefUpdate> projectVersionRefUpdateProvider;
@Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
private ReplicationStatus objectUnderTest;
private Cache<String, Long> replicationStatusCache;
@@ -50,8 +48,7 @@
replicationStatusCache = CacheBuilder.newBuilder().build();
objectUnderTest =
new ReplicationStatus(
- replicationStatusCache, projectVersionRefUpdateProvider, verLogger, projectCache);
- lenient().when(projectVersionRefUpdateProvider.get()).thenReturn(projectVersionRefUpdate);
+ replicationStatusCache, Optional.of(projectVersionRefUpdate), verLogger, projectCache);
}
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
index 6aa2ce6..4c45e07 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
@@ -27,7 +27,6 @@
import com.google.gerrit.server.events.RefUpdatedEvent;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
import java.util.Optional;
@@ -49,7 +48,6 @@
@Mock private MetricMaker metricMaker;
@Mock private ProjectVersionLogger verLogger;
@Mock private ProjectCache projectCache;
- @Mock private Provider<ProjectVersionRefUpdate> projectVersionRefUpdateProvider;
@Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
private SubscriberMetrics metrics;
private EventMessage.Header msgHeader;
@@ -62,10 +60,9 @@
metricMaker,
new ReplicationStatus(
CacheBuilder.newBuilder().build(),
- projectVersionRefUpdateProvider,
+ Optional.of(projectVersionRefUpdate),
verLogger,
projectCache));
- when(projectVersionRefUpdateProvider.get()).thenReturn(projectVersionRefUpdate);
}
@Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
index da02d02..0739eab 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/http/ReplicationStatusServletIT.java
@@ -27,6 +27,8 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.httpd.restapi.RestApiServlet;
import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.OptionalBinder;
import com.googlesource.gerrit.plugins.multisite.Log4jProjectVersionLogger;
import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
@@ -35,6 +37,8 @@
import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderModule;
import com.googlesource.gerrit.plugins.multisite.forwarder.router.RouterModule;
import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdateImpl;
import java.io.IOException;
import org.eclipse.jgit.lib.Config;
import org.junit.Before;
@@ -64,6 +68,10 @@
bind(SharedRefDbConfiguration.class).toInstance(sharedRefDbConfig);
bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+ OptionalBinder.newOptionalBinder(binder(), ProjectVersionRefUpdate.class)
+ .setBinding()
+ .to(ProjectVersionRefUpdateImpl.class)
+ .in(Scopes.SINGLETON);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
index 917c6bf..0d6fb67 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
@@ -110,7 +110,7 @@
when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
- new ProjectVersionRefUpdate(
+ new ProjectVersionRefUpdateImpl(
repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
.onEvent(refUpdatedEvent);
@@ -155,7 +155,7 @@
when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
- new ProjectVersionRefUpdate(
+ new ProjectVersionRefUpdateImpl(
repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
.onEvent(refUpdatedEvent);
@@ -195,7 +195,7 @@
when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
- new ProjectVersionRefUpdate(
+ new ProjectVersionRefUpdateImpl(
repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
.onEvent(refUpdatedEvent);
@@ -238,7 +238,7 @@
when(refUpdatedEvent.getRefName()).thenReturn(magicRefName);
repo.branch(magicRefName).commit().create();
- new ProjectVersionRefUpdate(
+ new ProjectVersionRefUpdateImpl(
repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
.onEvent(refUpdatedEvent);
@@ -254,7 +254,7 @@
when(refUpdatedEvent.getProjectNameKey()).thenReturn(Project.nameKey("aNonExistentProject"));
when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
- new ProjectVersionRefUpdate(
+ new ProjectVersionRefUpdateImpl(
repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
.onEvent(refUpdatedEvent);
@@ -276,7 +276,7 @@
Thread.sleep(1000L);
repo.branch("master").update(masterCommit);
- new ProjectVersionRefUpdate(
+ new ProjectVersionRefUpdateImpl(
repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
.onEvent(refUpdatedEvent);
@@ -292,7 +292,7 @@
.thenReturn(Optional.of("123"));
Optional<Long> version =
- new ProjectVersionRefUpdate(
+ new ProjectVersionRefUpdateImpl(
repoManager, sharedRefDb, gitReferenceUpdated, verLogger, projectsFilter)
.getProjectRemoteVersion(A_TEST_PROJECT_NAME);