320 lines
11 KiB
Python
320 lines
11 KiB
Python
"""
|
|
This module provides utilities for performing Azure Blob Storage operations without requiring
|
|
the heavyweight azure-storage-blob library dependency
|
|
"""
|
|
|
|
import logging
|
|
import urllib
|
|
from copy import deepcopy
|
|
|
|
from mlflow.utils import rest_utils
|
|
from mlflow.utils.file_utils import read_chunk
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
_PUT_BLOCK_HEADERS = {
|
|
"x-ms-blob-type": "BlockBlob",
|
|
}
|
|
|
|
|
|
def put_adls_file_creation(sas_url, headers):
|
|
"""Performs an ADLS Azure file create `Put` operation
|
|
(https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/create)
|
|
|
|
Args:
|
|
sas_url: A shared access signature URL referring to the Azure ADLS server
|
|
to which the file creation command should be issued.
|
|
headers: Additional headers to include in the Put request body.
|
|
"""
|
|
request_url = _append_query_parameters(sas_url, {"resource": "file"})
|
|
|
|
request_headers = {}
|
|
for name, value in headers.items():
|
|
if _is_valid_adls_put_header(name):
|
|
request_headers[name] = value
|
|
else:
|
|
_logger.debug("Removed unsupported '%s' header for ADLS Gen2 Put operation", name)
|
|
|
|
with rest_utils.cloud_storage_http_request(
|
|
"put", request_url, headers=request_headers
|
|
) as response:
|
|
rest_utils.augmented_raise_for_status(response)
|
|
|
|
|
|
def patch_adls_file_upload(sas_url, local_file, start_byte, size, position, headers, is_single):
|
|
"""
|
|
Performs an ADLS Azure file create `Patch` operation
|
|
(https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update)
|
|
|
|
Args:
|
|
sas_url: A shared access signature URL referring to the Azure ADLS server
|
|
to which the file update command should be issued.
|
|
local_file: The local file to upload
|
|
start_byte: The starting byte of the local file to upload
|
|
size: The number of bytes to upload
|
|
position: Positional offset of the data in the Patch request
|
|
headers: Additional headers to include in the Patch request body
|
|
is_single: Whether this is the only patch operation for this file
|
|
"""
|
|
new_params = {"action": "append", "position": str(position)}
|
|
if is_single:
|
|
new_params["flush"] = "true"
|
|
request_url = _append_query_parameters(sas_url, new_params)
|
|
|
|
request_headers = {}
|
|
for name, value in headers.items():
|
|
if _is_valid_adls_patch_header(name):
|
|
request_headers[name] = value
|
|
else:
|
|
_logger.debug("Removed unsupported '%s' header for ADLS Gen2 Patch operation", name)
|
|
|
|
data = read_chunk(local_file, size, start_byte)
|
|
with rest_utils.cloud_storage_http_request(
|
|
"patch", request_url, data=data, headers=request_headers
|
|
) as response:
|
|
rest_utils.augmented_raise_for_status(response)
|
|
|
|
|
|
def patch_adls_flush(sas_url, position, headers):
|
|
"""Performs an ADLS Azure file flush `Patch` operation
|
|
(https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update)
|
|
|
|
Args:
|
|
sas_url: A shared access signature URL referring to the Azure ADLS server
|
|
to which the file update command should be issued.
|
|
position: The final size of the file to flush.
|
|
headers: Additional headers to include in the Patch request body.
|
|
|
|
"""
|
|
request_url = _append_query_parameters(sas_url, {"action": "flush", "position": str(position)})
|
|
|
|
request_headers = {}
|
|
for name, value in headers.items():
|
|
if _is_valid_adls_put_header(name):
|
|
request_headers[name] = value
|
|
else:
|
|
_logger.debug("Removed unsupported '%s' header for ADLS Gen2 Patch operation", name)
|
|
|
|
with rest_utils.cloud_storage_http_request(
|
|
"patch", request_url, headers=request_headers
|
|
) as response:
|
|
rest_utils.augmented_raise_for_status(response)
|
|
|
|
|
|
def put_block(sas_url, block_id, data, headers):
|
|
"""
|
|
Performs an Azure `Put Block` operation
|
|
(https://docs.microsoft.com/en-us/rest/api/storageservices/put-block)
|
|
|
|
Args:
|
|
sas_url: A shared access signature URL referring to the Azure Block Blob
|
|
to which the specified data should be staged.
|
|
block_id: A base64-encoded string identifying the block.
|
|
data: Data to include in the Put Block request body.
|
|
headers: Additional headers to include in the Put Block request body
|
|
(the `x-ms-blob-type` header is always included automatically).
|
|
"""
|
|
request_url = _append_query_parameters(sas_url, {"comp": "block", "blockid": block_id})
|
|
|
|
request_headers = deepcopy(_PUT_BLOCK_HEADERS)
|
|
for name, value in headers.items():
|
|
if _is_valid_put_block_header(name):
|
|
request_headers[name] = value
|
|
else:
|
|
_logger.debug("Removed unsupported '%s' header for Put Block operation", name)
|
|
|
|
with rest_utils.cloud_storage_http_request(
|
|
"put", request_url, data=data, headers=request_headers
|
|
) as response:
|
|
rest_utils.augmented_raise_for_status(response)
|
|
|
|
|
|
def put_block_list(sas_url, block_list, headers):
|
|
"""Performs an Azure `Put Block List` operation
|
|
(https://docs.microsoft.com/en-us/rest/api/storageservices/put-block-list)
|
|
|
|
Args:
|
|
sas_url: A shared access signature URL referring to the Azure Block Blob
|
|
to which the specified data should be staged.
|
|
block_list: A list of uncommitted base64-encoded string block IDs to commit. For
|
|
more information, see
|
|
https://docs.microsoft.com/en-us/rest/api/storageservices/put-block-list.
|
|
headers: Headers to include in the Put Block request body.
|
|
|
|
"""
|
|
request_url = _append_query_parameters(sas_url, {"comp": "blocklist"})
|
|
data = _build_block_list_xml(block_list)
|
|
|
|
request_headers = {}
|
|
for name, value in headers.items():
|
|
if _is_valid_put_block_list_header(name):
|
|
request_headers[name] = value
|
|
else:
|
|
_logger.debug("Removed unsupported '%s' header for Put Block List operation", name)
|
|
|
|
with rest_utils.cloud_storage_http_request(
|
|
"put", request_url, data=data, headers=request_headers
|
|
) as response:
|
|
rest_utils.augmented_raise_for_status(response)
|
|
|
|
|
|
def _append_query_parameters(url, parameters):
|
|
parsed_url = urllib.parse.urlparse(url)
|
|
query_dict = dict(urllib.parse.parse_qsl(parsed_url.query))
|
|
query_dict.update(parameters)
|
|
new_query = urllib.parse.urlencode(query_dict)
|
|
new_url_components = parsed_url._replace(query=new_query)
|
|
return urllib.parse.urlunparse(new_url_components)
|
|
|
|
|
|
def _build_block_list_xml(block_list):
|
|
xml = '<?xml version="1.0" encoding="utf-8"?>\n<BlockList>\n'
|
|
for block_id in block_list:
|
|
# Because block IDs are base64-encoded and base64 strings do not contain
|
|
# XML special characters, we can safely insert the block ID directly into
|
|
# the XML document
|
|
xml += f"<Uncommitted>{block_id}</Uncommitted>\n"
|
|
xml += "</BlockList>"
|
|
return xml
|
|
|
|
|
|
def _is_valid_put_block_list_header(header_name):
|
|
"""
|
|
Returns:
|
|
True if the specified header name is a valid header for the Put Block List operation,
|
|
False otherwise. For a list of valid headers, see https://docs.microsoft.com/en-us/
|
|
rest/api/storageservices/put-block-list#request-headers and https://docs.microsoft.com/
|
|
en-us/rest/api/storageservices/
|
|
specifying-conditional-headers-for-blob-service-operations#Subheading1.
|
|
"""
|
|
return header_name.startswith("x-ms-meta-") or header_name in {
|
|
"Authorization",
|
|
"Date",
|
|
"x-ms-date",
|
|
"x-ms-version",
|
|
"Content-Length",
|
|
"Content-MD5",
|
|
"x-ms-content-crc64",
|
|
"x-ms-blob-cache-control",
|
|
"x-ms-blob-content-type",
|
|
"x-ms-blob-content-encoding",
|
|
"x-ms-blob-content-language",
|
|
"x-ms-blob-content-md5",
|
|
"x-ms-encryption-scope",
|
|
"x-ms-tags",
|
|
"x-ms-lease-id",
|
|
"x-ms-client-request-id",
|
|
"x-ms-blob-content-disposition",
|
|
"x-ms-access-tier",
|
|
"If-Modified-Since",
|
|
"If-Unmodified-Since",
|
|
"If-Match",
|
|
"If-None-Match",
|
|
}
|
|
|
|
|
|
def _is_valid_put_block_header(header_name):
|
|
"""
|
|
Returns:
|
|
True if the specified header name is a valid header for the Put Block operation, False
|
|
otherwise. For a list of valid headers, see
|
|
https://docs.microsoft.com/en-us/rest/api/storageservices/put-block#request-headers and
|
|
https://docs.microsoft.com/en-us/rest/api/storageservices/put-block#
|
|
request-headers-customer-provided-encryption-keys.
|
|
"""
|
|
return header_name in {
|
|
"Authorization",
|
|
"x-ms-date",
|
|
"x-ms-version",
|
|
"Content-Length",
|
|
"Content-MD5",
|
|
"x-ms-content-crc64",
|
|
"x-ms-encryption-scope",
|
|
"x-ms-lease-id",
|
|
"x-ms-client-request-id",
|
|
"x-ms-encryption-key",
|
|
"x-ms-encryption-key-sha256",
|
|
"x-ms-encryption-algorithm",
|
|
}
|
|
|
|
|
|
def _is_valid_adls_put_header(header_name):
|
|
"""
|
|
Returns:
|
|
True if the specified header name is a valid header for the ADLS Put operation, False
|
|
otherwise. For a list of valid headers, see
|
|
https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/create
|
|
"""
|
|
return header_name in {
|
|
"Cache-Control",
|
|
"Content-Encoding",
|
|
"Content-Language",
|
|
"Content-Disposition",
|
|
"x-ms-cache-control",
|
|
"x-ms-content-type",
|
|
"x-ms-content-encoding",
|
|
"x-ms-content-language",
|
|
"x-ms-content-disposition",
|
|
"x-ms-rename-source",
|
|
"x-ms-lease-id",
|
|
"x-ms-properties",
|
|
"x-ms-permissions",
|
|
"x-ms-umask",
|
|
"x-ms-owner",
|
|
"x-ms-group",
|
|
"x-ms-acl",
|
|
"x-ms-proposed-lease-id",
|
|
"x-ms-expiry-option",
|
|
"x-ms-expiry-time",
|
|
"If-Match",
|
|
"If-None-Match",
|
|
"If-Modified-Since",
|
|
"If-Unmodified-Since",
|
|
"x-ms-source-if-match",
|
|
"x-ms-source-if-none-match",
|
|
"x-ms-source-if-modified-since",
|
|
"x-ms-source-if-unmodified-since",
|
|
"x-ms-encryption-key",
|
|
"x-ms-encryption-key-sha256",
|
|
"x-ms-encryption-algorithm",
|
|
"x-ms-encryption-context",
|
|
"x-ms-client-request-id",
|
|
"x-ms-date",
|
|
"x-ms-version",
|
|
}
|
|
|
|
|
|
def _is_valid_adls_patch_header(header_name):
|
|
"""
|
|
Returns:
|
|
True if the specified header name is a valid header for the ADLS Patch operation, False
|
|
otherwise. For a list of valid headers, see
|
|
https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update
|
|
"""
|
|
return header_name in {
|
|
"Content-Length",
|
|
"Content-MD5",
|
|
"x-ms-lease-id",
|
|
"x-ms-cache-control",
|
|
"x-ms-content-type",
|
|
"x-ms-content-disposition",
|
|
"x-ms-content-encoding",
|
|
"x-ms-content-language",
|
|
"x-ms-content-md5",
|
|
"x-ms-properties",
|
|
"x-ms-owner",
|
|
"x-ms-group",
|
|
"x-ms-permissions",
|
|
"x-ms-acl",
|
|
"If-Match",
|
|
"If-None-Match",
|
|
"If-Modified-Since",
|
|
"If-Unmodified-Since",
|
|
"x-ms-encryption-key",
|
|
"x-ms-encryption-key-sha256",
|
|
"x-ms-encryption-algorithm",
|
|
"x-ms-encryption-context",
|
|
"x-ms-client-request-id",
|
|
"x-ms-date",
|
|
"x-ms-version",
|
|
}
|