From 03bfd33137f0004e905cfd92db96f0e9ab7c306e Mon Sep 17 00:00:00 2001 From: zezha-msft Date: Tue, 2 Oct 2018 10:26:37 -0700 Subject: [PATCH] Added support for directory operations --- azure-storage-blob/ChangeLog.md | 6 + .../azure/storage/blob/_constants.py | 3 + .../azure/storage/blob/_deserialization.py | 5 + .../azure/storage/blob/_serialization.py | 9 + .../azure/storage/blob/baseblobservice.py | 270 +++++++++- tests/blob/test_directory.py | 393 ++++++++++++++ ...directory_with_hierarchical_namespace.yaml | 245 +++++++++ ...ectory_without_hierarchical_namespace.yaml | 499 ++++++++++++++++++ ...directory_with_hierarchical_namespace.yaml | 45 ++ ...ectory_without_hierarchical_namespace.yaml | 48 ++ tests/settings_fake.py | 3 + tests/testcase.py | 14 + 12 files changed, 1539 insertions(+), 1 deletion(-) create mode 100644 tests/blob/test_directory.py create mode 100644 tests/recordings/test_directory.test_create_delete_directory_with_hierarchical_namespace.yaml create mode 100644 tests/recordings/test_directory.test_create_delete_directory_without_hierarchical_namespace.yaml create mode 100644 tests/recordings/test_directory.test_rename_directory_with_hierarchical_namespace.yaml create mode 100644 tests/recordings/test_directory.test_rename_directory_without_hierarchical_namespace.yaml diff --git a/azure-storage-blob/ChangeLog.md b/azure-storage-blob/ChangeLog.md index a4146664..84d3dbb3 100644 --- a/azure-storage-blob/ChangeLog.md +++ b/azure-storage-blob/ChangeLog.md @@ -2,10 +2,16 @@ > See [BreakingChanges](BreakingChanges.md) for a detailed list of API breaks. +## Version XX.XX.XX + +- Added support for directory operations: create, rename, and delete. + ## Version 2.0.1: + - Updated dependency on azure-storage-common. ## Version 2.0.0: + - Support for 2018-11-09 REST version. Please see our REST API documentation and blog for information about the related added features. - Added support for append block from URL(synchronously) for append blobs. - Added support for update page from URL(synchronously) for page blobs. diff --git a/azure-storage-blob/azure/storage/blob/_constants.py b/azure-storage-blob/azure/storage/blob/_constants.py index 062a0356..c847bc2f 100644 --- a/azure-storage-blob/azure/storage/blob/_constants.py +++ b/azure-storage-blob/azure/storage/blob/_constants.py @@ -12,3 +12,6 @@ # internal configurations, should not be changed _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024 + +_BLOB_SERVICE_PUBLIC_CLOUD_HOST = 'blob.core.windows.net' +_DFS_SERVICE_PUBLIC_CLOUD_HOST = 'dfs.core.windows.net' diff --git a/azure-storage-blob/azure/storage/blob/_deserialization.py b/azure-storage-blob/azure/storage/blob/_deserialization.py index 06328552..ff42da20 100644 --- a/azure-storage-blob/azure/storage/blob/_deserialization.py +++ b/azure-storage-blob/azure/storage/blob/_deserialization.py @@ -554,3 +554,8 @@ def _convert_xml_to_user_delegation_key(response): delegation_key.value = key_element.findtext('Value') return delegation_key + + +def _parse_continuation_token(response): + marker = response.headers.get('x-ms-continuation') + return marker if marker is not '' else None diff --git a/azure-storage-blob/azure/storage/blob/_serialization.py b/azure-storage-blob/azure/storage/blob/_serialization.py index 12fa6430..3ddb188c 100644 --- a/azure-storage-blob/azure/storage/blob/_serialization.py +++ b/azure-storage-blob/azure/storage/blob/_serialization.py @@ -151,3 +151,12 @@ def _convert_delegation_key_info_to_xml(start_time, expiry_time): # return xml value return output + + +# TODO to be removed after service update +def _add_file_or_directory_properties_header(properties_dict, request): + if properties_dict: + if not request.headers: + request.headers = {} + request.headers['x-ms-properties'] = \ + ",".join(["{}={}".format(str(name), _encode_base64(value)) for name, value in properties_dict.items()]) diff --git a/azure-storage-blob/azure/storage/blob/baseblobservice.py b/azure-storage-blob/azure/storage/blob/baseblobservice.py index 95fa0109..1700df04 100644 --- a/azure-storage-blob/azure/storage/blob/baseblobservice.py +++ b/azure-storage-blob/azure/storage/blob/baseblobservice.py @@ -6,7 +6,10 @@ import sys from abc import ABCMeta -from azure.common import AzureHttpError +from azure.common import ( + AzureHttpError, + AzureMissingResourceHttpError, +) from azure.storage.common._auth import ( _StorageSASAuthentication, @@ -50,6 +53,7 @@ Services, ListGenerator, _OperationContext, + LocationMode, ) from .sharedaccesssignature import ( BlobSharedAccessSignature, @@ -67,6 +71,7 @@ _parse_base_properties, _parse_account_information, _convert_xml_to_user_delegation_key, + _parse_continuation_token, ) from ._download_chunking import _download_blob_chunks from ._error import ( @@ -77,6 +82,7 @@ _get_path, _validate_and_format_range_headers, _convert_delegation_key_info_to_xml, + _add_file_or_directory_properties_header, ) from .models import ( BlobProperties, @@ -88,11 +94,14 @@ from ._constants import ( X_MS_VERSION, __version__ as package_version, + _BLOB_SERVICE_PUBLIC_CLOUD_HOST, + _DFS_SERVICE_PUBLIC_CLOUD_HOST, ) _CONTAINER_ALREADY_EXISTS_ERROR_CODE = 'ContainerAlreadyExists' _BLOB_NOT_FOUND_ERROR_CODE = 'BlobNotFound' _CONTAINER_NOT_FOUND_ERROR_CODE = 'ContainerNotFound' +_PATH_NOT_FOUND_ERROR_CODE = 'PathNotFound' if sys.version_info >= (3,): from io import BytesIO @@ -3395,3 +3404,262 @@ def undelete_blob(self, container_name, blob_name, timeout=None): } self._perform_request(request) + + # ----------------------------Methods related to directory manipulations---------------------------- # + + def create_directory(self, container_name, directory_path, proposed_lease_id=None, lease_id=None, metadata=None, + posix_permissions=None, posix_umask=None, timeout=None): + """ + Create a directory which can contain other directories or blobs. + + :param str container_name: + Name of existing container. + :param str directory_path: + Path of the directory to be created. Ex: 'dirfoo/dirbar'. + :param str proposed_lease_id: + Proposed lease ID, in a GUID string format. The Blob service + returns 400 (Invalid request) if the proposed lease ID is not + in the correct format. + :param str lease_id: + Required if the directory to be overwritten has an active lease. + :param metadata: + A dict with name_value pairs to associate with the + container as metadata. Example:{'Category':'test'} + :type metadata: dict(str, str) + :param str posix_permissions: + Optional and only valid if Hierarchical Namespace is enabled for the account. + Sets POSIX access permissions for the file owner, the file owning group, and others. + Each class may be granted read, write, or execute permission. + The sticky bit is also supported. + Both symbolic (rwxrw-rw-) and 4-digit octal notation (e.g. 0766) are supported. + :param str posix_umask: + Optional and only valid if Hierarchical Namespace is enabled for the account. + This umask restricts permission settings for file and directory, + and will only be applied when default Acl does not exist in parent directory. + If the umask bit has set, it means that the corresponding permission will be disabled. + Otherwise the corresponding permission will be determined by the permission. + A 4-digit octal notation (e.g. 0022) is supported here. + If no umask was specified, a default umask - 0027 will be used. + :param int timeout: + The timeout parameter is expressed in seconds. + :return: ETag and last modified time of the new directory. + :rtype: :class:`~azure.storage.blob.models.ResourceProperties` + """ + + _validate_not_none('container_name', container_name) + _validate_not_none('directory_path', directory_path) + + request = HTTPRequest() + # TODO remove endpoint swapping after service update + request.host_locations = self._swap_blob_endpoints(self._get_host_locations()) + request.method = 'PUT' + request.path = _get_path(container_name, directory_path) + request.query = { + 'resource': 'directory', + 'timeout': _int_to_str(timeout), + } + request.headers = { + 'x-ms-proposed-lease-id': _to_str(proposed_lease_id), + 'x-ms-lease-id': _to_str(lease_id), + 'x-ms-permissions': _to_str(posix_permissions), + 'x-ms-umask': _to_str(posix_umask), + } + # TODO add test cases for lease and metadata + _add_file_or_directory_properties_header(metadata, request) + return self._perform_request(request, parser=_parse_base_properties) + + def delete_directory(self, container_name, directory_path, fail_not_exist=False, recursive=True, marker=None, + lease_id=None, if_modified_since=None, if_unmodified_since=None, if_match=None, + if_none_match=None, timeout=None): + """ + Delete a directory. This operation's behavior is different depending on whether Hierarchical Namespace + is enabled; if yes, then the delete operation can be atomic and instantaneous; + if not, the operation is performed in batches and a continuation token could be returned. + + :param str container_name: + Name of existing container. + :param str directory_path: + Path of the directory to be deleted. Ex: 'dirfoo/dirbar'. + :param fail_not_exist: + Specify whether to throw an exception when the directory doesn't exist. + :param recursive: + If "true", all paths beneath the directory will be deleted. + If "false" and the directory is non-empty, an error occurs. + :param marker: + Optional. When deleting a directory without the Hierarchical Namespace, + the number of paths that are deleted with each invocation is limited. + If the number of paths to be deleted exceeds this limit, + a continuation token is returned. When a continuation token is returned, + it must be specified in a subsequent invocation of the delete operation to continue deleting the directory. + :param str lease_id: + Required if the directory 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. + :param int timeout: + The timeout parameter is expressed in seconds. + :return: + (True, marker) if the directory is successfully deleted. Otherwise return (False, None). + :rtype: (bool, str) + """ + _validate_not_none('container_name', container_name) + _validate_not_none('directory_path', directory_path) + + request = HTTPRequest() + # TODO remove endpoint swapping after service update + request.host_locations = self._swap_blob_endpoints(self._get_host_locations()) + request.method = 'DELETE' + request.path = _get_path(container_name, directory_path) + request.query = { + 'recursive': _to_str(recursive), + 'continuation': _to_str(marker), + '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), + } + # TODO add test cases for lease + if not fail_not_exist: + try: + return True, self._perform_request(request, expected_errors=[_PATH_NOT_FOUND_ERROR_CODE], + parser=_parse_continuation_token) + except AzureMissingResourceHttpError as ex: + _dont_fail_not_exist(ex) + return False, None + else: + return True, self._perform_request(request, parser=_parse_continuation_token) + + def rename_directory(self, container_name, new_directory_path, source_directory_path, + mode=None, marker=None, lease_id=None, source_lease_id=None, + metadata=None, source_if_modified_since=None, source_if_unmodified_since=None, + source_if_match=None, source_if_none_match=None, timeout=None): + """ + Rename a directory which can contain other directories or blobs. + + :param str container_name: + Name of existing container. + :param str new_directory_path: + New path for source_directory_path. Ex: 'dirfoo/dirsubfoo'. + :param str source_directory_path: + Directory to be renamed. Ex: 'dirfoo/dirbar'. + :param mode: + Optional. Valid only when namespace is enabled. + This parameter determines the behavior of the rename operation. + The value must be "legacy" or "posix", and the default value will be "posix". + A "posix" rename is done atomically; a "legacy" rename is done in batches and could return a marker. + :param marker: + Optional. When renaming a directory, the number of paths that are renamed with each invocation is limited. + If the number of paths to be renamed exceeds this limit, + a continuation token is returned. When a continuation token is returned, + it must be specified in a subsequent invocation of the rename operation to continue renaming the directory. + :param str lease_id: + Optional. A lease ID for the new_directory_path. + The new_directory_path must have an active lease and the lease ID must match. + :param str source_lease_id: + Optional. A lease ID for the source_directory_path. + The source_directory_path must have an active lease and the lease ID must match. + :param metadata: + Optional. A dict with name_value pairs to associate with the directory as metadata. + Example:{'Category':'test'}. + If metadata is specified, it will overwrite the existing metadata; + otherwise, the existing metadata will be preserved. + :type metadata: dict(str, str) + :param datetime source_if_modified_since: + Optional. A date and time value. Specify this header to perform the rename operation + only if the source has been modified since the specified date and time. + :param datetime source_if_unmodified_since: + Optional. A date and time value. Specify this header to perform the rename operation + only if the source has not been modified since the specified date and time. + :param str source_if_match: + Optional. An ETag value. Specify this header to perform the rename operation + only if the source's ETag matches the value specified. + :param str source_if_none_match: + Optional. An ETag value or the special wildcard ("*") value. + Specify this header to perform the rename operation + only if the source's ETag does not match the value specified. + :param int timeout: + The timeout parameter is expressed in seconds. + :return: + A continuation marker if applicable. Otherwise return None. + :rtype: str + """ + _validate_not_none('source_directory_path', source_directory_path) + _validate_not_none('new_directory_path', new_directory_path) + + request = HTTPRequest() + # TODO remove endpoint swapping after service update + request.host_locations = self._swap_blob_endpoints(self._get_host_locations()) + request.method = 'PUT' + request.path = _get_path(container_name, new_directory_path) + request.query = { + 'mode': mode, + 'continuation': _to_str(marker), + 'timeout': _int_to_str(timeout), + } + request.headers = { + 'x-ms-rename-source': _get_path(container_name, source_directory_path), + 'x-ms-lease-id': _to_str(lease_id), + 'x-ms-source-lease-id': _to_str(source_lease_id), + 'x-ms-source-if-modified-since': _datetime_to_utc_string(source_if_modified_since), + 'x-ms-source-if-unmodified-since': _datetime_to_utc_string(source_if_unmodified_since), + 'x-ms-source-if-match': _to_str(source_if_match), + 'x-ms-source-if-none-match': _to_str(source_if_none_match), + } + # TODO add test cases for lease and metadata + _add_file_or_directory_properties_header(metadata, request) + return self._perform_request(request, parser=_parse_continuation_token) + + # ----------------------------Helpers for directory manipulations---------------------------- # + @staticmethod + def _swap_blob_endpoints(host_locations): + # Note that only the primary endpoint is supported for the DFS endpoint + return {LocationMode.PRIMARY: host_locations[LocationMode.PRIMARY].replace(_BLOB_SERVICE_PUBLIC_CLOUD_HOST, + _DFS_SERVICE_PUBLIC_CLOUD_HOST, 1)} + + # TODO remove after service update, needed for testing against HN-enabled account for the moment + def _create_file_system(self, filesystem_name, timeout=None): + _validate_not_none('filesystem_name', filesystem_name) + request = HTTPRequest() + request.host_locations = self._swap_blob_endpoints(self._get_host_locations()) + request.method = 'PUT' + request.path = _get_path(filesystem_name) + request.query = { + 'resource': 'filesystem', + 'timeout': _int_to_str(timeout), + } + self._perform_request(request) + return True + + # TODO remove after service update, needed for testing against HN-enabled account for the moment + def _delete_file_system(self, filesystem_name, timeout=None): + _validate_not_none('filesystem_name', filesystem_name) + request = HTTPRequest() + request.host_locations = self._swap_blob_endpoints(self._get_host_locations()) + request.method = 'DELETE' + request.path = _get_path(filesystem_name) + request.query = { + 'resource': 'filesystem', + 'timeout': _int_to_str(timeout), + } + self._perform_request(request) + return True diff --git a/tests/blob/test_directory.py b/tests/blob/test_directory.py new file mode 100644 index 00000000..6992c6e2 --- /dev/null +++ b/tests/blob/test_directory.py @@ -0,0 +1,393 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import datetime + +from azure.common import ( + AzureHttpError, + AzureMissingResourceHttpError, + AzureConflictHttpError, +) + +from azure.storage.blob import ( + BlockBlobService, +) +from azure.storage.blob.baseblobservice import ( + BaseBlobService, +) +from azure.storage.common import ( + LocationMode, + ExponentialRetry, +) +from tests.testcase import ( + StorageTestCase, + TestMode, + record, +) + + +class StorageDirectoryTest(StorageTestCase): + def setUp(self): + super(StorageDirectoryTest, self).setUp() + self.bs = self._create_storage_service(BlockBlobService, self.settings) + self.bs_namespace = self._create_storage_service_with_hierarchical_namespace(BlockBlobService, self.settings) + + # shorten retries for faster failures + self.bs.retry = ExponentialRetry(initial_backoff=1, increment_base=2, max_attempts=3).retry + self.bs_namespace.retry = ExponentialRetry(initial_backoff=1, increment_base=2, max_attempts=3).retry + + self.container_name = self.get_resource_name('utcontainer') + if not self.is_playback(): + self.bs.create_container(self.container_name) + self.bs_namespace._create_file_system(self.container_name) + + def tearDown(self): + if not self.is_playback(): + self.bs.delete_container(self.container_name) + self.bs_namespace._delete_file_system(self.container_name) + return super(StorageDirectoryTest, self).tearDown() + + def _get_directory_reference(self, suffix=""): + return self.get_resource_name("directorytest" + suffix) + + def _get_blob_reference(self, directory_path): + return "{}/{}".format(directory_path, self.get_resource_name("blob")) + + def _create_sub_dirs(self, blob_service, directory_name, num_of_sub_dir): + import concurrent.futures + import itertools + # Use a thread pool because it is too slow otherwise + with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: + def create_sub_dir(): + blob_service.create_directory(self.container_name, + "{}/{}".format(directory_name, self._get_directory_reference())) + + futures = {executor.submit(create_sub_dir) for _ in itertools.repeat(None, num_of_sub_dir)} + concurrent.futures.wait(futures) + + def test_host_swapping(self): + # Arrange + example_blob_hosts = {LocationMode.PRIMARY: "account.blob.core.windows.net", + LocationMode.SECONDARY: "account-secondary.blob.core.windows.net"} + + # Act + swapped_hosts = BaseBlobService._swap_blob_endpoints(example_blob_hosts) + + # Assert + # DFS only supports the primary endpoint + self.assertEqual(len(swapped_hosts), 1) + self.assertEqual(swapped_hosts[LocationMode.PRIMARY], "account.dfs.core.windows.net") + + @record + def test_create_delete_directory_without_hierarchical_namespace(self): + self.create_delete_directory_simple_test_implementation(self.bs) + self.create_directory_with_permission_implementation(self.bs, hierarchical_namespace_enabled=False) + self.delete_directory_marker_test_implementation(self.bs) + self.delete_directory_recursive_test_implementation(self.bs) + self.delete_directory_access_conditions_test_implementation(self.bs) + + @record + def test_create_delete_directory_with_hierarchical_namespace(self): + self.create_delete_directory_simple_test_implementation(self.bs_namespace) + self.create_directory_with_permission_implementation(self.bs_namespace, hierarchical_namespace_enabled=True) + self.delete_directory_recursive_test_implementation(self.bs_namespace) + + # TODO encountering error 500 for if_match and if_none_match condition, uncomment after service fix + # self.delete_directory_access_conditions_test_implementation(self.bs_namespace) + + def create_delete_directory_simple_test_implementation(self, blob_service): + # Arrange + directory_name = self._get_directory_reference() + metadata = {"foo": "bar", "mama": "mia"} + + # Act + props = blob_service.create_directory(self.container_name, directory_name, metadata=metadata) + + # Assert + self.assertIsNotNone(props) + self.assertIsNotNone(props.etag) + self.assertIsNotNone(props.last_modified) + + # Act + # Creating the same directory again + self.sleep(1) + props2 = blob_service.create_directory(self.container_name, directory_name) + + # Assert + self.assertIsNotNone(props2) + self.assertNotEqual(props.etag, props2.etag) + self.assertNotEqual(props.last_modified, props2.last_modified) + + # Act + deleted, marker = blob_service.delete_directory(self.container_name, directory_name) + + # Assert + self.assertTrue(deleted) + + # Act + # Delete an already non-existing directory + deleted, marker = blob_service.delete_directory(self.container_name, directory_name) + + # Assert + self.assertFalse(deleted) + + # Act + # Delete an already non-existing directory, but with let it throw an error + with self.assertRaises(AzureMissingResourceHttpError): + blob_service.delete_directory(self.container_name, directory_name, fail_not_exist=True) + + def create_directory_with_permission_implementation(self, blob_service, hierarchical_namespace_enabled): + # Arrange + directory_name = self._get_directory_reference() + + if not hierarchical_namespace_enabled: + # Act + # Create with permission and umask is expected to fail due to the lack of namespace service + with self.assertRaises(AzureHttpError): + blob_service.create_directory(self.container_name, directory_name, + posix_permissions='rwxrw-rw-', posix_umask='0022') + else: + # Act + # Create with permission and umask + props = blob_service.create_directory(self.container_name, directory_name, + posix_permissions='rwxrw-rw-', posix_umask='0022') + + # Assert + self.assertIsNotNone(props) + self.assertIsNotNone(props.etag) + self.assertIsNotNone(props.last_modified) + + # Cleanup + blob_service.delete_directory(self.container_name, directory_name) + + def delete_directory_marker_test_implementation(self, blob_service): + # this test is too costly(too many requests to the service) and should only run in live mode + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + directory_name = self._get_directory_reference() + blob_service.create_directory(self.container_name, directory_name) + + # Create enough sub-directories to trigger the service to return a marker + self._create_sub_dirs(blob_service, directory_name, num_of_sub_dir=1000) + + # Act + deleted, marker = blob_service.delete_directory(self.container_name, directory_name) + + # Assert + self.assertTrue(deleted) + self.assertIsNotNone(marker) + + # Act + # Continue the delete + count = 0 + while marker is not None: + deleted, new_marker = blob_service.delete_directory(self.container_name, directory_name, marker=marker) + + # Assert + self.assertTrue(deleted) + self.assertNotEqual(marker, new_marker) + marker = new_marker + count += 1 + + self.logger.info("Took {} calls to finish deleting.".format(count)) + + def delete_directory_recursive_test_implementation(self, blob_service): + # Arrange + directory_name = self._get_directory_reference() + sub_directory_name = self._get_directory_reference() + blob_service.create_directory(self.container_name, directory_name) + blob_service.create_directory(self.container_name, "{}/{}".format(directory_name, sub_directory_name)) + + # Act + with self.assertRaises(AzureConflictHttpError): + blob_service.delete_directory(self.container_name, directory_name, recursive=False) + + deleted, marker = blob_service.delete_directory(self.container_name, directory_name, recursive=True) + self.assertTrue(deleted) + self.assertIsNone(marker) + + def delete_directory_access_conditions_test_implementation(self, blob_service): + # Arrange + directory_name = self._get_directory_reference() + props = blob_service.create_directory(self.container_name, directory_name) + + # if_match fails + with self.assertRaises(AzureHttpError): + blob_service.delete_directory(self.container_name, directory_name, if_match='0x111111111111111') + + # if_match succeeds + deleted, marker = blob_service.delete_directory(self.container_name, directory_name, + if_match=props.etag) + self.assertTrue(deleted) + self.assertIsNone(marker) + + # Arrange + props = blob_service.create_directory(self.container_name, directory_name) + + # if_none_match fails + with self.assertRaises(AzureHttpError): + blob_service.delete_directory(self.container_name, directory_name, if_none_match=props.etag) + + # if_none_match succeeds + deleted, marker = blob_service.delete_directory(self.container_name, directory_name, + if_none_match='0x111111111111111') + self.assertTrue(deleted) + self.assertIsNone(marker) + + # Arrange + props = blob_service.create_directory(self.container_name, directory_name) + + # if_modified_since fails + with self.assertRaises(AzureHttpError): + blob_service.delete_directory(self.container_name, directory_name, if_modified_since=props.last_modified) + + # if_modified_since succeeds + deleted, marker = blob_service.delete_directory(self.container_name, directory_name, + if_modified_since=props.last_modified - datetime.timedelta( + minutes=1)) + self.assertTrue(deleted) + self.assertIsNone(marker) + + # Arrange + props = blob_service.create_directory(self.container_name, directory_name) + + # if_unmodified_since fails + with self.assertRaises(AzureHttpError): + blob_service.delete_directory(self.container_name, directory_name, + if_unmodified_since=props.last_modified - datetime.timedelta( + minutes=1)) + + # if_unmodified_since succeeds + deleted, marker = blob_service.delete_directory(self.container_name, directory_name, + if_unmodified_since=props.last_modified) + self.assertTrue(deleted) + self.assertIsNone(marker) + + @record + def test_rename_directory_without_hierarchical_namespace(self): + self.rename_directory_simple_test_implementation(self.bs) + self.rename_directory_marker_test_implementation(self.bs) + + # TODO error 500s get thrown for precondition fail, uncomment after service fix + # self.rename_directory_access_conditions_test_implementation(self.bs) + + @record + def test_rename_directory_with_hierarchical_namespace(self): + self.rename_directory_simple_test_implementation(self.bs_namespace) + + # TODO access conditions get ignored, uncomment after service fix + # self.rename_directory_access_conditions_test_implementation(self.bs_namespace) + + def rename_directory_simple_test_implementation(self, blob_service): + # Arrange + directory_name = self._get_directory_reference() + + # Act + props = blob_service.create_directory(self.container_name, directory_name) + + # Assert + self.assertIsNotNone(props) + self.assertIsNotNone(props.etag) + self.assertIsNotNone(props.last_modified) + + # Arrange + new_directory_name = self._get_directory_reference("new") + + # Act + marker = blob_service.rename_directory(self.container_name, new_directory_name, directory_name) + + # Assert + self.assertIsNone(marker) + + def rename_directory_marker_test_implementation(self, blob_service): + # this test is too costly(too many requests to the service) and should only run in live mode + if TestMode.need_recording_file(self.test_mode): + return + + # Arrange + directory_name = self._get_directory_reference() + new_directory_name = self._get_directory_reference("new") + blob_service.create_directory(self.container_name, directory_name) + + # Create enough sub-directories to trigger the service to return a marker + self._create_sub_dirs(blob_service, directory_name, num_of_sub_dir=1000) + + # Act + marker = blob_service.rename_directory(self.container_name, new_directory_name, directory_name) + + # Assert + self.assertIsNotNone(marker) + + # Act + # Continue the rename + count = 0 + while marker is not None: + new_marker = blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + marker=marker) + self.assertNotEqual(marker, new_marker) + marker = new_marker + count += 1 + + self.logger.info("Took {} calls to finish renaming.".format(count)) + + def rename_directory_access_conditions_test_implementation(self, blob_service): + # Arrange + directory_name = self._get_directory_reference() + new_directory_name = self._get_directory_reference("new") + props = blob_service.create_directory(self.container_name, directory_name) + + # if_match fails + with self.assertRaises(AzureHttpError): + blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_match='0x111111111111111') + + # if_match succeeds + marker = blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_match=props.etag) + self.assertIsNone(marker) + + # Arrange + props = blob_service.create_directory(self.container_name, directory_name) + + # if_none_match fails + with self.assertRaises(AzureHttpError): + blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_none_match=props.etag) + + # if_none_match succeeds + marker = blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_none_match='0x111111111111111') + self.assertIsNone(marker) + + # Arrange + props = blob_service.create_directory(self.container_name, directory_name) + + # if_modified_since fails + with self.assertRaises(AzureHttpError): + blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_modified_since=props.last_modified) + + # if_modified_since succeeds + marker = blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_modified_since=props.last_modified - datetime.timedelta( + minutes=1)) + self.assertIsNone(marker) + + # Arrange + props = blob_service.create_directory(self.container_name, directory_name) + + # if_unmodified_since fails + with self.assertRaises(AzureHttpError): + blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_unmodified_since=props.last_modified - datetime.timedelta( + minutes=1)) + + # if_unmodified_since succeeds + marker = blob_service.rename_directory(self.container_name, new_directory_name, directory_name, + source_if_unmodified_since=props.last_modified) + self.assertIsNone(marker) diff --git a/tests/recordings/test_directory.test_create_delete_directory_with_hierarchical_namespace.yaml b/tests/recordings/test_directory.test_create_delete_directory_with_hierarchical_namespace.yaml new file mode 100644 index 00000000..b3aa214c --- /dev/null +++ b/tests/recordings/test_directory.test_create_delete_directory_with_hierarchical_namespace.yaml @@ -0,0 +1,245 @@ +interactions: +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8d17ae72-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:10 GMT'] + x-ms-properties: ['foo=YmFy,mama=bWlh'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:11 GMT'] + ETag: ['"0x8D6297571A4A833"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:11 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff34-001f-00a9-0c5e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e0badec-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + ETag: ['"0x8D62975724499BF"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff35-001f-00a9-0d5e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e123e46-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff36-001f-00a9-0e5e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e17878e-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?recursive=True + response: + body: {string: '{"error":{"code":"PathNotFound","message":"The specified path + does not exist.\nRequestId:3e5fff37-001f-00a9-0f5e-5b38ea000000\nTime:2018-10-03T21:16:12.5240091Z"}}'} + headers: + Content-Length: ['163'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [PathNotFound] + x-ms-request-id: [3e5fff37-001f-00a9-0f5e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 404, message: The specified path does not exist.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e1ccd7a-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?recursive=True + response: + body: {string: '{"error":{"code":"PathNotFound","message":"The specified path + does not exist.\nRequestId:3e5fff38-001f-00a9-105e-5b38ea000000\nTime:2018-10-03T21:16:12.5570393Z"}}'} + headers: + Content-Length: ['163'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [PathNotFound] + x-ms-request-id: [3e5fff38-001f-00a9-105e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 404, message: The specified path does not exist.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e21d572-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-permissions: [rwxrw-rw-] + x-ms-umask: ['0022'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + ETag: ['"0x8D6297572596435"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff39-001f-00a9-115e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e270466-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff3a-001f-00a9-125e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e2c548e-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + ETag: ['"0x8D629757263E4A5"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff3b-001f-00a9-135e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e316122-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21/directorytest1c491d21?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + ETag: ['"0x8D6297572694BF7"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff3c-001f-00a9-145e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e36d1ca-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?recursive=False + response: + body: {string: '{"error":{"code":"DirectoryNotEmpty","message":"The recursive + query parameter value must be true to delete a non-empty directory.\nRequestId:3e5fff3d-001f-00a9-155e-5b38ea000000\nTime:2018-10-03T21:16:12.7272015Z"}}'} + headers: + Content-Length: ['215'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [DirectoryNotEmpty] + x-ms-request-id: [3e5fff3d-001f-00a9-155e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 409, message: The recursive query parameter value must be true + to delete a non-empty directory.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [8e3bee80-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer1c491d21/directorytest1c491d21?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:12 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3e5fff3e-001f-00a9-165e-5b38ea000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +version: 1 diff --git a/tests/recordings/test_directory.test_create_delete_directory_without_hierarchical_namespace.yaml b/tests/recordings/test_directory.test_create_delete_directory_without_hierarchical_namespace.yaml new file mode 100644 index 00000000..8a583703 --- /dev/null +++ b/tests/recordings/test_directory.test_create_delete_directory_without_hierarchical_namespace.yaml @@ -0,0 +1,499 @@ +interactions: +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [953e54a2-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:24 GMT'] + x-ms-properties: ['foo=YmFy,mama=bWlh'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:23 GMT'] + ETag: ['"0x8D6297579AE2EAC"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:24 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cb7-201f-0001-2f5e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [961ac356-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:25 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + ETag: ['"0x8D629757A58510E"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cb8-201f-0001-305e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [962c08aa-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cb9-201f-0001-315e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [964aecfc-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: '{"error":{"code":"PathNotFound","message":"The specified path + does not exist.\nRequestId:695e3cba-201f-0001-325e-5bcde7000000\nTime:2018-10-03T21:16:26.3077942Z"}}'} + headers: + Content-Length: ['163'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [PathNotFound] + x-ms-request-id: [695e3cba-201f-0001-325e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 404, message: The specified path does not exist.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [965ad63a-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: '{"error":{"code":"PathNotFound","message":"The specified path + does not exist.\nRequestId:695e3cbb-201f-0001-335e-5bcde7000000\nTime:2018-10-03T21:16:26.4138003Z"}}'} + headers: + Content-Length: ['163'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [PathNotFound] + x-ms-request-id: [695e3cbb-201f-0001-335e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 404, message: The specified path does not exist.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96697be0-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-permissions: [rwxrw-rw-] + x-ms-umask: ['0022'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: '{"error":{"code":"UnsupportedHeader","message":"One of the headers + specified in the request is not supported.\nRequestId:695e3cbc-201f-0001-345e-5bcde7000000\nTime:2018-10-03T21:16:26.5047893Z"}}'} + headers: + Content-Length: ['195'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [UnsupportedHeader] + x-ms-request-id: [695e3cbc-201f-0001-345e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 400, message: One of the headers specified in the request is not + supported.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96775490-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + ETag: ['"0x8D629757AB39149"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cbd-201f-0001-355e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [968777e4-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + ETag: ['"0x8D629757AC40B29"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cbe-201f-0001-365e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [969728b0-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=False + response: + body: {string: '{"error":{"code":"DirectoryNotEmpty","message":"The recursive + query parameter value must be true to delete a non-empty directory.\nRequestId:695e3cbf-201f-0001-375e-5bcde7000000\nTime:2018-10-03T21:16:26.8158344Z"}}'} + headers: + Content-Length: ['215'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [DirectoryNotEmpty] + x-ms-request-id: [695e3cbf-201f-0001-375e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 409, message: The recursive query parameter value must be true + to delete a non-empty directory.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96a6d558-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cc0-201f-0001-385e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96b62332-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:26 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + ETag: ['"0x8D629757AF29517"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cc1-201f-0001-395e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-Match: ['0x111111111111111'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96c5ac6c-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: '{"error":{"code":"ConditionNotMet","message":"The condition specified + using HTTP conditional header(s) is not met.\nRequestId:695e3cc2-201f-0001-3a5e-5bcde7000000\nTime:2018-10-03T21:16:27.1268204Z"}}'} + headers: + Content-Length: ['200'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [ConditionNotMet] + x-ms-request-id: [695e3cc2-201f-0001-3a5e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 412, message: The condition specified using HTTP conditional header(s) + is not met.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-Match: ['"0x8D629757AF29517"'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96d6955e-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cc3-201f-0001-3b5e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96e6129a-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + ETag: ['"0x8D629757B227FC4"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cc4-201f-0001-3c5e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-None-Match: ['"0x8D629757B227FC4"'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [96f59f6c-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: '{"error":{"code":"ConditionNotMet","message":"The condition specified + using HTTP conditional header(s) is not met.\nRequestId:695e3cc5-201f-0001-3d5e-5bcde7000000\nTime:2018-10-03T21:16:27.4248079Z"}}'} + headers: + Content-Length: ['200'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [ConditionNotMet] + x-ms-request-id: [695e3cc5-201f-0001-3d5e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 412, message: The condition specified using HTTP conditional header(s) + is not met.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-None-Match: ['0x111111111111111'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [97039d2e-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cc6-201f-0001-3e5e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [9712af30-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + ETag: ['"0x8D629757B4EE606"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cc7-201f-0001-3f5e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-Modified-Since: ['Wed, 03 Oct 2018 21:16:27 GMT'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [9721ff76-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: '{"error":{"code":"ConditionNotMet","message":"The condition specified + using HTTP conditional header(s) is not met.\nRequestId:695e3cc8-201f-0001-405e-5bcde7000000\nTime:2018-10-03T21:16:27.7207932Z"}}'} + headers: + Content-Length: ['200'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [ConditionNotMet] + x-ms-request-id: [695e3cc8-201f-0001-405e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 412, message: The condition specified using HTTP conditional header(s) + is not met.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-Modified-Since: ['Wed, 03 Oct 2018 21:15:27 GMT'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [9730eaa4-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cc9-201f-0001-415e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [973ffe9a-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + ETag: ['"0x8D629757B7C3750"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3cca-201f-0001-425e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-Unmodified-Since: ['Wed, 03 Oct 2018 21:15:27 GMT'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [974f369e-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: '{"error":{"code":"ConditionNotMet","message":"The condition specified + using HTTP conditional header(s) is not met.\nRequestId:695e3ccb-201f-0001-435e-5bcde7000000\nTime:2018-10-03T21:16:28.0117962Z"}}'} + headers: + Content-Length: ['200'] + Content-Type: [application/json;charset=utf-8] + Date: ['Wed, 03 Oct 2018 21:16:27 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-error-code: [ConditionNotMet] + x-ms-request-id: [695e3ccb-201f-0001-435e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 412, message: The condition specified using HTTP conditional header(s) + is not met.} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + If-Unmodified-Since: ['Wed, 03 Oct 2018 21:16:27 GMT'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [975d2fb0-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:28 GMT'] + x-ms-version: ['2018-06-17'] + method: DELETE + uri: https://storagename.dfs.core.windows.net/utcontainer79611e79/directorytest79611e79?recursive=True + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:28 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [695e3ccc-201f-0001-445e-5bcde7000000] + x-ms-version: ['2018-06-17'] + status: {code: 200, message: OK} +version: 1 diff --git a/tests/recordings/test_directory.test_rename_directory_with_hierarchical_namespace.yaml b/tests/recordings/test_directory.test_rename_directory_with_hierarchical_namespace.yaml new file mode 100644 index 00000000..309c41c7 --- /dev/null +++ b/tests/recordings/test_directory.test_rename_directory_with_hierarchical_namespace.yaml @@ -0,0 +1,45 @@ +interactions: +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [98df3342-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:30 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer5bc51a53/directorytest5bc51a53?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:30 GMT'] + ETag: ['"0x8D629757D814599"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:31 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [a041ac47-901f-0006-5d5e-5b1a7a000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [995bcb6e-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:31 GMT'] + x-ms-rename-source: [/utcontainer5bc51a53/directorytest5bc51a53] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://hierarchicalnamespacestoragename.dfs.core.windows.net/utcontainer5bc51a53/directorytestnew5bc51a53 + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:30 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [a041ac48-901f-0006-5e5e-5b1a7a000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +version: 1 diff --git a/tests/recordings/test_directory.test_rename_directory_without_hierarchical_namespace.yaml b/tests/recordings/test_directory.test_rename_directory_without_hierarchical_namespace.yaml new file mode 100644 index 00000000..dd00e27b --- /dev/null +++ b/tests/recordings/test_directory.test_rename_directory_without_hierarchical_namespace.yaml @@ -0,0 +1,48 @@ +interactions: +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [9a2dee50-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:32 GMT'] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainerb0731bab/directorytestb0731bab?resource=directory + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:32 GMT'] + ETag: ['"0x8D629757E92ED44"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:33 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-request-id: [3eb87f4f-301f-0022-685e-5b5724000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +- request: + body: null + headers: + Connection: [keep-alive] + Content-Length: ['0'] + User-Agent: [Azure-Storage/1.3.0-1.3.1 (Python CPython 3.7.0; Darwin 17.7.0)] + x-ms-client-request-id: [9a65caf0-c751-11e8-a389-f218981b70f8] + x-ms-date: ['Wed, 03 Oct 2018 21:16:33 GMT'] + x-ms-rename-source: [/utcontainerb0731bab/directorytestb0731bab] + x-ms-version: ['2018-06-17'] + method: PUT + uri: https://storagename.dfs.core.windows.net/utcontainerb0731bab/directorytestnewb0731bab + response: + body: {string: ''} + headers: + Content-Length: ['0'] + Date: ['Wed, 03 Oct 2018 21:16:32 GMT'] + ETag: ['"0x8D629757EA31AA2"'] + Last-Modified: ['Wed, 03 Oct 2018 21:16:33 GMT'] + Server: [Windows-Azure-HDFS/1.0 Microsoft-HTTPAPI/2.0] + x-ms-continuation: [''] + x-ms-request-id: [3eb87f50-301f-0022-695e-5b5724000000] + x-ms-version: ['2018-06-17'] + status: {code: 201, message: Created} +version: 1 diff --git a/tests/settings_fake.py b/tests/settings_fake.py index 0a17a191..a19ff96c 100644 --- a/tests/settings_fake.py +++ b/tests/settings_fake.py @@ -9,6 +9,8 @@ STORAGE_ACCOUNT_NAME = "storagename" STORAGE_ACCOUNT_KEY = "NzhL3hKZbJBuJ2484dPTR+xF30kYaWSSCbs2BzLgVVI1woqeST/1IgqaLm6QAOTxtGvxctSNbIR/1hW8yH+bJg==" +HIERARCHICAL_NAMESPACE_ACCOUNT_NAME = "hierarchicalnamespacestoragename" +HIERARCHICAL_NAMESPACE_ACCOUNT_KEY = "NzhL3hKZbJBuJ2484dPTR+xF30kYaWSSCbs2BzLgVVI1woqeST/1IgqaLm6QAOTxtGvxctSNbIR/1hW8yH+bJg==" BLOB_STORAGE_ACCOUNT_NAME = "blobstoragename" BLOB_STORAGE_ACCOUNT_KEY = "NzhL3hKZbJBuJ2484dPTR+xF30kYaWSSCbs2BzLgVVI1woqeST/1IgqaLm6QAOTxtGvxctSNbIR/1hW8yH+bJg==" REMOTE_STORAGE_ACCOUNT_NAME = "remotestoragename" @@ -28,6 +30,7 @@ CONNECTION_STRING = "" BLOB_CONNECTION_STRING = "" PREMIUM_CONNECTION_STRING = "" +HIERARCHICAL_NAMESPACE_CONNECTION_STRING = "" # Use 'https' or 'http' protocol for sending requests, 'https' highly recommended PROTOCOL = "https" diff --git a/tests/testcase.py b/tests/testcase.py index 91e9098c..0565cb32 100644 --- a/tests/testcase.py +++ b/tests/testcase.py @@ -171,6 +171,18 @@ def _create_storage_service(self, service_class, settings): self._set_test_proxy(service, settings) return service + def _create_storage_service_with_hierarchical_namespace(self, service_class, settings): + if settings.HIERARCHICAL_NAMESPACE_CONNECTION_STRING: + service = service_class(connection_string=settings.HIERARCHICAL_NAMESPACE_CONNECTION_STRING) + else: + service = service_class( + settings.HIERARCHICAL_NAMESPACE_ACCOUNT_NAME, + settings.HIERARCHICAL_NAMESPACE_ACCOUNT_KEY, + protocol=settings.PROTOCOL, + ) + self._set_test_proxy(service, settings) + return service + # for blob storage account def _create_storage_service_for_blob_storage_account(self, service_class, settings): if hasattr(settings, 'BLOB_CONNECTION_STRING') and settings.BLOB_CONNECTION_STRING != "": @@ -301,6 +313,8 @@ def _scrub(self, val): self.settings.STORAGE_ACCOUNT_KEY: self.fake_settings.STORAGE_ACCOUNT_KEY, self.settings.OAUTH_STORAGE_ACCOUNT_NAME: self.fake_settings.OAUTH_STORAGE_ACCOUNT_NAME, self.settings.OAUTH_STORAGE_ACCOUNT_KEY: self.fake_settings.OAUTH_STORAGE_ACCOUNT_KEY, + self.settings.HIERARCHICAL_NAMESPACE_ACCOUNT_NAME: self.fake_settings.HIERARCHICAL_NAMESPACE_ACCOUNT_NAME, + self.settings.HIERARCHICAL_NAMESPACE_ACCOUNT_KEY: self.fake_settings.HIERARCHICAL_NAMESPACE_ACCOUNT_KEY, self.settings.BLOB_STORAGE_ACCOUNT_NAME: self.fake_settings.BLOB_STORAGE_ACCOUNT_NAME, self.settings.BLOB_STORAGE_ACCOUNT_KEY: self.fake_settings.BLOB_STORAGE_ACCOUNT_KEY, self.settings.REMOTE_STORAGE_ACCOUNT_KEY: self.fake_settings.REMOTE_STORAGE_ACCOUNT_KEY,