All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.reactiveeventhubs.checkpointing.Backends.AzureBlob.scala Maven / Gradle / Ivy

The newest version!
// Copyright (c) Microsoft. All rights reserved.

package com.microsoft.azure.reactiveeventhubs.checkpointing.Backends

import java.io.IOException
import java.net.URISyntaxException
import java.util.UUID

import com.microsoft.azure.reactiveeventhubs.checkpointing.ICPConfiguration
import com.microsoft.azure.reactiveeventhubs.scaladsl.EventHubPartition
import com.microsoft.azure.reactiveeventhubs.{Logger, Retry}
import com.microsoft.azure.storage.blob.CloudBlockBlob
import com.microsoft.azure.storage.{AccessCondition, CloudStorageAccount, OperationContext, StorageException}

import scala.concurrent.duration._
import scala.language.{implicitConversions, postfixOps}

/** Storage logic to write checkpoints to Azure blobs
  */
private[reactiveeventhubs] class AzureBlob(cpconfig: ICPConfiguration) extends CheckpointBackend with Logger {

  // Set the account to point either to Azure or the emulator
  val account: CloudStorageAccount = if (cpconfig.azureBlobEmulator)
                                       CloudStorageAccount.getDevelopmentStorageAccount()
                                     else
                                       CloudStorageAccount.parse(cpconfig.azureBlobConnectionString)

  val client = account.createCloudBlobClient()

  // Set the container, ensure it's ready
  val container = client.getContainerReference(checkpointNamespace(cpconfig))
  try {
    Retry(2, 5 seconds) {
      container.createIfNotExists()
    }
  } catch {
    case e: StorageException ⇒
      log.error(e, s"Err: ${e.getMessage}; Code: ${e.getErrorCode}; Status: ${e.getHttpStatusCode}")
      throw e

    case e: IOException ⇒
      log.error(e, e.getMessage)
      throw e

    case e: Exception ⇒
      log.error(e, e.getMessage)
      throw e
  }

  /** Read the offset of the last record processed for the given partition
    *
    * @param partition Partition number
    *
    * @return Offset of the last record (already) processed
    */
  override def readOffset(partition: Int): String = {
    val file = getBlockBlobReference(partition)
    try {
      file.downloadText()
    } catch {
      case e: StorageException ⇒
        if (e.getErrorCode == "BlobNotFound") {
          EventHubPartition.OffsetCheckpointNotFound
        } else {
          log.error(e, s"Err: ${e.getMessage}; Code: ${e.getErrorCode}; Status: ${e.getHttpStatusCode}")
          throw e
        }

      case e: IOException ⇒
        log.error(e, e.getMessage)
        throw e

      case e: Exception ⇒
        log.error(e, e.getMessage)
        throw e
    }
  }

  /** Store the offset for the given Event hub partition
    *
    * @param partition Event hub partition number
    * @param offset    Event hub partition offset
    */
  override def writeOffset(partition: Int, offset: String): Unit = {
    val file = getBlockBlobReference(partition)
    val leaseId = acquireLease(file)
    writeAndRelease(file, leaseId, offset)
  }

  private[this] def getBlockBlobReference(partition: Int): CloudBlockBlob = {
    try {
      Retry(2, 2 seconds) {
        container.getBlockBlobReference(filename(partition))
      }
    } catch {

      case e: StorageException ⇒
        log.error(e, e.getMessage)
        throw e

      case e: URISyntaxException ⇒
        log.error(e, e.getMessage)
        throw e

      case e: Exception ⇒
        log.error(e, e.getMessage)
        throw e
    }
  }

  private[this] def acquireLease(file: CloudBlockBlob): String = {
    // Note: the lease ID must be a Guid otherwise the service returs 400
    var leaseId = UUID.randomUUID().toString
    try {
      file.acquireLease(cpconfig.azureBlobLeaseDuration.toSeconds.toInt, leaseId)
    } catch {

      case e: StorageException ⇒
        if (e.getErrorCode == "BlobNotFound") {
          leaseId = ""
        } else {
          log.error(e, s"Err: ${e.getMessage}; Code: ${e.getErrorCode}; Status: ${e.getHttpStatusCode}")
          throw e
        }

      case e: Exception ⇒
        log.error(e, e.getMessage)
        throw e
    }

    leaseId
  }

  private[this] def writeAndRelease(file: CloudBlockBlob, leaseId: String, content: String): Unit = {

    // The access condition depends on the file existing
    val accessCondition = if (leaseId == "")
                            AccessCondition.generateEmptyCondition()
                          else
                            AccessCondition.generateLeaseCondition(leaseId)

    try {
      file.uploadText(content, "UTF-8", accessCondition, null, new OperationContext)

      // If this is a new file, there is no lease to release
      if (leaseId != "") file.releaseLease(accessCondition)
    } catch {

      case e: StorageException ⇒
        log.error(e, e.getMessage)
        throw e

      case e: IOException ⇒
        log.error(e, e.getMessage)
        throw e

      case e: Exception ⇒
        log.error(e, e.getMessage)
        throw e
    }
  }

  private[this] def filename(partition: Int): String = "partition-" + partition
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy