@Test
  public void testUndeleteFailure() throws Exception {
    try {
      assumeTrue(!testEncryption && !includeCloudDc);
      setRouter();
      assertExpectedThreadCounts(2, 1);

      // 1. Test undelete a non-exist blob
      setOperationParams();
      String nonExistBlobId = new BlobId(routerConfig.routerBlobidCurrentVersion, BlobId.BlobIdType.NATIVE,
          mockClusterMap.getLocalDatacenterId(), putBlobProperties.getAccountId(), putBlobProperties.getContainerId(),
          mockClusterMap.getWritablePartitionIds(MockClusterMap.DEFAULT_PARTITION_CLASS).get(0), false,
          BlobId.BlobDataType.DATACHUNK).getID();
      try {
        router.getBlob(nonExistBlobId, new GetBlobOptionsBuilder().build()).get();
        fail("Should fail because of non-existed id");
      } catch (ExecutionException e) {
        RouterException r = (RouterException) e.getCause();
        Assert.assertEquals("BlobDoesNotExist error is expected", RouterErrorCode.BlobDoesNotExist, r.getErrorCode());
      }
      try {
        router.undeleteBlob(nonExistBlobId, "undelete_server_id").get();
        fail("Should fail because of non-existed id");
      } catch (ExecutionException e) {
        RouterException r = (RouterException) e.getCause();
        Assert.assertEquals("BlobDoesNotExist error is expected", RouterErrorCode.BlobDoesNotExist, r.getErrorCode());
      }

      // 2. Test not-deleted blob
      setOperationParams();
      String notDeletedBlobId =
          router.putBlob(putBlobProperties, putUserMetadata, putChannel, PutBlobOptions.DEFAULT).get();
      ensurePutInAllServers(notDeletedBlobId, mockServerLayout);
      router.getBlob(notDeletedBlobId, new GetBlobOptionsBuilder().build()).get();
      try {
        router.undeleteBlob(notDeletedBlobId, "undelete_server_id").get();
        fail("Should fail because of not-deleted id");
      } catch (ExecutionException e) {
        RouterException r = (RouterException) e.getCause();
        Assert.assertEquals("BlobNotDeleted error is expected", RouterErrorCode.BlobNotDeleted, r.getErrorCode());
      }

      // 3. Test lifeVersion conflict blob
      setOperationParams();
      String conflictBlobId =
          router.putBlob(putBlobProperties, putUserMetadata, putChannel, PutBlobOptions.DEFAULT).get();
      ensurePutInAllServers(conflictBlobId, mockServerLayout);
      router.getBlob(conflictBlobId, new GetBlobOptionsBuilder().build()).get();
      router.deleteBlob(conflictBlobId, null).get();
      ensureDeleteInAllServers(conflictBlobId, mockServerLayout); // All lifeVersion should be 0
      int count = 0;
      for (MockServer server : mockServerLayout.getMockServers()) {
        server.getBlobs().get(conflictBlobId).lifeVersion = 3;
        count++;
        if (count == 4) {
          // Only change 4 servers, since they are 3 datacenters and 9 servers. If we chang less than 4 servers, eg 3, then
          // this 3 changes might be distributed to 3 datacenters and undelete can still reach global quorum.
          break;
        }
      }

      try {
        router.undeleteBlob(conflictBlobId, "undelete_server_id").get();
        fail("Should fail because of lifeVersion conflict");
      } catch (ExecutionException e) {
        RouterException r = (RouterException) e.getCause();
        Assert.assertEquals("LifeVersionConflict error is expected", RouterErrorCode.LifeVersionConflict,
            r.getErrorCode());
      }
    } finally {
      if (router != null) {
        router.close();
        assertExpectedThreadCounts(0, 0);

        //submission after closing should return a future that is already done.
        assertClosed();
      }
    }
  }
Test failure cases of undelete.
@throws Exception
ambry-router/src/test/java/com/github/ambry/router/NonBlockingRouterTest.java