Remove project from replication node's project cache on project deletion

Even if a project is deleted from the local filesystem via pull-
replication, it remains in the node's project cache. This is
problematic, as subsequent operations (eg to recreate the deleted repo)
fail, because the replica thinks the project exists locally.

This change is therefore removing the project from the node's cache,
whenever a project is successfully deleted from disk. To do this, it
reuses the CacheDeleteHandler from the delete-project plugin code.

Bug: Issue 16730
Change-Id: I62e7653a8d78f25c1de1f95a5f8df34e9c6a181e
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
index 574f632..f9165ad 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionAction.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.googlesource.gerrit.plugins.deleteproject.cache.CacheDeleteHandler;
 import com.googlesource.gerrit.plugins.deleteproject.fs.RepositoryDelete;
 import com.googlesource.gerrit.plugins.replication.pull.GerritConfigOps;
 import java.io.IOException;
@@ -47,17 +48,20 @@
   private final GerritConfigOps gerritConfigOps;
   private final PermissionBackend permissionBackend;
   private final RepositoryDelete repositoryDelete;
+  private final CacheDeleteHandler cacheDeleteHandler;
 
   @Inject
   ProjectDeletionAction(
       GerritConfigOps gerritConfigOps,
       PermissionBackend permissionBackend,
       Provider<CurrentUser> userProvider,
-      RepositoryDelete repositoryDelete) {
+      RepositoryDelete repositoryDelete,
+      CacheDeleteHandler cacheDeleteHandler) {
     this.gerritConfigOps = gerritConfigOps;
     this.permissionBackend = permissionBackend;
     this.userProvider = userProvider;
     this.repositoryDelete = repositoryDelete;
+    this.cacheDeleteHandler = cacheDeleteHandler;
   }
 
   @Override
@@ -78,7 +82,12 @@
         // reuse repo deletion logic from delete-project plugin, as it can successfully delete
         // the git directories hosted on nfs.
         repositoryDelete.execute(projectResource.getNameKey());
-        repLog.info("Deleted local repository {}", projectResource.getName());
+        // delete the project from the local project cache, otherwise future ops
+        // will fail as the replica will think that the project still exists locally.
+        cacheDeleteHandler.delete(projectResource.getProjectState().getProject());
+        repLog.info(
+            "Deleted local repository {} and removed it from the local project cache",
+            projectResource.getName());
         return Response.ok();
       } catch (RepositoryNotFoundException e) {
         throw new ResourceNotFoundException(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
index de8a942..586e724 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectDeletionActionIT.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.replication.pull.api;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
 
 import com.google.gerrit.acceptance.config.GerritConfig;
@@ -170,6 +171,46 @@
             assertHttpResponseCode(HttpServletResponse.SC_OK));
   }
 
+  @Test
+  public void shouldRemoveFromTheProjectCacheWhenProjectIsSuccessfullyDeleted() throws Exception {
+    String testProjectName = project.get();
+    url = getURLWithAuthenticationPrefix(testProjectName);
+    assertThat(projectCache.get(project).isPresent()).isTrue();
+
+    httpClientFactory
+        .create(source)
+        .execute(
+            withBasicAuthenticationAsAdmin(createDeleteRequest()),
+            assertHttpResponseCode(HttpServletResponse.SC_OK));
+    assertThat(projectCache.get(project).isPresent()).isFalse();
+  }
+
+  @Test
+  @GerritConfig(name = "container.replica", value = "true")
+  public void shouldRemoveFromtheReplicaProjectCacheWhenProjectIsSuccessfullyDeletedFromTheReplica()
+      throws Exception {
+    String testProjectName = project.get();
+    url = getURLWithAuthenticationPrefix(testProjectName);
+    assertThat(projectCache.get(project).isPresent()).isTrue();
+
+    httpClientFactory
+        .create(source)
+        .execute(
+            withBasicAuthenticationAsAdmin(createDeleteRequest()),
+            assertHttpResponseCode(HttpServletResponse.SC_OK));
+    assertThat(projectCache.get(project).isPresent()).isFalse();
+  }
+
+  @Test
+  @GerritConfig(name = "container.replica", value = "true")
+  public void shouldNotRemoveFromTheReplicaCacheIfAProjectCannotBeDeleted() throws Exception {
+    assertThat(projectCache.get(project).isPresent()).isTrue();
+    httpClientFactory
+        .create(source)
+        .execute(createDeleteRequest(), assertHttpResponseCode(HttpServletResponse.SC_FORBIDDEN));
+    assertThat(projectCache.get(project).isPresent()).isTrue();
+  }
+
   @Override
   protected String getURLWithAuthenticationPrefix(String projectName) {
     return String.format(