@Override
  public short undeleteBlob(BlobId blobId, short lifeVersion, CloudUpdateValidator unused)
      throws CloudStorageException, StoreException {
    Timer.Context storageTimer = azureMetrics.blobUndeleteLatency.time();
    AzureBlobLayoutStrategy.BlobLayout blobLayout = azureBlobLayoutStrategy.getDataBlobLayout(blobId);
    String blobIdStr = blobLayout.blobFilePath;
    BlobProperties blobPropertiesCached = getAzureBlobPropertiesCached(blobLayout).getProperties();
    Map<String, String> cloudMetadataCached = blobPropertiesCached.getMetadata();
    Map<String, Object> newMetadata = new HashMap<>();
    newMetadata.put(CloudBlobMetadata.FIELD_LIFE_VERSION, lifeVersion);
    try {
      // If we are here, it means the cloudLifeVersion >= replicaLifeVersion.
      // Cloud is either ahead of server or caught up.
      // lifeVersion must always be present
      short cloudlifeVersion = Short.parseShort(cloudMetadataCached.get(CloudBlobMetadata.FIELD_LIFE_VERSION));
      // if cloudLifeVersion == replicaLifeVersion && deleteTime absent in cloudMetatadata, then something is wrong. Throw Life_Version_Conflict.
      // if cloudLifeVersion == replicaLifeVersion && deleteTime != -1, then something is wrong. Throw Life_Version_Conflict.
      if (cloudlifeVersion == lifeVersion && !cloudMetadataCached.containsKey(CloudBlobMetadata.FIELD_DELETION_TIME)) {
        String error = String.format("Failed to undelete blob %s as it is undeleted in cloud", blobIdStr);
        logger.trace(error);
        throw new StoreException(error, StoreErrorCodes.IDUndeleted);
      }
      if (cloudlifeVersion > lifeVersion) {
        String error = String.format("Failed to undelete blob %s as it has a higher life version in cloud than replicated message : %s > %s",
            blobIdStr, cloudlifeVersion, lifeVersion);
        logger.trace(error);
        throw new StoreException(error, StoreErrorCodes.LifeVersionConflict);
      }
      // Just remove the deletion time, instead of setting it to -1.
      // It just leads to two cases in code later in compaction, for deleted blobs.
      newMetadata.forEach((k,v) -> cloudMetadataCached.put(k, String.valueOf(v)));
      cloudMetadataCached.remove(CloudBlobMetadata.FIELD_DELETION_TIME);
      logger.trace("Resetting deleteTime of blob {} in Azure blob storage ", blobLayout.blobFilePath);
      Response<Void> response = updateBlobMetadata(blobLayout, blobPropertiesCached);
      // Success rate is effective, success counter is ineffective because it just monotonically increases
      azureMetrics.blobUndeleteSuccessRate.mark();
      logger.trace("Successfully reset deleteTime of blob {} in Azure blob storage with statusCode = {}, etag = {}",
          blobLayout.blobFilePath, response.getStatusCode(), response.getHeaders().get(HttpHeaderName.ETAG));
      return lifeVersion;
    } catch (BlobStorageException bse) {
      String error =
          String.format("Failed to undelete blob %s in Azure blob storage due to (%s)", blobLayout, bse.getMessage());
      if (bse.getErrorCode() == BlobErrorCode.CONDITION_NOT_MET) {
        /*
          If we are here, it just means that two threads tried to un-delete concurrently. This is ok.
        */
        logger.trace(error);
        return lifeVersion;
      }
      azureMetrics.blobUndeleteErrorCount.inc();
      logger.error(error);
      throw new CloudStorageException(error);
    } catch (StoreException e) {
      azureMetrics.blobUndeleteErrorCount.inc();
      throw e;
    } catch (Throwable t) {
      // Unknown error
      azureMetrics.blobUndeleteErrorCount.inc();
      String error = String.format("Failed to undelete blob %s in Azure blob storage due to (%s)", blobLayout, t.getMessage());
      logger.error(error);
      throw new CloudStorageException(error);
    } finally {
      storageTimer.stop();
      getThreadLocalMdCache().deleteObject(blobLayout.blobFilePath);
    } // try-catch
  }
Returns blob properties from Azure
@param blobLayout BlobLayout
@return Blob properties
@throws CloudStorageException
ambry-cloud/src/main/java/com/github/ambry/cloud/azure/AzureCloudDestinationSync.java