Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[Storage] [STG94] Snapshot Management Support via REST for NFS Shares…
… & New Header Custom Sorter (#35359)
  • Loading branch information
vincenttran-msft authored May 30, 2024
commit 31bd830d747b8a0d5ddeb77b574d880c20877524
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-blob/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/storage/azure-storage-blob",
"Tag": "python/storage/azure-storage-blob_863b753fbb"
"Tag": "python/storage/azure-storage-blob_275000b78a"
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import re
from typing import List, Tuple
from urllib.parse import unquote, urlparse
from functools import cmp_to_key

try:
from yarl import URL
Expand All @@ -27,6 +28,66 @@
logger = logging.getLogger(__name__)


table_lv0 = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71c, 0x0, 0x71f, 0x721, 0x723, 0x725,
0x0, 0x0, 0x0, 0x72d, 0x803, 0x0, 0x0, 0x733, 0x0, 0xd03, 0xd1a, 0xd1c, 0xd1e,
0xd20, 0xd22, 0xd24, 0xd26, 0xd28, 0xd2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25, 0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51,
0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99, 0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9,
0x0, 0x0, 0x0, 0x743, 0x744, 0x748, 0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25,
0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51, 0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99,
0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9, 0x0, 0x74c, 0x0, 0x750, 0x0,
]

table_lv4 = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8212, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
]

def compare(lhs: str, rhs: str) -> int: # pylint:disable=too-many-return-statements
tables = [table_lv0, table_lv4]
curr_level, i, j, n = 0, 0, 0, len(tables)
lhs_len = len(lhs)
rhs_len = len(rhs)
while curr_level < n:
if curr_level == (n - 1) and i != j:
if i > j:
return -1
if i < j:
return 1
return 0

w1 = tables[curr_level][ord(lhs[i])] if i < lhs_len else 0x1
w2 = tables[curr_level][ord(rhs[j])] if j < rhs_len else 0x1

if w1 == 0x1 and w2 == 0x1:
i = 0
j = 0
curr_level += 1
elif w1 == w2:
i += 1
j += 1
elif w1 == 0:
i += 1
elif w2 == 0:
j += 1
else:
if w1 < w2:
return -1
if w1 > w2:
return 1
return 0
return 0


# wraps a given exception with the desired exception type
def _wrap_exception(ex, desired_type):
msg = ""
Expand All @@ -36,8 +97,6 @@ def _wrap_exception(ex, desired_type):

# This method attempts to emulate the sorting done by the service
def _storage_header_sort(input_headers: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
# Define the custom alphabet for weights
custom_weights = "-!#$%&*.^_|~+\"\'(),/`~0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]abcdefghijklmnopqrstuvwxyz{}"

# Build dict of tuples and list of keys
header_dict = {}
Expand All @@ -46,9 +105,8 @@ def _storage_header_sort(input_headers: List[Tuple[str, str]]) -> List[Tuple[str
header_dict[k] = v
header_keys.append(k)

# Sort according to custom defined weights
try:
header_keys = sorted(header_keys, key=lambda word: [custom_weights.index(c) for c in word])
header_keys = sorted(header_keys, key=cmp_to_key(compare))
except ValueError as exc:
raise ValueError("Illegal character encountered when sorting headers.") from exc

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3124,4 +3124,45 @@ def test_header_metadata_sort_in_upload_blob(self, **kwargs):
# Act
blob_client.upload_blob(data, length=len(data), metadata=metadata)

@BlobPreparer()
@recorded_by_proxy
def test_header_metadata_sort_in_upload_blob_translation(self, **kwargs):
storage_account_name = kwargs.pop("storage_account_name")
storage_account_key = kwargs.pop("storage_account_key")

self._setup()
data = b'hello world'
bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), storage_account_key)
try:
container_client = bsc.create_container(self.container_name)
except:
container_client = bsc.get_container_client(self.container_name)
blob_client = container_client.get_blob_client('blob1')

# Hand-picked metadata examples that sorted incorrectly with our previous implementation.
metadata = {
'test': 'val',
'test-': 'val',
'test--': 'val',
'test-_': 'val',
'test_-': 'val',
'test__': 'val',
'test-a': 'val',
'test-A': 'val',
'test-_A': 'val',
'test_a': 'val',
'test_Z': 'val',
'test_a_': 'val',
'test_a-': 'val',
'test_a-_': 'val',
}

# Act
# If we hit invalid metadata error, that means we have successfully sorted headers properly to pass auth error
with pytest.raises(HttpResponseError) as e:
blob_client.upload_blob(data, length=len(data), metadata=metadata)

# Assert
assert StorageErrorCode.invalid_metadata == e.value.error_code

# ------------------------------------------------------------------------------
Original file line number Diff line number Diff line change
Expand Up @@ -3092,4 +3092,69 @@ async def test_header_metadata_sort_in_upload_blob(self, **kwargs):
# Act
await blob_client.upload_blob(data, length=len(data), metadata=metadata)

@BlobPreparer()
@recorded_by_proxy_async
async def test_header_metadata_sort_in_upload_blob(self, **kwargs):
storage_account_name = kwargs.pop("storage_account_name")
storage_account_key = kwargs.pop("storage_account_key")

self._setup()
data = b'hello world'
bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), storage_account_key)
try:
container_client = await bsc.create_container(self.container_name)
except:
container_client = bsc.get_container_client(self.container_name)
blob_client = container_client.get_blob_client('blob1')

# Hand-picked metadata examples as Python & service don't sort '_' with the same weight
metadata = {'a0': 'a', 'a1': 'a', 'a2': 'a', 'a3': 'a', 'a4': 'a', 'a5': 'a', 'a6': 'a', 'a7': 'a', 'a8': 'a',
'a9': 'a', '_': 'a', '_a': 'a', 'a_': 'a', '__': 'a', '_a_': 'a', 'b': 'a', 'c': 'a', 'y': 'a',
'z': 'z_', '_z': 'a', '_F': 'a', 'F': 'a', 'F_': 'a', '_F_': 'a', '__F': 'a', '__a': 'a', 'a__': 'a'
}

# Act
await blob_client.upload_blob(data, length=len(data), metadata=metadata)

@BlobPreparer()
@recorded_by_proxy_async
async def test_header_metadata_sort_in_upload_blob_translation(self, **kwargs):
storage_account_name = kwargs.pop("storage_account_name")
storage_account_key = kwargs.pop("storage_account_key")

self._setup()
data = b'hello world'
bsc = BlobServiceClient(self.account_url(storage_account_name, "blob"), storage_account_key)
try:
container_client = await bsc.create_container(self.container_name)
except:
container_client = bsc.get_container_client(self.container_name)
blob_client = container_client.get_blob_client('blob1')

# Hand-picked metadata examples that sorted incorrectly with our previous implementation.
metadata = {
'test': 'val',
'test-': 'val',
'test--': 'val',
'test-_': 'val',
'test_-': 'val',
'test__': 'val',
'test-a': 'val',
'test-A': 'val',
'test-_A': 'val',
'test_a': 'val',
'test_Z': 'val',
'test_a_': 'val',
'test_a-': 'val',
'test_a-_': 'val',
}

# Act
# If we hit invalid metadata error, that means we have successfully sorted headers properly to pass auth error
with pytest.raises(HttpResponseError) as e:
await blob_client.upload_blob(data, length=len(data), metadata=metadata)

# Assert
assert StorageErrorCode.invalid_metadata == e.value.error_code

# ------------------------------------------------------------------------------
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import re
from typing import List, Tuple
from urllib.parse import unquote, urlparse
from functools import cmp_to_key

try:
from yarl import URL
Expand All @@ -27,6 +28,66 @@
logger = logging.getLogger(__name__)


table_lv0 = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71c, 0x0, 0x71f, 0x721, 0x723, 0x725,
0x0, 0x0, 0x0, 0x72d, 0x803, 0x0, 0x0, 0x733, 0x0, 0xd03, 0xd1a, 0xd1c, 0xd1e,
0xd20, 0xd22, 0xd24, 0xd26, 0xd28, 0xd2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25, 0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51,
0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99, 0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9,
0x0, 0x0, 0x0, 0x743, 0x744, 0x748, 0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25,
0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51, 0xe70, 0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99,
0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9, 0x0, 0x74c, 0x0, 0x750, 0x0,
]

table_lv4 = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8212, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
]

def compare(lhs: str, rhs: str) -> int: # pylint:disable=too-many-return-statements
tables = [table_lv0, table_lv4]
curr_level, i, j, n = 0, 0, 0, len(tables)
lhs_len = len(lhs)
rhs_len = len(rhs)
while curr_level < n:
if curr_level == (n - 1) and i != j:
if i > j:
return -1
if i < j:
return 1
return 0

w1 = tables[curr_level][ord(lhs[i])] if i < lhs_len else 0x1
w2 = tables[curr_level][ord(rhs[j])] if j < rhs_len else 0x1

if w1 == 0x1 and w2 == 0x1:
i = 0
j = 0
curr_level += 1
elif w1 == w2:
i += 1
j += 1
elif w1 == 0:
i += 1
elif w2 == 0:
j += 1
else:
if w1 < w2:
return -1
if w1 > w2:
return 1
return 0
return 0


# wraps a given exception with the desired exception type
def _wrap_exception(ex, desired_type):
msg = ""
Expand All @@ -36,8 +97,6 @@ def _wrap_exception(ex, desired_type):

# This method attempts to emulate the sorting done by the service
def _storage_header_sort(input_headers: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
# Define the custom alphabet for weights
custom_weights = "-!#$%&*.^_|~+\"\'(),/`~0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]abcdefghijklmnopqrstuvwxyz{}"

# Build dict of tuples and list of keys
header_dict = {}
Expand All @@ -46,9 +105,8 @@ def _storage_header_sort(input_headers: List[Tuple[str, str]]) -> List[Tuple[str
header_dict[k] = v
header_keys.append(k)

# Sort according to custom defined weights
try:
header_keys = sorted(header_keys, key=lambda word: [custom_weights.index(c) for c in word])
header_keys = sorted(header_keys, key=cmp_to_key(compare))
except ValueError as exc:
raise ValueError("Illegal character encountered when sorting headers.") from exc

Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-file-share/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/storage/azure-storage-file-share",
"Tag": "python/storage/azure-storage-file-share_a600655193"
"Tag": "python/storage/azure-storage-file-share_e105feb7fb"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@

# This file is used for handwritten extensions to the generated code. Example:
# https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/customize_code/how-to-patch-sdk-code.md


def patch_sdk():
pass
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@

# This file is used for handwritten extensions to the generated code. Example:
# https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/customize_code/how-to-patch-sdk-code.md


def patch_sdk():
pass
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------

"""Customize generated code here.

Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize
Expand Down
Loading