@Override
  public short updateBlobExpiration(BlobId blobId, long unused, CloudUpdateValidator unused2)
      throws CloudStorageException, StoreException {
    Timer.Context storageTimer = azureMetrics.blobUpdateTTLLatency.time();
    AzureBlobLayoutStrategy.BlobLayout blobLayout = azureBlobLayoutStrategy.getDataBlobLayout(blobId);
    String blobIdStr = blobLayout.blobFilePath;
    BlobProperties blobPropertiesCached = getAzureBlobPropertiesCached(blobLayout).getProperties();
    Map<String, String> cloudMetadataCached = blobPropertiesCached.getMetadata();
    // lifeVersion must always be present because we add it explicitly before PUT
    short cloudlifeVersion = Short.parseShort(cloudMetadataCached.get(CloudBlobMetadata.FIELD_LIFE_VERSION));
    try {
      // Below is the correct behavior. For ref, look at BlobStore::updateTTL and ReplicaThread::applyTtlUpdate.
      // We should never hit this case however because ReplicaThread::applyUpdatesToBlobInLocalStore does all checks.
      // It is absorbed by applyTtlUpdate::L1395.
      // The validator doesn't check for this, perhaps another gap in legacy code.
      if (isDeleted(cloudMetadataCached)) {
        // Replication must first undelete the deleted blob, and then update-TTL.
        String error = String.format("Unable to update TTL of %s as it is marked for deletion in cloud", blobIdStr);
        logger.trace(error);
        throw new StoreException(error, StoreErrorCodes.IDDeleted);
      }
      if (!cloudMetadataCached.containsKey(CloudBlobMetadata.FIELD_EXPIRATION_TIME)) {
        String error = String.format("Unable to update TTL of %s as its TTL is already updated cloud", blobIdStr);
        logger.trace(error);
        throw new StoreException(error, StoreErrorCodes.AlreadyUpdated);
      }
      // Just remove the expiration time, instead of setting it to -1.
      // It just leads to two cases in code later in compaction and recovery, for permanent blobs.
      cloudMetadataCached.remove(CloudBlobMetadata.FIELD_EXPIRATION_TIME);
      logger.trace("Updating TTL 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.blobUpdateTTLSuccessRate.mark();
      logger.trace("Successfully updated TTL of blob {} in Azure blob storage with statusCode = {}, etag = {}",
          blobLayout.blobFilePath, response.getStatusCode(), response.getHeaders().get(HttpHeaderName.ETAG));
      return cloudlifeVersion;
    } catch (StoreException e) {
      // Auth error from validator
      azureMetrics.blobUpdateTTLErrorCount.inc();
      throw e;
    } catch (Throwable t) {
      azureMetrics.blobUpdateTTLErrorCount.inc();
      String error = String.format("Failed to update TTL of 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 true if a blob exists in Azure storage, false otherwise.
@param blobId
@return
ambry-cloud/src/main/java/com/github/ambry/cloud/azure/AzureCloudDestinationSync.java