|  | @@ -0,0 +1,148 @@
 | 
	
		
			
				|  |  | +"""The Azure Storage Block Blob backend for Celery."""
 | 
	
		
			
				|  |  | +from __future__ import absolute_import, unicode_literals
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +from kombu.utils import cached_property
 | 
	
		
			
				|  |  | +from kombu.utils.encoding import bytes_to_str
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +from celery.exceptions import ImproperlyConfigured
 | 
	
		
			
				|  |  | +from celery.utils.log import get_logger
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +from .base import KeyValueStoreBackend
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +try:
 | 
	
		
			
				|  |  | +    import azure.storage as azurestorage
 | 
	
		
			
				|  |  | +    from azure.common import AzureMissingResourceHttpError
 | 
	
		
			
				|  |  | +    from azure.storage.blob import BlockBlobService
 | 
	
		
			
				|  |  | +    from azure.storage.common.retry import ExponentialRetry
 | 
	
		
			
				|  |  | +except ImportError:  # pragma: no cover
 | 
	
		
			
				|  |  | +    azurestorage = BlockBlobService = ExponentialRetry = \
 | 
	
		
			
				|  |  | +        AzureMissingResourceHttpError = None  # noqa
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +__all__ = ("AzureBlockBlobBackend",)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +LOGGER = get_logger(__name__)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class AzureBlockBlobBackend(KeyValueStoreBackend):
 | 
	
		
			
				|  |  | +    """Azure Storage Block Blob backend for Celery."""
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self,
 | 
	
		
			
				|  |  | +                 url=None,
 | 
	
		
			
				|  |  | +                 container_name=None,
 | 
	
		
			
				|  |  | +                 retry_initial_backoff_sec=None,
 | 
	
		
			
				|  |  | +                 retry_increment_base=None,
 | 
	
		
			
				|  |  | +                 retry_max_attempts=None,
 | 
	
		
			
				|  |  | +                 *args,
 | 
	
		
			
				|  |  | +                 **kwargs):
 | 
	
		
			
				|  |  | +        super(AzureBlockBlobBackend, self).__init__(*args, **kwargs)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if azurestorage is None:
 | 
	
		
			
				|  |  | +            raise ImproperlyConfigured(
 | 
	
		
			
				|  |  | +                "You need to install the azure-storage library to use the "
 | 
	
		
			
				|  |  | +                "AzureBlockBlob backend")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        conf = self.app.conf
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._connection_string = self._parse_url(url)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._container_name = (
 | 
	
		
			
				|  |  | +            container_name or
 | 
	
		
			
				|  |  | +            conf["azureblockblob_container_name"])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._retry_initial_backoff_sec = (
 | 
	
		
			
				|  |  | +            retry_initial_backoff_sec or
 | 
	
		
			
				|  |  | +            conf["azureblockblob_retry_initial_backoff_sec"])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._retry_increment_base = (
 | 
	
		
			
				|  |  | +            retry_increment_base or
 | 
	
		
			
				|  |  | +            conf["azureblockblob_retry_increment_base"])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._retry_max_attempts = (
 | 
	
		
			
				|  |  | +            retry_max_attempts or
 | 
	
		
			
				|  |  | +            conf["azureblockblob_retry_max_attempts"])
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @classmethod
 | 
	
		
			
				|  |  | +    def _parse_url(cls, url, prefix="azureblockblob://"):
 | 
	
		
			
				|  |  | +        connection_string = url[len(prefix):]
 | 
	
		
			
				|  |  | +        if not connection_string:
 | 
	
		
			
				|  |  | +            raise ImproperlyConfigured("Invalid URL")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return connection_string
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @cached_property
 | 
	
		
			
				|  |  | +    def _client(self):
 | 
	
		
			
				|  |  | +        """Return the Azure Storage Block Blob service.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        If this is the first call to the property, the client is created and
 | 
	
		
			
				|  |  | +        the container is created if it doesn't yet exist.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        client = BlockBlobService(connection_string=self._connection_string)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        created = client.create_container(
 | 
	
		
			
				|  |  | +            container_name=self._container_name, fail_on_exist=False)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if created:
 | 
	
		
			
				|  |  | +            LOGGER.info("Created Azure Blob Storage container %s",
 | 
	
		
			
				|  |  | +                        self._container_name)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        client.retry = ExponentialRetry(
 | 
	
		
			
				|  |  | +            initial_backoff=self._retry_initial_backoff_sec,
 | 
	
		
			
				|  |  | +            increment_base=self._retry_increment_base,
 | 
	
		
			
				|  |  | +            max_attempts=self._retry_max_attempts).retry
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return client
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def get(self, key):
 | 
	
		
			
				|  |  | +        """Read the value stored at the given key.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Args:
 | 
	
		
			
				|  |  | +              key: The key for which to read the value.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        key = bytes_to_str(key)
 | 
	
		
			
				|  |  | +        LOGGER.debug("Getting Azure Block Blob %s/%s",
 | 
	
		
			
				|  |  | +                     self._container_name, key)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            return self._client.get_blob_to_text(
 | 
	
		
			
				|  |  | +                self._container_name, key).content
 | 
	
		
			
				|  |  | +        except AzureMissingResourceHttpError:
 | 
	
		
			
				|  |  | +            return None
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def set(self, key, value):
 | 
	
		
			
				|  |  | +        """Store a value for a given key.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Args:
 | 
	
		
			
				|  |  | +              key: The key at which to store the value.
 | 
	
		
			
				|  |  | +              value: The value to store.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        key = bytes_to_str(key)
 | 
	
		
			
				|  |  | +        LOGGER.debug("Creating Azure Block Blob at %s/%s",
 | 
	
		
			
				|  |  | +                     self._container_name, key)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return self._client.create_blob_from_text(
 | 
	
		
			
				|  |  | +            self._container_name, key, value)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def mget(self, keys):
 | 
	
		
			
				|  |  | +        """Read all the values for the provided keys.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Args:
 | 
	
		
			
				|  |  | +              keys: The list of keys to read.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        return [self.get(key) for key in keys]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def delete(self, key):
 | 
	
		
			
				|  |  | +        """Delete the value at a given key.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Args:
 | 
	
		
			
				|  |  | +              key: The key of the value to delete.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        """
 | 
	
		
			
				|  |  | +        key = bytes_to_str(key)
 | 
	
		
			
				|  |  | +        LOGGER.debug("Deleting Azure Block Blob at %s/%s",
 | 
	
		
			
				|  |  | +                     self._container_name, key)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self._client.delete_blob(self._container_name, key)
 |