Clean up projects from the SharedRef Db
When a project is deleted using delete-project plugin the project
data needs to be cleaned up from the shared-ref database.
Bug: Issue 10766
Change-Id: Ie954c758921b58e39c67cf56ca72fec7b2aedb9a
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
new file mode 100644
index 0000000..7f58d39
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2019 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 com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
+import java.io.IOException;
+
+public class ProjectDeletedSharedDbCleanup implements ProjectDeletedListener {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final SharedRefDatabase sharedDb;
+
+ private final ValidationMetrics validationMetrics;
+
+ @Inject
+ public ProjectDeletedSharedDbCleanup(
+ SharedRefDatabase sharedDb, ValidationMetrics validationMetrics) {
+ this.sharedDb = sharedDb;
+ this.validationMetrics = validationMetrics;
+ }
+
+ @Override
+ public void onProjectDeleted(Event event) {
+ String projectName = event.getProjectName();
+ logger.atInfo().log(
+ "Deleting project '%s'. Will perform a cleanup in Shared-Ref database.", projectName);
+
+ try {
+ sharedDb.removeProject(projectName);
+ } catch (IOException e) {
+ validationMetrics.incrementSplitBrain();
+ logger.atSevere().withCause(e).log(
+ "Project '%s' deleted from GIT but it was not able to cleanup"
+ + " from Shared-Ref database",
+ projectName);
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
index 790935b..cfbd783 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
@@ -156,4 +156,12 @@
* @return true if the ref exists on the project
*/
boolean exists(String project, String refName);
+
+ /**
+ * Clean project path from SharedRefDatabase
+ *
+ * @param project project name
+ * @throws IOException
+ */
+ void removeProject(String project) throws IOException;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
index 321ad8a..afd3766 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
@@ -73,6 +73,15 @@
}
@Override
+ public void removeProject(String project) throws IOException {
+ try {
+ client.delete().deletingChildrenIfNeeded().forPath("/" + project);
+ } catch (Exception e) {
+ throw new IOException(String.format("Not able to delete project '%s'", project), e);
+ }
+ }
+
+ @Override
public boolean exists(String project, String refName) throws ZookeeperRuntimeException {
try {
return client.checkExists().forPath(pathFor(project, refName)) != null;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
index 927591e..3e8f75a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
@@ -14,8 +14,11 @@
package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.inject.AbstractModule;
import com.googlesource.gerrit.plugins.multisite.Configuration;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
import org.apache.curator.framework.CuratorFramework;
@@ -38,5 +41,7 @@
new ZkConnectionConfig(
cfg.getZookeeperConfig().buildCasRetryPolicy(),
cfg.getZookeeperConfig().getZkInterProcessLockTimeOut()));
+
+ DynamicSet.bind(binder(), ProjectDeletedListener.class).to(ProjectDeletedSharedDbCleanup.class);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
index 9e85563..8819b8b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
@@ -15,7 +15,12 @@
package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
+import com.googlesource.gerrit.plugins.multisite.validation.ValidationMetrics;
import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
@@ -36,6 +41,8 @@
ZkSharedRefDatabase zkSharedRefDatabase;
SharedRefEnforcement refEnforcement;
+ ValidationMetrics mockValidationMetrics;
+
@Before
public void setup() {
refEnforcement = new DefaultSharedRefEnforcement();
@@ -50,6 +57,8 @@
new ZkConnectionConfig(
new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
TRANSACTION_LOCK_TIMEOUT));
+
+ mockValidationMetrics = mock(ValidationMetrics.class);
}
@After
@@ -156,8 +165,62 @@
return SharedRefDatabase.newRef(aBranchRef(), objectId);
}
+ @Test
+ public void removeProjectShouldRemoveTheWholePathInZk() throws Exception {
+ String projectName = A_TEST_PROJECT_NAME;
+ Ref someRef = refOf(AN_OBJECT_ID_1);
+
+ zookeeperContainer.createRefInZk(projectName, someRef);
+
+ assertThat(zookeeperContainer.readRefValueFromZk(projectName, someRef))
+ .isEqualTo(AN_OBJECT_ID_1);
+
+ assertThat(getNumChildrenForPath("/")).isEqualTo(1);
+
+ zkSharedRefDatabase.removeProject(projectName);
+
+ assertThat(getNumChildrenForPath("/")).isEqualTo(0);
+ }
+
+ @Test
+ public void aDeleteProjectEventShouldCleanupProjectFromZk() throws Exception {
+ String projectName = A_TEST_PROJECT_NAME;
+ Ref someRef = refOf(AN_OBJECT_ID_1);
+ ProjectDeletedSharedDbCleanup projectDeletedSharedDbCleanup =
+ new ProjectDeletedSharedDbCleanup(zkSharedRefDatabase, mockValidationMetrics);
+
+ ProjectDeletedListener.Event event =
+ new ProjectDeletedListener.Event() {
+ @Override
+ public String getProjectName() {
+ return projectName;
+ }
+
+ @Override
+ public NotifyHandling getNotify() {
+ return NotifyHandling.NONE;
+ }
+ };
+
+ zookeeperContainer.createRefInZk(projectName, someRef);
+
+ assertThat(getNumChildrenForPath("/")).isEqualTo(1);
+
+ projectDeletedSharedDbCleanup.onProjectDeleted(event);
+
+ assertThat(getNumChildrenForPath("/")).isEqualTo(0);
+ }
+
@Override
public String testBranch() {
return "branch_" + nameRule.getMethodName();
}
+
+ private int getNumChildrenForPath(String path) throws Exception {
+ return zookeeperContainer
+ .getCurator()
+ .checkExists()
+ .forPath(String.format(path))
+ .getNumChildren();
+ }
}