Ejemplo n.º 1
0
    def test_append_block_from_url_and_validate_content_md5(self):
        # Arrange
        src_md5 = _get_content_md5(self.source_blob_data)
        dest_blob_name = self.get_resource_name('destblob')
        self.bs.create_blob(self.container_name, dest_blob_name)

        # Act part 1: make append block from url calls with correct md5
        resp = self.bs.append_block_from_url(self.container_name,
                                             dest_blob_name,
                                             self.source_blob_url,
                                             source_content_md5=src_md5)
        self.assertEqual(resp.append_offset, 0)
        self.assertEqual(resp.committed_block_count, 1)
        self.assertIsNotNone(resp.etag)
        self.assertIsNotNone(resp.last_modified)

        # Assert the destination blob is constructed correctly
        blob = self.bs.get_blob_properties(self.container_name, dest_blob_name)
        self.assertBlobEqual(self.container_name, dest_blob_name,
                             self.source_blob_data)
        self.assertEqual(blob.properties.etag, resp.etag)
        self.assertEqual(blob.properties.last_modified, resp.last_modified)

        # Act part 2: put block from url with wrong md5
        with self.assertRaises(AzureHttpError):
            self.bs.append_block_from_url(
                self.container_name,
                dest_blob_name,
                self.source_blob_url,
                source_content_md5=_get_content_md5(b"POTATO"))
    def test_put_block_from_url_and_validate_content_md5(self):
        # Arrange
        dest_blob_name = self.get_resource_name('destblob')
        src_md5 = _get_content_md5(self.source_blob_data)

        # Act part 1: put block from url with md5 validation
        self.bs.put_block_from_url(self.container_name,
                                   dest_blob_name,
                                   self.source_blob_url,
                                   block_id=1,
                                   source_content_md5=src_md5)

        # Assert block was staged
        block_list = self.bs.get_block_list(self.container_name,
                                            dest_blob_name, None, 'all')
        self.assertEqual(len(block_list.uncommitted_blocks), 1)
        self.assertEqual(len(block_list.committed_blocks), 0)

        # Act part 2: put block from url with wrong md5
        with self.assertRaises(AzureHttpError):
            self.bs.put_block_from_url(
                self.container_name,
                dest_blob_name,
                self.source_blob_url,
                block_id=2,
                source_content_md5=_get_content_md5(b"POTATO"))

        # Assert block was not staged
        block_list = self.bs.get_block_list(self.container_name,
                                            dest_blob_name, None, 'all')
        self.assertEqual(len(block_list.uncommitted_blocks), 1)
        self.assertEqual(len(block_list.committed_blocks), 0)
Ejemplo n.º 3
0
def _parse_blob(response, name, snapshot, validate_content=False, require_encryption=False,
                key_encryption_key=None, key_resolver_function=None, start_offset=None, end_offset=None):
    if response is None:
        return None

    metadata = _parse_metadata(response)
    props = _parse_properties(response, BlobProperties)

    # For range gets, only look at 'x-ms-blob-content-md5' for overall MD5
    content_settings = getattr(props, 'content_settings')
    if 'content-range' in response.headers:
        if 'x-ms-blob-content-md5' in response.headers:
            setattr(content_settings, 'content_md5', _to_str(response.headers['x-ms-blob-content-md5']))
        else:
            delattr(content_settings, 'content_md5')

    if validate_content:
        computed_md5 = _get_content_md5(response.body)
        _validate_content_match(response.headers['content-md5'], computed_md5)

    if key_encryption_key is not None or key_resolver_function is not None:
        try:
            response.body = _decrypt_blob(require_encryption, key_encryption_key, key_resolver_function,
                                          response, start_offset, end_offset)
        except:
            raise AzureException(_ERROR_DECRYPTION_FAILURE)

    return Blob(name, snapshot, response.body, props, metadata)
    def _put_block_list(
            self, container_name, blob_name, block_list, content_settings=None,
            metadata=None, validate_content=False, lease_id=None, if_modified_since=None,
            if_unmodified_since=None, if_match=None, if_none_match=None,
            timeout=None, encryption_data=None):
        '''
        See put_block_list for more details. This helper method
        allows for encryption or other such special behavior because
        it is safely handled by the library. These behaviors are
        prohibited in the public version of this function.
        :param str encryption_data:
            A JSON formatted string containing the encryption metadata generated for this 
            blob if it was encrypted all at once upon upload. This should only be passed
            in by internal methods.
        '''

        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_not_none('block_list', block_list)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {
            'comp': 'blocklist',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-lease-id': _to_str(lease_id),
            'If-Modified-Since': _datetime_to_utc_string(if_modified_since),
            'If-Unmodified-Since': _datetime_to_utc_string(if_unmodified_since),
            'If-Match': _to_str(if_match),
            'If-None-Match': _to_str(if_none_match),
        }
        _add_metadata_headers(metadata, request)
        if content_settings is not None:
            request.headers.update(content_settings._to_headers())
        request.body = _get_request_body(
            _convert_block_list_to_xml(block_list))

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        if encryption_data is not None:
            request.headers['x-ms-meta-encryptiondata'] = encryption_data

        return self._perform_request(request, _parse_base_properties)
Ejemplo n.º 5
0
    def _put_block_list(
            self, container_name, blob_name, block_list, content_settings=None,
            metadata=None, validate_content=False, lease_id=None, if_modified_since=None,
            if_unmodified_since=None, if_match=None, if_none_match=None,
            timeout=None, encryption_data=None):
        '''
        See put_block_list for more details. This helper method
        allows for encryption or other such special behavior because
        it is safely handled by the library. These behaviors are
        prohibited in the public version of this function.
        :param str encryption_data:
            A JSON formatted string containing the encryption metadata generated for this 
            blob if it was encrypted all at once upon upload. This should only be passed
            in by internal methods.
        '''

        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_not_none('block_list', block_list)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {
            'comp': 'blocklist',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-lease-id': _to_str(lease_id),
            'If-Modified-Since': _datetime_to_utc_string(if_modified_since),
            'If-Unmodified-Since': _datetime_to_utc_string(if_unmodified_since),
            'If-Match': _to_str(if_match),
            'If-None-Match': _to_str(if_none_match),
        }
        _add_metadata_headers(metadata, request)
        if content_settings is not None:
            request.headers.update(content_settings._to_headers())
        request.body = _get_request_body(
            _convert_block_list_to_xml(block_list))

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        if encryption_data is not None:
            request.headers['x-ms-meta-encryptiondata'] = encryption_data

        return self._perform_request(request, _parse_base_properties)
Ejemplo n.º 6
0
def _parse_file(response, name, validate_content=False):
    if response is None:
        return None

    metadata = _parse_metadata(response)
    props = _parse_properties(response, FileProperties)

    # For range gets, only look at 'x-ms-content-md5' for overall MD5
    content_settings = getattr(props, 'content_settings')
    if 'content-range' in response.headers:
        if 'x-ms-content-md5' in response.headers:
            setattr(content_settings, 'content_md5', _to_str(response.headers['x-ms-content-md5']))
        else:
            delattr(content_settings, 'content_md5')

    if validate_content:
        computed_md5 = _get_content_md5(response.body)
        _validate_content_match(response.headers['content-md5'], computed_md5)

    return File(name, response.body, props, metadata)
def _parse_file(response, name, validate_content=False):
    if response is None:
        return None

    metadata = _parse_metadata(response)
    props = _parse_properties(response, FileProperties)

    # For range gets, only look at 'x-ms-content-md5' for overall MD5
    content_settings = getattr(props, 'content_settings')
    if 'content-range' in response.headers:
        if 'x-ms-content-md5' in response.headers:
            setattr(content_settings, 'content_md5', _to_str(response.headers['x-ms-content-md5']))
        else:
            delattr(content_settings, 'content_md5')

    if validate_content:
        computed_md5 = _get_content_md5(response.body)
        _validate_content_match(response.headers['content-md5'], computed_md5)

    return File(name, response.body, props, metadata)
    def _put_block(self, container_name, blob_name, block, block_id,
                   validate_content=False, lease_id=None, timeout=None):
        '''
        See put_block for more details. This helper method
        allows for encryption or other such special behavior because
        it is safely handled by the library. These behaviors are
        prohibited in the public version of this function.
        '''

        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_not_none('block', block)
        _validate_not_none('block_id', block_id)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {
            'comp': 'block',
            'blockid': _encode_base64(_to_str(block_id)),
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-lease-id': _to_str(lease_id)
        }
        request.body = _get_data_bytes_or_stream_only('block', block)
        if hasattr(request.body, 'read'):
            if _len_plus(request.body) is None:
                try:
                    data = b''
                    for chunk in iter(lambda: request.body.read(4096), b""):
                        data += chunk
                    request.body = data
                except AttributeError:
                    raise ValueError(_ERROR_VALUE_SHOULD_BE_STREAM.format('request.body'))

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        self._perform_request(request)
Ejemplo n.º 9
0
    def _put_block(self, container_name, blob_name, block, block_id,
                   validate_content=False, lease_id=None, timeout=None):
        '''
        See put_block for more details. This helper method
        allows for encryption or other such special behavior because
        it is safely handled by the library. These behaviors are
        prohibited in the public version of this function.
        '''

        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_not_none('block', block)
        _validate_not_none('block_id', block_id)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {
            'comp': 'block',
            'blockid': _encode_base64(_to_str(block_id)),
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-lease-id': _to_str(lease_id)
        }
        request.body = _get_data_bytes_or_stream_only('block', block)
        if hasattr(request.body, 'read'):
            if _len_plus(request.body) is None:
                try:
                    data = b''
                    for chunk in iter(lambda: request.body.read(4096), b""):
                        data += chunk
                    request.body = data
                except AttributeError:
                    raise ValueError(_ERROR_VALUE_SHOULD_BE_STREAM.format('request.body'))

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        self._perform_request(request)
    def _put_blob(self, container_name, blob_name, blob, content_settings=None,
                  metadata=None, validate_content=False, lease_id=None, if_modified_since=None,
                  if_unmodified_since=None, if_match=None, if_none_match=None,
                  timeout=None):
        '''
        Creates a blob or updates an existing blob.

        See create_blob_from_* for high level
        functions that handle the creation and upload of large blobs with
        automatic chunking and progress notifications.

        :param str container_name:
            Name of existing container.
        :param str blob_name:
            Name of blob to create or update.
        :param bytes blob:
            Content of blob as bytes (size < 64MB). For larger size, you
            must call put_block and put_block_list to set content of blob.
        :param ~azure.storage.blob.models.ContentSettings content_settings:
            ContentSettings object used to set properties on the blob.
        :param metadata:
            Name-value pairs associated with the blob as metadata.
        :param bool validate_content:
            If true, calculates an MD5 hash of the blob content. The storage
            service checks the hash of the content that has arrived
            with the hash that was sent. This is primarily valuable for detecting
            bitflips on the wire if using http instead of https as https (the default)
            will already validate. Note that this MD5 hash is not stored with the
            blob.
        :param str lease_id:
            Required if the blob has an active lease.
        :param datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :param datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :param str if_match:
            An ETag value, or the wildcard character (*). Specify this header to perform
            the operation only if the resource's ETag matches the value specified.
        :param str if_none_match:
            An ETag value, or the wildcard character (*). Specify this header
            to perform the operation only if the resource's ETag does not match
            the value specified. Specify the wildcard character (*) to perform
            the operation only if the resource does not exist, and fail the
            operation if it does exist.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: ETag and last modified properties for the new Block Blob
        :rtype: :class:`~azure.storage.blob.models.ResourceProperties`
        '''
        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_encryption_required(self.require_encryption, self.key_encryption_key)

        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {'timeout': _int_to_str(timeout)}
        request.headers = {
            'x-ms-blob-type': _to_str(self.blob_type),
            'x-ms-lease-id': _to_str(lease_id),
            'If-Modified-Since': _datetime_to_utc_string(if_modified_since),
            'If-Unmodified-Since': _datetime_to_utc_string(if_unmodified_since),
            'If-Match': _to_str(if_match),
            'If-None-Match': _to_str(if_none_match)
        }
        _add_metadata_headers(metadata, request)
        if content_settings is not None:
            request.headers.update(content_settings._to_headers())
        blob = _get_data_bytes_only('blob', blob)
        if self.key_encryption_key:
            encryption_data, blob = _encrypt_blob(blob, self.key_encryption_key)
            request.headers['x-ms-meta-encryptiondata'] = encryption_data
        request.body = blob

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        return self._perform_request(request, _parse_base_properties)
Ejemplo n.º 11
0
    def _put_blob(self, container_name, blob_name, blob, content_settings=None,
                  metadata=None, validate_content=False, lease_id=None, if_modified_since=None,
                  if_unmodified_since=None, if_match=None, if_none_match=None,
                  timeout=None):
        '''
        Creates a blob or updates an existing blob.

        See create_blob_from_* for high level
        functions that handle the creation and upload of large blobs with
        automatic chunking and progress notifications.

        :param str container_name:
            Name of existing container.
        :param str blob_name:
            Name of blob to create or update.
        :param bytes blob:
            Content of blob as bytes (size < 64MB). For larger size, you
            must call put_block and put_block_list to set content of blob.
        :param ~azure.storage.blob.models.ContentSettings content_settings:
            ContentSettings object used to set properties on the blob.
        :param metadata:
            Name-value pairs associated with the blob as metadata.
        :param bool validate_content:
            If true, calculates an MD5 hash of the blob content. The storage
            service checks the hash of the content that has arrived
            with the hash that was sent. This is primarily valuable for detecting
            bitflips on the wire if using http instead of https as https (the default)
            will already validate. Note that this MD5 hash is not stored with the
            blob.
        :param str lease_id:
            Required if the blob has an active lease.
        :param datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :param datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :param str if_match:
            An ETag value, or the wildcard character (*). Specify this header to perform
            the operation only if the resource's ETag matches the value specified.
        :param str if_none_match:
            An ETag value, or the wildcard character (*). Specify this header
            to perform the operation only if the resource's ETag does not match
            the value specified. Specify the wildcard character (*) to perform
            the operation only if the resource does not exist, and fail the
            operation if it does exist.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: ETag and last modified properties for the new Block Blob
        :rtype: :class:`~azure.storage.blob.models.ResourceProperties`
        '''
        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_encryption_required(self.require_encryption, self.key_encryption_key)

        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {'timeout': _int_to_str(timeout)}
        request.headers = {
            'x-ms-blob-type': _to_str(self.blob_type),
            'x-ms-lease-id': _to_str(lease_id),
            'If-Modified-Since': _datetime_to_utc_string(if_modified_since),
            'If-Unmodified-Since': _datetime_to_utc_string(if_unmodified_since),
            'If-Match': _to_str(if_match),
            'If-None-Match': _to_str(if_none_match)
        }
        _add_metadata_headers(metadata, request)
        if content_settings is not None:
            request.headers.update(content_settings._to_headers())
        blob = _get_data_bytes_only('blob', blob)
        if self.key_encryption_key:
            encryption_data, blob = _encrypt_blob(blob, self.key_encryption_key)
            request.headers['x-ms-meta-encryptiondata'] = encryption_data
        request.body = blob

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        return self._perform_request(request, _parse_base_properties)
Ejemplo n.º 12
0
    def append_block(self,
                     container_name,
                     blob_name,
                     block,
                     validate_content=False,
                     maxsize_condition=None,
                     appendpos_condition=None,
                     lease_id=None,
                     if_modified_since=None,
                     if_unmodified_since=None,
                     if_match=None,
                     if_none_match=None,
                     timeout=None,
                     cpk=None):
        '''
        Commits a new block of data to the end of an existing append blob.
        
        :param str container_name:
            Name of existing container.
        :param str blob_name:
            Name of existing blob.
        :param bytes block:
            Content of the block in bytes.
        :param bool validate_content:
            If true, calculates an MD5 hash of the block content. The storage 
            service checks the hash of the content that has arrived
            with the hash that was sent. This is primarily valuable for detecting 
            bitflips on the wire if using http instead of https as https (the default) 
            will already validate. Note that this MD5 hash is not stored with the 
            blob.
        :param int maxsize_condition:
            Optional conditional header. The max length in bytes permitted for
            the append blob. If the Append Block operation would cause the blob
            to exceed that limit or if the blob size is already greater than the
            value specified in this header, the request will fail with
            MaxBlobSizeConditionNotMet error (HTTP status code 412 - Precondition Failed).
        :param int appendpos_condition:
            Optional conditional header, used only for the Append Block operation.
            A number indicating the byte offset to compare. Append Block will
            succeed only if the append position is equal to this number. If it
            is not, the request will fail with the
            AppendPositionConditionNotMet error
            (HTTP status code 412 - Precondition Failed).
        :param str lease_id:
            Required if the blob has an active lease.
        :param datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC. 
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :param datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :param str if_match:
            An ETag value, or the wildcard character (*). Specify this header to perform
            the operation only if the resource's ETag matches the value specified.
        :param str if_none_match:
            An ETag value, or the wildcard character (*). Specify this header
            to perform the operation only if the resource's ETag does not match
            the value specified. Specify the wildcard character (*) to perform
            the operation only if the resource does not exist, and fail the
            operation if it does exist.
        :param ~azure.storage.blob.models.CustomerProvidedEncryptionKey cpk:
            Encrypts the data on the service-side with the given key.
            Use of customer-provided keys must be done over HTTPS.
            As the encryption key itself is provided in the request,
            a secure connection must be established to transfer the key.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return:
            ETag, last modified, append offset, and committed block count 
            properties for the updated Append Blob
        :rtype: :class:`~azure.storage.blob.models.AppendBlockProperties`
        '''
        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_not_none('block', block)
        _validate_encryption_unsupported(self.require_encryption,
                                         self.key_encryption_key)

        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {
            'comp': 'appendblock',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-blob-condition-maxsize': _to_str(maxsize_condition),
            'x-ms-blob-condition-appendpos': _to_str(appendpos_condition),
            'x-ms-lease-id': _to_str(lease_id),
            'If-Modified-Since': _datetime_to_utc_string(if_modified_since),
            'If-Unmodified-Since':
            _datetime_to_utc_string(if_unmodified_since),
            'If-Match': _to_str(if_match),
            'If-None-Match': _to_str(if_none_match)
        }
        _validate_and_add_cpk_headers(request,
                                      encryption_key=cpk,
                                      protocol=self.protocol)
        request.body = _get_data_bytes_only('block', block)

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        return self._perform_request(request, _parse_append_block)
    def append_block(self, container_name, blob_name, block,
                     validate_content=False, maxsize_condition=None,
                     appendpos_condition=None,
                     lease_id=None, if_modified_since=None,
                     if_unmodified_since=None, if_match=None,
                     if_none_match=None, timeout=None):
        '''
        Commits a new block of data to the end of an existing append blob.
        
        :param str container_name:
            Name of existing container.
        :param str blob_name:
            Name of existing blob.
        :param bytes block:
            Content of the block in bytes.
        :param bool validate_content:
            If true, calculates an MD5 hash of the block content. The storage 
            service checks the hash of the content that has arrived
            with the hash that was sent. This is primarily valuable for detecting 
            bitflips on the wire if using http instead of https as https (the default) 
            will already validate. Note that this MD5 hash is not stored with the 
            blob.
        :param int maxsize_condition:
            Optional conditional header. The max length in bytes permitted for
            the append blob. If the Append Block operation would cause the blob
            to exceed that limit or if the blob size is already greater than the
            value specified in this header, the request will fail with
            MaxBlobSizeConditionNotMet error (HTTP status code 412 - Precondition Failed).
        :param int appendpos_condition:
            Optional conditional header, used only for the Append Block operation.
            A number indicating the byte offset to compare. Append Block will
            succeed only if the append position is equal to this number. If it
            is not, the request will fail with the
            AppendPositionConditionNotMet error
            (HTTP status code 412 - Precondition Failed).
        :param str lease_id:
            Required if the blob has an active lease.
        :param datetime if_modified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC. 
            Specify this header to perform the operation only
            if the resource has been modified since the specified time.
        :param datetime if_unmodified_since:
            A DateTime value. Azure expects the date value passed in to be UTC.
            If timezone is included, any non-UTC datetimes will be converted to UTC.
            If a date is passed in without timezone info, it is assumed to be UTC.
            Specify this header to perform the operation only if
            the resource has not been modified since the specified date/time.
        :param str if_match:
            An ETag value, or the wildcard character (*). Specify this header to perform
            the operation only if the resource's ETag matches the value specified.
        :param str if_none_match:
            An ETag value, or the wildcard character (*). Specify this header
            to perform the operation only if the resource's ETag does not match
            the value specified. Specify the wildcard character (*) to perform
            the operation only if the resource does not exist, and fail the
            operation if it does exist.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return:
            ETag, last modified, append offset, and committed block count 
            properties for the updated Append Blob
        :rtype: :class:`~azure.storage.blob.models.AppendBlockProperties`
        '''
        _validate_not_none('container_name', container_name)
        _validate_not_none('blob_name', blob_name)
        _validate_not_none('block', block)
        _validate_encryption_unsupported(self.require_encryption, self.key_encryption_key)

        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(container_name, blob_name)
        request.query = {
            'comp': 'appendblock',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-blob-condition-maxsize': _to_str(maxsize_condition),
            'x-ms-blob-condition-appendpos': _to_str(appendpos_condition),
            'x-ms-lease-id': _to_str(lease_id),
            'If-Modified-Since': _datetime_to_utc_string(if_modified_since),
            'If-Unmodified-Since': _datetime_to_utc_string(if_unmodified_since),
            'If-Match': _to_str(if_match),
            'If-None-Match': _to_str(if_none_match)
        }
        request.body = _get_data_bytes_only('block', block)

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        return self._perform_request(request, _parse_append_block)