From 7615a9ee9b5a8f7dd56d545a9f5dd8d15903f585 Mon Sep 17 00:00:00 2001 From: Kan Tang Date: Mon, 11 Jan 2021 13:10:17 +0800 Subject: [PATCH] Added support for CreateIfNotExists and DeleteIfExists (#1282) * Added support for CreateIfNotExists and DeleteIfExists * Added DataLake * Refined test cases. * Resolve review comments. * Resolve more review comments. --- .../CMakeLists.txt | 1 + .../files/datalake/datalake_constants.hpp | 15 ++++ .../datalake/datalake_directory_client.hpp | 26 +++++++ .../files/datalake/datalake_file_client.hpp | 22 ++++++ .../datalake/datalake_file_system_client.hpp | 20 +++++ .../files/datalake/datalake_path_client.hpp | 23 ++++++ .../files/datalake/datalake_responses.hpp | 25 +++++- .../src/datalake_directory_client.cpp | 30 +++++--- .../src/datalake_file_client.cpp | 31 +++++--- .../src/datalake_file_system_client.cpp | 48 +++++++++++- .../src/datalake_path_client.cpp | 56 +++++++++++++- .../test/datalake_directory_client_test.cpp | 77 ++++++++++++------- .../test/datalake_file_client_test.cpp | 74 ++++++++++++------ .../test/datalake_file_system_client_test.cpp | 36 +++++++++ .../azure-storage-files-shares/CHANGELOG.md | 5 ++ .../storage/files/shares/share_client.hpp | 18 +++++ .../storage/files/shares/share_constants.hpp | 7 ++ .../files/shares/share_directory_client.hpp | 20 +++++ .../files/shares/share_file_client.hpp | 9 +++ .../storage/files/shares/share_responses.hpp | 59 ++++++++++++-- .../src/share_client.cpp | 54 ++++++++++++- .../src/share_directory_client.cpp | 68 +++++++++++++++- .../src/share_file_client.cpp | 58 ++++++++++++-- .../test/share_client_test.cpp | 36 +++++++++ .../test/share_directory_client_test.cpp | 51 ++++++++++++ .../test/share_file_client_test.cpp | 31 ++++++++ 26 files changed, 797 insertions(+), 103 deletions(-) create mode 100644 sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_constants.hpp diff --git a/sdk/storage/azure-storage-files-datalake/CMakeLists.txt b/sdk/storage/azure-storage-files-datalake/CMakeLists.txt index 0a8530629..8b0622a2b 100644 --- a/sdk/storage/azure-storage-files-datalake/CMakeLists.txt +++ b/sdk/storage/azure-storage-files-datalake/CMakeLists.txt @@ -29,6 +29,7 @@ endif() set( AZURE_STORAGE_FILES_DATALAKE_HEADER inc/azure/storage/files/datalake/protocol/datalake_rest_client.hpp + inc/azure/storage/files/datalake/datalake_constants.hpp inc/azure/storage/files/datalake/datalake_directory_client.hpp inc/azure/storage/files/datalake/datalake_file_client.hpp inc/azure/storage/files/datalake/datalake_file_system_client.hpp diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_constants.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_constants.hpp new file mode 100644 index 000000000..11077cdfe --- /dev/null +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_constants.hpp @@ -0,0 +1,15 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +namespace Azure { namespace Storage { namespace Files { namespace DataLake { namespace Details { + // Error codes: + constexpr static const char* ContainerAlreadyExists = "ContainerAlreadyExists"; + constexpr static const char* ContainerNotFound = "ContainerNotFound"; + constexpr static const char* DataLakeFilesystemNotFound = "FilesystemNotFound"; + constexpr static const char* DataLakePathNotFound = "PathNotFound"; + constexpr static const char* DataLakePathAlreadyExists = "PathAlreadyExists"; + +}}}}} // namespace Azure::Storage::Files::DataLake::Details diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_directory_client.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_directory_client.hpp index f26356901..0afb0ff72 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_directory_client.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_directory_client.hpp @@ -110,6 +110,19 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { return PathClient::Create(Models::PathResourceType::Directory, options); } + /** + * @brief Create a directory. If it already exists, nothing will happen. + * @param options Optional parameters to create the directory the path points to. + * @return Azure::Core::Response containing the information of + * the created directory + * @remark This request is sent to dfs endpoint. + */ + Azure::Core::Response CreateIfNotExists( + const CreateDirectoryOptions& options = CreateDirectoryOptions()) const + { + return PathClient::CreateIfNotExists(Models::PathResourceType::Directory, options); + } + /** * @brief Renames a directory. By default, the destination is overwritten and * if the destination already exists and has a lease the lease is broken. @@ -139,6 +152,19 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { bool recursive, const DeleteDirectoryOptions& options = DeleteDirectoryOptions()) const; + /** + * @brief Deletes the directory if it already exists. + * @param recursive If "true", all paths beneath the directory will be deleted. If "false" and + * the directory is non-empty, an error occurs. + * @param options Optional parameters to delete the directory the path points to. + * @return Azure::Core::Response containing the information + * returned when deleting the directory. + * @remark This request is sent to dfs endpoint. + */ + Azure::Core::Response DeleteIfExists( + bool recursive, + const DeleteDirectoryOptions& options = DeleteDirectoryOptions()) const; + /** * @brief Sets POSIX access control rights on files and directories under given directory * recursively. diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_client.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_client.hpp index fe08ae3e2..2fbf381fe 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_client.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_client.hpp @@ -137,6 +137,19 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { return PathClient::Create(Models::PathResourceType::File, options); } + /** + * @brief Create a file. If it already exists, it will remain unchanged. + * @param options Optional parameters to create the resource the path points to. + * @return Azure::Core::Response containing the information returned + * when creating the file. + * @remark This request is sent to dfs endpoint. + */ + Azure::Core::Response CreateIfNotExists( + const CreateFileOptions& options = CreateFileOptions()) const + { + return PathClient::CreateIfNotExists(Models::PathResourceType::File, options); + } + /** * @brief Renames a file. By default, the destination is overwritten and * if the destination already exists and has a lease the lease is broken. @@ -162,6 +175,15 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Response Delete( const FileDeleteOptions& options = FileDeleteOptions()) const; + /** + * @brief Deletes the file if it already exists. + * @param options Optional parameters to delete the file the path points to. + * @return Azure::Core::Response + * @remark This request is sent to dfs endpoint. + */ + Azure::Core::Response DeleteIfExists( + const FileDeleteOptions& options = FileDeleteOptions()) const; + /** * @brief Read the contents of a file. For read operations, range requests are supported. * @param options Optional parameters to read the content from the resource the path points to. diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_system_client.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_system_client.hpp index 63c3cf702..9d477846f 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_system_client.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_file_system_client.hpp @@ -115,6 +115,16 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Response Create( const CreateFileSystemOptions& options = CreateFileSystemOptions()) const; + /** + * @brief Creates the file system if it does not exists. + * @param options Optional parameters to create this file system. + * @return Azure::Core::Response containing the information of + * create a file system. Only valid when successfully created the file system. + * @remark This request is sent to blob endpoint. + */ + Azure::Core::Response CreateIfNotExists( + const CreateFileSystemOptions& options = CreateFileSystemOptions()) const; + /** * @brief Deletes the file system. * @param options Optional parameters to delete this file system. @@ -125,6 +135,16 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Response Delete( const DeleteFileSystemOptions& options = DeleteFileSystemOptions()) const; + /** + * @brief Deletes the file system if it exists. + * @param options Optional parameters to delete this file system. + * @return Azure::Core::Response containing the information + * returned when deleting file systems. Only valid when successfully deleted the file system. + * @remark This request is sent to blob endpoint. + */ + Azure::Core::Response DeleteIfExists( + const DeleteFileSystemOptions& options = DeleteFileSystemOptions()) const; + /** * @brief Sets the metadata of file system. * @param metadata User-defined metadata to be stored with the filesystem. Note that the string diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp index 93694dbe4..edb0415fd 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp @@ -96,6 +96,19 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Models::PathResourceType type, const CreatePathOptions& options = CreatePathOptions()) const; + /** + * @brief Creates a file or directory. By default, the destination is not changed if it already + * exists. + * @param options Optional parameters to create the resource the path points to. + * @return Azure::Core::Response containing the information returned + * when creating a path, the information will only be valid when the create operation is + * successful. + * @remark This request is sent to dfs endpoint. + */ + Azure::Core::Response CreateIfNotExists( + Models::PathResourceType type, + const CreatePathOptions& options = CreatePathOptions()) const; + /** * @brief Deletes the resource the path points to. * @param options Optional parameters to delete the reource the path points to. @@ -106,6 +119,16 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Response Delete( const DeletePathOptions& options = DeletePathOptions()) const; + /** + * @brief Deletes the resource the path points to if it exists. + * @param options Optional parameters to delete the reource the path points to. + * @return Azure::Core::Response which is current empty but preserved + * for future usage. The result will only valid if the delete operation is successful. + * @remark This request is sent to dfs endpoint. + */ + Azure::Core::Response DeleteIfExists( + const DeletePathOptions& options = DeletePathOptions()) const; + /** * @brief Sets the owner, group, permissions, or access control list for a file or directory. * Note that Hierarchical Namespace must be enabled for the account in order to use diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp index cdb4f6b00..7f7497f70 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp @@ -21,7 +21,6 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { nam // FileSystemClient models: - using DeleteFileSystemResult = FileSystemDeleteResult; using ListPathsResult = FileSystemListPathsResult; struct GetFileSystemPropertiesResult @@ -31,12 +30,28 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { nam Storage::Metadata Metadata; }; - using CreateFileSystemResult = FileSystemCreateResult; + struct CreateFileSystemResult + { + bool Created = true; + std::string ETag; + Core::DateTime LastModified; + }; + + struct DeleteFileSystemResult + { + bool Deleted = true; + }; + using SetFileSystemMetadataResult = FileSystemCreateResult; // PathClient models: - using DeletePathResult = PathDeleteResult; + struct DeletePathResult + { + bool Deleted = true; + Azure::Core::Nullable ContinuationToken; + }; + using AcquirePathLeaseResult = Blobs::Models::AcquireBlobLeaseResult; using RenewPathLeaseResult = Blobs::Models::RenewBlobLeaseResult; using ReleasePathLeaseResult = Blobs::Models::ReleaseBlobLeaseResult; @@ -124,6 +139,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { nam struct CreatePathResult { + bool Created = true; std::string ETag; Core::DateTime LastModified; Azure::Core::Nullable ContentLength; @@ -161,6 +177,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { nam struct DeleteFileResult { + bool Deleted = true; }; struct DownloadFileToResult @@ -185,6 +202,6 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { nam using SetDirectoryAccessControlRecursiveResult = PathSetAccessControlRecursiveResult; using CreateDirectoryResult = CreatePathResult; - using DeleteDirectoryResult = PathDeleteResult; + using DeleteDirectoryResult = DeletePathResult; }}}}} // namespace Azure::Storage::Files::DataLake::Models diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_directory_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_directory_client.cpp index 9bbca4c26..2154ac901 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_directory_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_directory_client.cpp @@ -177,7 +177,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { auto result = Details::DataLakeRestClient::Path::Create( destinationDfsUri, *m_pipeline, options.Context, protocolLayerOptions); // At this point, there is not more exception thrown, meaning the rename is successful. - auto ret = Models::RenameDirectoryResult(); + Models::RenameDirectoryResult ret; ret.ContinuationToken = std::move(result->ContinuationToken); return Azure::Core::Response( std::move(ret), result.ExtractRawResponse()); @@ -187,16 +187,24 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { bool recursive, const DeleteDirectoryOptions& options) const { - Details::DataLakeRestClient::Path::DeleteOptions protocolLayerOptions; - protocolLayerOptions.ContinuationToken = options.ContinuationToken; - protocolLayerOptions.LeaseIdOptional = options.AccessConditions.LeaseId; - protocolLayerOptions.IfMatch = options.AccessConditions.IfMatch; - protocolLayerOptions.IfNoneMatch = options.AccessConditions.IfNoneMatch; - protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; - protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; - protocolLayerOptions.RecursiveOptional = recursive; - return Details::DataLakeRestClient::Path::Delete( - m_dfsUri, *m_pipeline, options.Context, protocolLayerOptions); + DeletePathOptions deleteOptions; + deleteOptions.AccessConditions = options.AccessConditions; + deleteOptions.Context = options.Context; + deleteOptions.ContinuationToken = options.ContinuationToken; + deleteOptions.Recursive = recursive; + return PathClient::Delete(deleteOptions); + } + + Azure::Core::Response DirectoryClient::DeleteIfExists( + bool recursive, + const DeleteDirectoryOptions& options) const + { + DeletePathOptions deleteOptions; + deleteOptions.AccessConditions = options.AccessConditions; + deleteOptions.Context = options.Context; + deleteOptions.ContinuationToken = options.ContinuationToken; + deleteOptions.Recursive = recursive; + return PathClient::DeleteIfExists(deleteOptions); } Azure::Core::Response diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_file_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_file_client.cpp index b65635a17..beaf85bcd 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_file_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_file_client.cpp @@ -15,6 +15,7 @@ #include #include +#include "azure/storage/files/datalake/datalake_constants.hpp" #include "azure/storage/files/datalake/datalake_utilities.hpp" #include "azure/storage/files/datalake/version.hpp" @@ -276,7 +277,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { auto result = Details::DataLakeRestClient::Path::Create( destinationDfsUri, *m_pipeline, options.Context, protocolLayerOptions); // At this point, there is not more exception thrown, meaning the rename is successful. - auto ret = Models::RenameFileResult(); + Models::RenameFileResult ret; return Azure::Core::Response( std::move(ret), result.ExtractRawResponse()); } @@ -284,15 +285,25 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Azure::Core::Response FileClient::Delete( const FileDeleteOptions& options) const { - Details::DataLakeRestClient::Path::DeleteOptions protocolLayerOptions; - protocolLayerOptions.LeaseIdOptional = options.AccessConditions.LeaseId; - protocolLayerOptions.IfMatch = options.AccessConditions.IfMatch; - protocolLayerOptions.IfNoneMatch = options.AccessConditions.IfNoneMatch; - protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; - protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; - auto result = Details::DataLakeRestClient::Path::Delete( - m_dfsUri, *m_pipeline, options.Context, protocolLayerOptions); - auto ret = Models::DeleteFileResult(); + DeletePathOptions deleteOptions; + deleteOptions.AccessConditions = options.AccessConditions; + deleteOptions.Context = options.Context; + auto result = PathClient::Delete(deleteOptions); + Models::DeleteFileResult ret; + ret.Deleted = true; + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + + Azure::Core::Response FileClient::DeleteIfExists( + const FileDeleteOptions& options) const + { + DeletePathOptions deleteOptions; + deleteOptions.AccessConditions = options.AccessConditions; + deleteOptions.Context = options.Context; + auto result = PathClient::DeleteIfExists(deleteOptions); + Models::DeleteFileResult ret; + ret.Deleted = result->Deleted; return Azure::Core::Response( std::move(ret), result.ExtractRawResponse()); } diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp index f88a4806f..c820d6857 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_file_system_client.cpp @@ -12,6 +12,7 @@ #include #include +#include "azure/storage/files/datalake/datalake_constants.hpp" #include "azure/storage/files/datalake/datalake_directory_client.hpp" #include "azure/storage/files/datalake/datalake_file_client.hpp" #include "azure/storage/files/datalake/datalake_path_client.hpp" @@ -194,11 +195,32 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Models::CreateFileSystemResult ret; ret.ETag = std::move(result->ETag); ret.LastModified = std::move(result->LastModified); + ret.Created = true; return Azure::Core::Response( std::move(ret), result.ExtractRawResponse()); } - Azure::Core::Response FileSystemClient::Delete( + Azure::Core::Response FileSystemClient::CreateIfNotExists( + const CreateFileSystemOptions& options) const + { + try + { + return Create(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::ContainerAlreadyExists) + { + Models::CreateFileSystemResult ret; + ret.Created = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } + } + + Azure::Core::Response FileSystemClient::Delete( const DeleteFileSystemOptions& options) const { Blobs::DeleteBlobContainerOptions blobOptions; @@ -207,11 +229,31 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { blobOptions.AccessConditions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; blobOptions.AccessConditions.LeaseId = options.AccessConditions.LeaseId; auto result = m_blobContainerClient.Delete(blobOptions); - Models::FileSystemDeleteResult ret; - return Azure::Core::Response( + Models::DeleteFileSystemResult ret; + ret.Deleted = true; + return Azure::Core::Response( std::move(ret), result.ExtractRawResponse()); } + Azure::Core::Response FileSystemClient::DeleteIfExists( + const DeleteFileSystemOptions& options) const + { + try + { + return Delete(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::ContainerNotFound) + { + Models::DeleteFileSystemResult ret; + ret.Deleted = false; + return Azure::Core::Response(ret, std::move(e.RawResponse)); + } + throw; + } + } + Azure::Core::Response FileSystemClient::GetProperties( const GetFileSystemPropertiesOptions& options) const { diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp index 82220c12f..8d7be550a 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp @@ -11,6 +11,7 @@ #include #include +#include "azure/storage/files/datalake/datalake_constants.hpp" #include "azure/storage/files/datalake/datalake_utilities.hpp" #include "azure/storage/files/datalake/version.hpp" @@ -248,7 +249,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { protocolLayerOptions.Permissions = options.Permissions; auto result = Details::DataLakeRestClient::Path::Create( m_dfsUri, *m_pipeline, options.Context, protocolLayerOptions); - auto ret = Models::CreatePathResult(); + Models::CreatePathResult ret; ret.ETag = std::move(result->ETag.GetValue()); ret.LastModified = std::move(result->LastModified.GetValue()); ret.ContentLength = std::move(result->ContentLength); @@ -256,6 +257,29 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { std::move(ret), result.ExtractRawResponse()); } + Azure::Core::Response PathClient::CreateIfNotExists( + Models::PathResourceType type, + const CreatePathOptions& options) const + { + try + { + auto createOptions = options; + createOptions.AccessConditions.IfNoneMatch = ETagWildcard; + return Create(type, createOptions); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::DataLakePathAlreadyExists) + { + Models::CreatePathResult ret; + ret.Created = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } + } + Azure::Core::Response PathClient::Delete( const DeletePathOptions& options) const { @@ -267,8 +291,34 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; protocolLayerOptions.RecursiveOptional = options.Recursive; - return Details::DataLakeRestClient::Path::Delete( + auto result = Details::DataLakeRestClient::Path::Delete( m_dfsUri, *m_pipeline, options.Context, protocolLayerOptions); + Models::DeletePathResult ret; + ret.ContinuationToken = std::move(result->ContinuationToken); + ret.Deleted = true; + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + + Azure::Core::Response PathClient::DeleteIfExists( + const DeletePathOptions& options) const + { + try + { + return Delete(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::DataLakeFilesystemNotFound + || e.ErrorCode == Details::DataLakePathNotFound) + { + Models::DeletePathResult ret; + ret.Deleted = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } } Azure::Core::Response PathClient::GetProperties( @@ -332,7 +382,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { { acl = Models::Acl::DeserializeAcls(result->Acl.GetValue()); } - auto ret = Models::GetPathAccessControlResult{}; + Models::GetPathAccessControlResult ret; ret.ETag = std::move(result->ETag); ret.LastModified = std::move(result->LastModified); if (!acl.HasValue()) diff --git a/sdk/storage/azure-storage-files-datalake/test/datalake_directory_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/datalake_directory_client_test.cpp index 1883f48bc..c70d7cb2f 100644 --- a/sdk/storage/azure-storage-files-datalake/test/datalake_directory_client_test.cpp +++ b/sdk/storage/azure-storage-files-datalake/test/datalake_directory_client_test.cpp @@ -17,7 +17,7 @@ namespace Azure { namespace Storage { namespace Test { void DataLakeDirectoryClientTest::SetUpTestSuite() { DataLakeFileSystemClientTest::SetUpTestSuite(); - m_directoryName = LowercaseRandomString(10); + m_directoryName = RandomString(10); m_directoryClient = std::make_shared( m_fileSystemClient->GetDirectoryClient(m_directoryName)); m_fileSystemClient->GetFileClient(m_directoryName).Create(); @@ -36,7 +36,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClient; for (int32_t i = 0; i < 5; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); EXPECT_NO_THROW(client.Create()); directoryClient.emplace_back(std::move(client)); } @@ -50,7 +50,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); EXPECT_NO_THROW(client.Create()); directoryClient.emplace_back(std::move(client)); } @@ -71,7 +71,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); EXPECT_NO_THROW(client.Create()); directoryClient.emplace_back(std::move(client)); } @@ -90,13 +90,12 @@ namespace Azure { namespace Storage { namespace Test { { // Recursive delete works. std::vector directoryClient; - auto rootDir = LowercaseRandomString(); + auto rootDir = RandomString(); auto rootDirClient = m_fileSystemClient->GetDirectoryClient(rootDir); EXPECT_NO_THROW(rootDirClient.Create()); for (int32_t i = 0; i < 5; ++i) { - auto client - = m_fileSystemClient->GetDirectoryClient(rootDir + "/" + LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(rootDir + "/" + RandomString()); EXPECT_NO_THROW(client.Create()); directoryClient.emplace_back(std::move(client)); } @@ -105,6 +104,30 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(DataLakeDirectoryClientTest, CreateDeleteIfExistsDirectory) + { + { + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); + bool created = false; + bool deleted = false; + EXPECT_NO_THROW(created = client.Create()->Created); + EXPECT_TRUE(created); + EXPECT_NO_THROW(created = client.CreateIfNotExists()->Created); + EXPECT_FALSE(created); + EXPECT_NO_THROW(deleted = client.Delete(false)->Deleted); + EXPECT_TRUE(deleted); + EXPECT_NO_THROW(deleted = client.DeleteIfExists(false)->Deleted); + EXPECT_FALSE(deleted); + } + { + auto client = Files::DataLake::DirectoryClient::CreateFromConnectionString( + AdlsGen2ConnectionString(), LowercaseRandomString(), RandomString()); + bool deleted = false; + EXPECT_NO_THROW(deleted = client.DeleteIfExists(false)->Deleted); + EXPECT_FALSE(deleted); + } + } + TEST_F(DataLakeDirectoryClientTest, RenameDirectory) { { @@ -112,14 +135,14 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClients; for (int32_t i = 0; i < 5; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); EXPECT_NO_THROW(client.Create()); directoryClients.emplace_back(std::move(client)); } std::vector newPaths; for (auto& client : directoryClients) { - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath)); newPaths.push_back(newPath); } @@ -137,7 +160,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); EXPECT_NO_THROW(client.Create()); directoryClient.emplace_back(std::move(client)); } @@ -147,10 +170,10 @@ namespace Azure { namespace Storage { namespace Test { Files::DataLake::RenameDirectoryOptions options1; options1.SourceAccessConditions.IfModifiedSince = response->LastModified; EXPECT_TRUE(IsValidTime(response->LastModified)); - EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageException); + EXPECT_THROW(client.Rename(RandomString(), options1), StorageException); Files::DataLake::RenameDirectoryOptions options2; options2.SourceAccessConditions.IfUnmodifiedSince = response->LastModified; - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath, options2)); EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } @@ -160,7 +183,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); EXPECT_NO_THROW(client.Create()); directoryClient.emplace_back(std::move(client)); } @@ -169,10 +192,10 @@ namespace Azure { namespace Storage { namespace Test { auto response = client.GetProperties(); Files::DataLake::RenameDirectoryOptions options1; options1.SourceAccessConditions.IfNoneMatch = response->ETag; - EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageException); + EXPECT_THROW(client.Rename(RandomString(), options1), StorageException); Files::DataLake::RenameDirectoryOptions options2; options2.SourceAccessConditions.IfMatch = response->ETag; - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath, options2)); EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } @@ -182,7 +205,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); EXPECT_NO_THROW(client.Create()); directoryClient.emplace_back(std::move(client)); } @@ -192,7 +215,7 @@ namespace Azure { namespace Storage { namespace Test { options.DestinationFileSystem = LowercaseRandomString(); for (auto& client : directoryClient) { - EXPECT_THROW(client.Rename(LowercaseRandomString(), options), StorageException); + EXPECT_THROW(client.Rename(RandomString(), options), StorageException); EXPECT_NO_THROW(client.GetProperties()); } } @@ -207,7 +230,7 @@ namespace Azure { namespace Storage { namespace Test { options.DestinationFileSystem = newfileSystemName; for (auto& client : directoryClient) { - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath, options)); EXPECT_NO_THROW(newfileSystemClient->GetDirectoryClient(newPath).Delete(false)); } @@ -231,8 +254,8 @@ namespace Azure { namespace Storage { namespace Test { { // Create path with metadata works - auto client1 = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); - auto client2 = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client1 = m_fileSystemClient->GetDirectoryClient(RandomString()); + auto client2 = m_fileSystemClient->GetDirectoryClient(RandomString()); Files::DataLake::CreatePathOptions options1; Files::DataLake::CreatePathOptions options2; options1.Metadata = metadata1; @@ -284,7 +307,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector directoryClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetDirectoryClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetDirectoryClient(RandomString()); Files::DataLake::CreatePathOptions options; options.HttpHeaders = httpHeader; EXPECT_NO_THROW(client.Create(options)); @@ -305,9 +328,9 @@ namespace Azure { namespace Storage { namespace Test { TEST_F(DataLakeDirectoryClientTest, DirectorySetAccessControlRecursive) { // Setup directories. - auto rootDirectoryName = LowercaseRandomString(); - auto directoryName1 = LowercaseRandomString(); - auto directoryName2 = LowercaseRandomString(); + auto rootDirectoryName = RandomString(); + auto directoryName1 = RandomString(); + auto directoryName2 = RandomString(); auto rootDirectoryClient = m_fileSystemClient->GetDirectoryClient(rootDirectoryName); rootDirectoryClient.Create(); auto directoryClient1 @@ -346,7 +369,7 @@ namespace Azure { namespace Storage { namespace Test { { { // Create from connection string validates static creator function and shared key constructor. - auto directoryName = LowercaseRandomString(10); + auto directoryName = RandomString(10); auto connectionStringClient = Azure::Storage::Files::DataLake::DirectoryClient::CreateFromConnectionString( AdlsGen2ConnectionString(), m_fileSystemName, directoryName); @@ -361,7 +384,7 @@ namespace Azure { namespace Storage { namespace Test { auto clientSecretClient = Azure::Storage::Files::DataLake::DirectoryClient( Azure::Storage::Files::DataLake::DirectoryClient::CreateFromConnectionString( - AdlsGen2ConnectionString(), m_fileSystemName, LowercaseRandomString(10)) + AdlsGen2ConnectionString(), m_fileSystemName, RandomString(10)) .GetUri(), credential); @@ -371,7 +394,7 @@ namespace Azure { namespace Storage { namespace Test { { // Create from Anonymous credential. - auto objectName = LowercaseRandomString(10); + auto objectName = RandomString(10); auto containerClient = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( AdlsGen2ConnectionString(), m_fileSystemName); Azure::Storage::Blobs::SetBlobContainerAccessPolicyOptions options; diff --git a/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp index ebfaf4b7d..9f53fce16 100644 --- a/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp +++ b/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp @@ -33,7 +33,7 @@ namespace Azure { namespace Storage { namespace Test { void DataLakeFileClientTest::SetUpTestSuite() { DataLakeFileSystemClientTest::SetUpTestSuite(); - m_fileName = LowercaseRandomString(10); + m_fileName = RandomString(10); m_fileClient = std::make_shared( m_fileSystemClient->GetFileClient(m_fileName)); m_fileClient->Create(); @@ -52,7 +52,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClient; for (int32_t i = 0; i < 5; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); fileClient.emplace_back(std::move(client)); } @@ -66,7 +66,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); fileClient.emplace_back(std::move(client)); } @@ -87,7 +87,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); fileClient.emplace_back(std::move(client)); } @@ -104,6 +104,30 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(DataLakeFileClientTest, CreateDeleteIfExistsFiles) + { + { + auto client = m_fileSystemClient->GetFileClient(RandomString()); + bool created = false; + bool deleted = false; + EXPECT_NO_THROW(created = client.Create()->Created); + EXPECT_TRUE(created); + EXPECT_NO_THROW(created = client.CreateIfNotExists()->Created); + EXPECT_FALSE(created); + EXPECT_NO_THROW(deleted = client.Delete()->Deleted); + EXPECT_TRUE(deleted); + EXPECT_NO_THROW(deleted = client.DeleteIfExists()->Deleted); + EXPECT_FALSE(deleted); + } + { + auto client = Files::DataLake::FileClient::CreateFromConnectionString( + AdlsGen2ConnectionString(), LowercaseRandomString(), RandomString()); + bool deleted = false; + EXPECT_NO_THROW(deleted = client.DeleteIfExists()->Deleted); + EXPECT_FALSE(deleted); + } + } + TEST_F(DataLakeFileClientTest, RenameFiles) { { @@ -111,14 +135,14 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClients; for (int32_t i = 0; i < 5; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); fileClients.emplace_back(std::move(client)); } std::vector newPaths; for (auto& client : fileClients) { - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath)); newPaths.push_back(newPath); } @@ -136,7 +160,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); fileClient.emplace_back(std::move(client)); } @@ -145,10 +169,10 @@ namespace Azure { namespace Storage { namespace Test { auto response = client.GetProperties(); Files::DataLake::RenameFileOptions options1; options1.SourceAccessConditions.IfModifiedSince = response->LastModified; - EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageException); + EXPECT_THROW(client.Rename(RandomString(), options1), StorageException); Files::DataLake::RenameFileOptions options2; options2.SourceAccessConditions.IfUnmodifiedSince = response->LastModified; - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath, options2)); EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } @@ -158,7 +182,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); fileClient.emplace_back(std::move(client)); } @@ -167,10 +191,10 @@ namespace Azure { namespace Storage { namespace Test { auto response = client.GetProperties(); Files::DataLake::RenameFileOptions options1; options1.SourceAccessConditions.IfNoneMatch = response->ETag; - EXPECT_THROW(client.Rename(LowercaseRandomString(), options1), StorageException); + EXPECT_THROW(client.Rename(RandomString(), options1), StorageException); Files::DataLake::RenameFileOptions options2; options2.SourceAccessConditions.IfMatch = response->ETag; - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath, options2)); EXPECT_NO_THROW(m_fileSystemClient->GetDirectoryClient(newPath).Delete(false)); } @@ -180,7 +204,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); fileClient.emplace_back(std::move(client)); } @@ -190,7 +214,7 @@ namespace Azure { namespace Storage { namespace Test { options.DestinationFileSystem = LowercaseRandomString(); for (auto& client : fileClient) { - EXPECT_THROW(client.Rename(LowercaseRandomString(), options), StorageException); + EXPECT_THROW(client.Rename(RandomString(), options), StorageException); EXPECT_NO_THROW(client.GetProperties()); } } @@ -205,7 +229,7 @@ namespace Azure { namespace Storage { namespace Test { options.DestinationFileSystem = newfileSystemName; for (auto& client : fileClient) { - auto newPath = LowercaseRandomString(); + auto newPath = RandomString(); EXPECT_NO_THROW(client.Rename(newPath, options)); EXPECT_NO_THROW(newfileSystemClient->GetDirectoryClient(newPath).Delete(false)); } @@ -229,8 +253,8 @@ namespace Azure { namespace Storage { namespace Test { { // Create path with metadata works - auto client1 = m_fileSystemClient->GetFileClient(LowercaseRandomString()); - auto client2 = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client1 = m_fileSystemClient->GetFileClient(RandomString()); + auto client2 = m_fileSystemClient->GetFileClient(RandomString()); Files::DataLake::CreateFileOptions options1; Files::DataLake::CreateFileOptions options2; options1.Metadata = metadata1; @@ -280,7 +304,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector fileClient; for (int32_t i = 0; i < 2; ++i) { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); Files::DataLake::CreateFileOptions options; options.HttpHeaders = httpHeader; EXPECT_NO_THROW(client.Create(options)); @@ -331,7 +355,7 @@ namespace Azure { namespace Storage { namespace Test { auto buffer = RandomBuffer(bufferSize); auto bufferStream = std::make_unique( Azure::Core::Http::MemoryBodyStream(buffer)); - auto newFileName = LowercaseRandomString(10); + auto newFileName = RandomString(10); auto newFileClient = std::make_shared( m_fileSystemClient->GetFileClient(newFileName)); newFileClient->Create(); @@ -407,13 +431,13 @@ namespace Azure { namespace Storage { namespace Test { TEST_F(DataLakeFileClientTest, ScheduleForDeletion) { { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); EXPECT_NO_THROW( client.ScheduleDeletion(Files::DataLake::ScheduleFileExpiryOriginType::NeverExpire)); } { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); Files::DataLake::ScheduleFileDeletionOptions options; EXPECT_THROW( @@ -425,7 +449,7 @@ namespace Azure { namespace Storage { namespace Test { Files::DataLake::ScheduleFileExpiryOriginType::RelativeToNow, options)); } { - auto client = m_fileSystemClient->GetFileClient(LowercaseRandomString()); + auto client = m_fileSystemClient->GetFileClient(RandomString()); EXPECT_NO_THROW(client.Create()); Files::DataLake::ScheduleFileDeletionOptions options; EXPECT_THROW( @@ -539,7 +563,7 @@ namespace Azure { namespace Storage { namespace Test { { { // Create from connection string validates static creator function and shared key constructor. - auto fileName = LowercaseRandomString(10); + auto fileName = RandomString(10); auto connectionStringClient = Azure::Storage::Files::DataLake::FileClient::CreateFromConnectionString( AdlsGen2ConnectionString(), m_fileSystemName, fileName); @@ -554,7 +578,7 @@ namespace Azure { namespace Storage { namespace Test { auto clientSecretClient = Azure::Storage::Files::DataLake::FileClient( Azure::Storage::Files::DataLake::FileClient::CreateFromConnectionString( - AdlsGen2ConnectionString(), m_fileSystemName, LowercaseRandomString(10)) + AdlsGen2ConnectionString(), m_fileSystemName, RandomString(10)) .GetUri(), credential); @@ -567,7 +591,7 @@ namespace Azure { namespace Storage { namespace Test { std::vector blobContent; blobContent.resize(static_cast(1_MB)); RandomBuffer(reinterpret_cast(&blobContent[0]), blobContent.size()); - auto objectName = LowercaseRandomString(10); + auto objectName = RandomString(10); auto containerClient = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( AdlsGen2ConnectionString(), m_fileSystemName); Azure::Storage::Blobs::SetBlobContainerAccessPolicyOptions options; diff --git a/sdk/storage/azure-storage-files-datalake/test/datalake_file_system_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/datalake_file_system_client_test.cpp index d9412c81f..ecacb0d8c 100644 --- a/sdk/storage/azure-storage-files-datalake/test/datalake_file_system_client_test.cpp +++ b/sdk/storage/azure-storage-files-datalake/test/datalake_file_system_client_test.cpp @@ -130,6 +130,42 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_NO_THROW(client.Delete(options2)); } } + { + // CreateIfNotExists & DeleteIfExists. + { + auto client = Files::DataLake::FileSystemClient::CreateFromConnectionString( + AdlsGen2ConnectionString(), LowercaseRandomString()); + EXPECT_NO_THROW(client.Create()); + EXPECT_NO_THROW(client.CreateIfNotExists()); + EXPECT_NO_THROW(client.Delete()); + EXPECT_NO_THROW(client.DeleteIfExists()); + } + { + auto client = Files::DataLake::FileSystemClient::CreateFromConnectionString( + AdlsGen2ConnectionString(), LowercaseRandomString()); + EXPECT_NO_THROW(client.CreateIfNotExists()); + EXPECT_THROW(client.Create(), StorageException); + EXPECT_NO_THROW(client.DeleteIfExists()); + } + { + auto client = Files::DataLake::FileSystemClient::CreateFromConnectionString( + AdlsGen2ConnectionString(), LowercaseRandomString()); + auto created = client.Create()->Created; + EXPECT_TRUE(created); + auto createResult = client.CreateIfNotExists(); + EXPECT_FALSE(createResult->Created); + EXPECT_TRUE(createResult->ETag.empty()); + EXPECT_EQ(Core::DateTime(), createResult->LastModified); + auto deleted = client.Delete()->Deleted; + EXPECT_TRUE(deleted); + } + { + auto client = Files::DataLake::FileSystemClient::CreateFromConnectionString( + AdlsGen2ConnectionString(), LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + } } TEST_F(DataLakeFileSystemClientTest, FileSystemMetadata) diff --git a/sdk/storage/azure-storage-files-shares/CHANGELOG.md b/sdk/storage/azure-storage-files-shares/CHANGELOG.md index ae50806dc..0f3316070 100644 --- a/sdk/storage/azure-storage-files-shares/CHANGELOG.md +++ b/sdk/storage/azure-storage-files-shares/CHANGELOG.md @@ -21,6 +21,11 @@ - Removed `Offset` and `Length` pair in options. They are now represented with `Azure::Core::Http::Range`. - Replace scoped enums that don't support bitwise operations with extensible enum. - `IsServerEncrypted` member in `DownloadFileToResult`, `UploadFileFromResult`, `FileDownloadResult` and `FileGetPropertiesResult` are no longer nullable. +- Create APIs for Directory and File now returns `FileShareSmbProperties` that aggregates SMB related properties. + +### New Features + +- Added support for `CreateIfNotExists` for Share and Directory clients, and `DeleteIfExists` for Share, Directory and File clients. ## 12.0.0-beta.5 (2020-11-13) diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_client.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_client.hpp index 8cdc3d2ab..ee33c9c54 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_client.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_client.hpp @@ -105,6 +105,15 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Azure::Core::Response Create( const CreateShareOptions& options = CreateShareOptions()) const; + /** + * @brief Creates the file share if it does not exist, nothing will happen if the file share already exists. + * @param options Optional parameters to create this file share. + * @return Azure::Core::Response containing the information including + * the version and modified time of a share if it is successfully created. + */ + Azure::Core::Response CreateIfNotExists( + const CreateShareOptions& options = CreateShareOptions()) const; + /** * @brief Deletes the file share. * @param options Optional parameters to delete this file share. @@ -114,6 +123,15 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Azure::Core::Response Delete( const DeleteShareOptions& options = DeleteShareOptions()) const; + /** + * @brief Deletes the file share if it exists. + * @param options Optional parameters to delete this file share. + * @return Azure::Core::Response currently empty and reserved for + * future usage. + */ + Azure::Core::Response DeleteIfExists( + const DeleteShareOptions& options = DeleteShareOptions()) const; + /** * @brief Creates a snapshot for the share. * @param options Optional parameters to create the share snapshot. diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_constants.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_constants.hpp index c76fb7a11..04ab64e7d 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_constants.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_constants.hpp @@ -16,6 +16,13 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { constexpr int64_t c_FileUploadDefaultChunkSize = 4 * 1024 * 1024; constexpr int64_t c_FileDownloadDefaultChunkSize = 4 * 1024 * 1024; constexpr static const char* c_ShareSnapshotQueryParameter = "sharesnapshot"; + + // Error codes: + constexpr static const char* ParentNotFound = "ParentNotFound"; + constexpr static const char* ResourceNotFound = "ResourceNotFound"; + constexpr static const char* ShareAlreadyExists = "ShareAlreadyExists"; + constexpr static const char* ShareNotFound = "ShareNotFound"; + constexpr static const char* ResourceAlreadyExists = "ResourceAlreadyExists"; } // namespace Details }}}} // namespace Azure::Storage::Files::Shares diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_directory_client.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_directory_client.hpp index fa1c5f3bd..469a77a75 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_directory_client.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_directory_client.hpp @@ -101,6 +101,15 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Azure::Core::Response Create( const CreateDirectoryOptions& options = CreateDirectoryOptions()) const; + /** + * @brief Creates the directory if it does not exist. + * @param options Optional parameters to create this directory. + * @return Azure::Core::Response containing the information + * returned when creating the directory if successfully created. + */ + Azure::Core::Response CreateIfNotExists( + const CreateDirectoryOptions& options = CreateDirectoryOptions()) const; + /** * @brief Deletes the directory. * @param options Optional parameters to delete this directory. @@ -110,6 +119,17 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Azure::Core::Response Delete( const DeleteDirectoryOptions& options = DeleteDirectoryOptions()) const; + /** + * @brief Deletes the directory if it exists. + * @param options Optional parameters to delete this directory. + * @return Azure::Core::Response containing the information + * returned when deleting the directory. Currently empty but preserved for future usage. + * Only when the delete operation if successful, the returned information other than 'Deleted' + * is valid. + */ + Azure::Core::Response DeleteIfExists( + const DeleteDirectoryOptions& options = DeleteDirectoryOptions()) const; + /** * @brief Gets the properties of the directory. * @param options Optional parameters to get this directory's properties. diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_file_client.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_file_client.hpp index a3a76eac6..7401ce7f1 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_file_client.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_file_client.hpp @@ -94,6 +94,15 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Azure::Core::Response Delete( const DeleteFileOptions& options = DeleteFileOptions()) const; + /** + * @brief Deletes the file if it exists. + * @param options Optional parameters to delete this file. + * @return Azure::Core::Response containing the information returned when + * deleting the file. Only valid when successfully deleted. + */ + Azure::Core::Response DeleteIfExists( + const DeleteFileOptions& options = DeleteFileOptions()) const; + /** * @brief Open a stream for the file's content, or a range of the file's content that can be * used to download the server end data. diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_responses.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_responses.hpp index 0b0505222..06e16d541 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_responses.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_responses.hpp @@ -16,8 +16,17 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { names using GetServicePropertiesResult = StorageServiceProperties; // ShareClient models: - using CreateShareResult = ShareCreateResult; - using DeleteShareResult = ShareDeleteResult; + struct CreateShareResult + { + bool Created = true; + std::string ETag; + Core::DateTime LastModified; + }; + + struct DeleteShareResult + { + bool Deleted = true; + }; using CreateShareSnapshotResult = ShareCreateSnapshotResult; using GetSharePropertiesResult = ShareGetPropertiesResult; using SetShareQuotaResult = ShareSetQuotaResult; @@ -34,8 +43,26 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { names using ChangeShareLeaseResult = ShareChangeLeaseResult; // DirectoryClient models: - using CreateDirectoryResult = DirectoryCreateResult; - using DeleteDirectoryResult = DirectoryDeleteResult; + struct CreateDirectoryResult + { + bool Created = true; + std::string ETag; + Core::DateTime LastModified; + bool IsServerEncrypted = bool(); + std::string FilePermissionKey; + std::string FileAttributes; + Core::DateTime FileCreatedOn; + Core::DateTime FileLastWrittenOn; + Core::DateTime FileChangedOn; + std::string FileId; + std::string FileParentId; + }; + + struct DeleteDirectoryResult + { + bool Deleted = true; + }; + using GetDirectoryPropertiesResult = DirectoryGetPropertiesResult; using SetDirectoryPropertiesResult = DirectorySetPropertiesResult; using SetDirectoryMetadataResult = DirectorySetMetadataResult; @@ -65,6 +92,27 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { names std::string ContinuationToken; }; + // FileClient models: + struct CreateFileResult + { + bool Created = true; + std::string ETag; + Core::DateTime LastModified; + bool IsServerEncrypted = bool(); + std::string FilePermissionKey; + std::string FileAttributes; + Core::DateTime FileCreatedOn; + Core::DateTime FileLastWrittenOn; + Core::DateTime FileChangedOn; + std::string FileId; + std::string FileParentId; + }; + + struct DeleteFileResult + { + bool Deleted = true; + }; + struct FileShareSmbProperties { /** @@ -90,9 +138,6 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { names Azure::Core::Nullable LastWrittenOn; }; - // FileClient models: - using CreateFileResult = FileCreateResult; - using DeleteFileResult = FileDeleteResult; using DownloadFileResult = FileDownloadResult; using StartCopyFileResult = FileStartCopyResult; using AbortCopyFileResult = FileAbortCopyResult; diff --git a/sdk/storage/azure-storage-files-shares/src/share_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_client.cpp index 66561d675..3bfce2c12 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_client.cpp @@ -129,8 +129,34 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto protocolLayerOptions = Details::ShareRestClient::Share::CreateOptions(); protocolLayerOptions.Metadata = options.Metadata; protocolLayerOptions.ShareQuota = options.ShareQuotaInGiB; - return Details::ShareRestClient::Share::Create( + auto result = Details::ShareRestClient::Share::Create( m_shareUri, *m_pipeline, options.Context, protocolLayerOptions); + Models::CreateShareResult ret; + ret.Created = true; + ret.ETag = std::move(result->ETag); + ret.LastModified = std::move(result->LastModified); + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + + Azure::Core::Response ShareClient::CreateIfNotExists( + const CreateShareOptions& options) const + { + try + { + return Create(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::ShareAlreadyExists) + { + Models::CreateShareResult ret; + ret.Created = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } } Azure::Core::Response ShareClient::Delete( @@ -141,8 +167,32 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { protocolLayerOptions.XMsDeleteSnapshots = Models::DeleteSnapshotsOptionType::Include; } - return Details::ShareRestClient::Share::Delete( + auto result = Details::ShareRestClient::Share::Delete( m_shareUri, *m_pipeline, options.Context, protocolLayerOptions); + Models::DeleteShareResult ret; + ret.Deleted = true; + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + + Azure::Core::Response ShareClient::DeleteIfExists( + const DeleteShareOptions& options) const + { + try + { + return Delete(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::ShareNotFound) + { + Models::DeleteShareResult ret; + ret.Deleted = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } } Azure::Core::Response ShareClient::CreateSnapshot( diff --git a/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp index bdd2c8fe7..321105795 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp @@ -167,16 +167,77 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { protocolLayerOptions.FilePermission = std::string(c_FileInheritPermission); } - return Details::ShareRestClient::Directory::Create( + auto result = Details::ShareRestClient::Directory::Create( m_shareDirectoryUri, *m_pipeline, options.Context, protocolLayerOptions); + Models::CreateDirectoryResult ret; + ret.Created = true; + ret.ETag = std::move(result->ETag); + ret.FileAttributes = result->FileAttributes; + ret.FileCreatedOn = std::move(result->FileCreatedOn); + ret.FileLastWrittenOn = std::move(result->FileLastWrittenOn); + ret.FilePermissionKey = std::move(result->FilePermissionKey); + ret.FileChangedOn = std::move(result->FileChangedOn); + ret.FileId = std::move(result->FileId); + ret.FileParentId = std::move(result->FileParentId); + ret.IsServerEncrypted = result->IsServerEncrypted; + ret.LastModified = std::move(result->LastModified); + + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + + Azure::Core::Response ShareDirectoryClient::CreateIfNotExists( + const CreateDirectoryOptions& options) const + + { + try + { + return Create(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::ResourceAlreadyExists) + { + Models::CreateDirectoryResult ret; + ret.Created = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } } Azure::Core::Response ShareDirectoryClient::Delete( const DeleteDirectoryOptions& options) const { auto protocolLayerOptions = Details::ShareRestClient::Directory::DeleteOptions(); - return Details::ShareRestClient::Directory::Delete( + auto result = Details::ShareRestClient::Directory::Delete( m_shareDirectoryUri, *m_pipeline, options.Context, protocolLayerOptions); + Models::DeleteDirectoryResult ret; + ret.Deleted = true; + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + + Azure::Core::Response ShareDirectoryClient::DeleteIfExists( + const DeleteDirectoryOptions& options) const + { + try + { + return Delete(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::ShareNotFound || e.ErrorCode == Details::ParentNotFound + || e.ErrorCode == Details::ResourceNotFound) + { + Models::DeleteDirectoryResult ret; + ret.Deleted = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } } Azure::Core::Response ShareDirectoryClient::GetProperties( @@ -266,7 +327,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { } Azure::Core::Response - ShareDirectoryClient::ListHandlesSinglePage(const ListDirectoryHandlesSinglePageOptions& options) const + ShareDirectoryClient::ListHandlesSinglePage( + const ListDirectoryHandlesSinglePageOptions& options) const { auto protocolLayerOptions = Details::ShareRestClient::Directory::ListHandlesOptions(); protocolLayerOptions.ContinuationToken = options.ContinuationToken; diff --git a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp index 5fa4081bb..8856ee468 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp @@ -185,8 +185,23 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { protocolLayerOptions.ContentMd5 = options.HttpHeaders.ContentHash; } protocolLayerOptions.LeaseIdOptional = options.AccessConditions.LeaseId; - return Details::ShareRestClient::File::Create( + auto result = Details::ShareRestClient::File::Create( m_shareFileUri, *m_pipeline, options.Context, protocolLayerOptions); + Models::CreateFileResult ret; + ret.Created = true; + ret.ETag = std::move(result->ETag); + ret.FileAttributes = result->FileAttributes; + ret.FileCreatedOn = std::move(result->FileCreatedOn); + ret.FileLastWrittenOn = std::move(result->FileLastWrittenOn); + ret.FilePermissionKey = std::move(result->FilePermissionKey); + ret.FileChangedOn = std::move(result->FileChangedOn); + ret.FileId = std::move(result->FileId); + ret.FileParentId = std::move(result->FileParentId); + ret.IsServerEncrypted = result->IsServerEncrypted; + ret.LastModified = std::move(result->LastModified); + + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); } Azure::Core::Response ShareFileClient::Delete( @@ -194,8 +209,33 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { auto protocolLayerOptions = Details::ShareRestClient::File::DeleteOptions(); protocolLayerOptions.LeaseIdOptional = options.AccessConditions.LeaseId; - return Details::ShareRestClient::File::Delete( + auto result = Details::ShareRestClient::File::Delete( m_shareFileUri, *m_pipeline, options.Context, protocolLayerOptions); + Models::DeleteFileResult ret; + ret.Deleted = true; + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + + Azure::Core::Response ShareFileClient::DeleteIfExists( + const DeleteFileOptions& options) const + { + try + { + return Delete(options); + } + catch (StorageException& e) + { + if (e.ErrorCode == Details::ShareNotFound || e.ErrorCode == Details::ParentNotFound + || e.ErrorCode == Details::ResourceNotFound) + { + Models::DeleteFileResult ret; + ret.Deleted = false; + return Azure::Core::Response( + std::move(ret), std::move(e.RawResponse)); + } + throw; + } } Azure::Core::Response ShareFileClient::Download( @@ -208,7 +248,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { protocolLayerOptions.Range = std::string("bytes=") + std::to_string(options.Range.GetValue().Offset) + std::string("-") - + std::to_string(options.Range.GetValue().Offset + options.Range.GetValue().Length.GetValue() - 1); + + std::to_string(options.Range.GetValue().Offset + + options.Range.GetValue().Length.GetValue() - 1); } else { @@ -472,12 +513,13 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { protocolLayerOptions.XMsRange = std::string("bytes=") + std::to_string(options.Range.GetValue().Offset) + std::string("-") - + std::to_string(options.Range.GetValue().Offset + options.Range.GetValue().Length.GetValue() - 1); + + std::to_string(options.Range.GetValue().Offset + + options.Range.GetValue().Length.GetValue() - 1); } else { - protocolLayerOptions.XMsRange - = std::string("bytes=") + std::to_string(options.Range.GetValue().Offset) + std::string("-"); + protocolLayerOptions.XMsRange = std::string("bytes=") + + std::to_string(options.Range.GetValue().Offset) + std::string("-"); } } @@ -487,8 +529,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { m_shareFileUri, *m_pipeline, options.Context, protocolLayerOptions); } - Azure::Core::Response ShareFileClient::ListHandlesSinglePage( - const ListFileHandlesSinglePageOptions& options) const + Azure::Core::Response + ShareFileClient::ListHandlesSinglePage(const ListFileHandlesSinglePageOptions& options) const { auto protocolLayerOptions = Details::ShareRestClient::File::ListHandlesOptions(); protocolLayerOptions.ContinuationToken = options.ContinuationToken; diff --git a/sdk/storage/azure-storage-files-shares/test/share_client_test.cpp b/sdk/storage/azure-storage-files-shares/test/share_client_test.cpp index 9c2c4c085..46aee9889 100644 --- a/sdk/storage/azure-storage-files-shares/test/share_client_test.cpp +++ b/sdk/storage/azure-storage-files-shares/test/share_client_test.cpp @@ -72,6 +72,42 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_NO_THROW(client.Delete()); } } + { + // CreateIfNotExists & DeleteIfExists. + { + auto client = Files::Shares::ShareClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + EXPECT_NO_THROW(client.Create()); + EXPECT_NO_THROW(client.CreateIfNotExists()); + EXPECT_NO_THROW(client.Delete()); + EXPECT_NO_THROW(client.DeleteIfExists()); + } + { + auto client = Files::Shares::ShareClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + EXPECT_NO_THROW(client.CreateIfNotExists()); + EXPECT_THROW(client.Create(), StorageException); + EXPECT_NO_THROW(client.DeleteIfExists()); + } + { + auto client = Files::Shares::ShareClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + auto created = client.Create()->Created; + EXPECT_TRUE(created); + auto createResult = client.CreateIfNotExists(); + EXPECT_FALSE(createResult->Created); + EXPECT_TRUE(createResult->ETag.empty()); + EXPECT_EQ(Core::DateTime(), createResult->LastModified); + auto deleted = client.Delete()->Deleted; + EXPECT_TRUE(deleted); + } + { + auto client = Files::Shares::ShareClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + } } TEST_F(FileShareClientTest, ShareMetadata) diff --git a/sdk/storage/azure-storage-files-shares/test/share_directory_client_test.cpp b/sdk/storage/azure-storage-files-shares/test/share_directory_client_test.cpp index c00ab84b7..4e1cbfdb7 100644 --- a/sdk/storage/azure-storage-files-shares/test/share_directory_client_test.cpp +++ b/sdk/storage/azure-storage-files-shares/test/share_directory_client_test.cpp @@ -100,6 +100,57 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(client.Create(), StorageException); } } + { + // CreateIfNotExists & DeleteIfExists. + { + auto client = m_shareClient->GetRootShareDirectoryClient().GetSubShareDirectoryClient( + LowercaseRandomString()); + EXPECT_NO_THROW(client.Create()); + EXPECT_NO_THROW(client.CreateIfNotExists()); + EXPECT_NO_THROW(client.Delete()); + EXPECT_NO_THROW(client.DeleteIfExists()); + } + { + auto client = m_shareClient->GetRootShareDirectoryClient().GetSubShareDirectoryClient( + LowercaseRandomString()); + EXPECT_NO_THROW(client.CreateIfNotExists()); + EXPECT_THROW(client.Create(), StorageException); + EXPECT_NO_THROW(client.DeleteIfExists()); + } + { + auto client = m_shareClient->GetRootShareDirectoryClient().GetSubShareDirectoryClient( + LowercaseRandomString()); + auto created = client.Create()->Created; + EXPECT_TRUE(created); + auto createResult = client.CreateIfNotExists(); + EXPECT_FALSE(createResult->Created); + EXPECT_TRUE(createResult->ETag.empty()); + EXPECT_EQ(Core::DateTime(), createResult->LastModified); + auto deleted = client.Delete()->Deleted; + EXPECT_TRUE(deleted); + } + { + auto client = m_shareClient->GetRootShareDirectoryClient().GetSubShareDirectoryClient( + LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + { + auto shareClient = Files::Shares::ShareClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + auto client = shareClient.GetRootShareDirectoryClient().GetSubShareDirectoryClient( + LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + { + auto client = m_shareClient->GetRootShareDirectoryClient() + .GetSubShareDirectoryClient(LowercaseRandomString()) + .GetSubShareDirectoryClient(LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + } } TEST_F(FileShareDirectoryClientTest, DirectoryMetadata) diff --git a/sdk/storage/azure-storage-files-shares/test/share_file_client_test.cpp b/sdk/storage/azure-storage-files-shares/test/share_file_client_test.cpp index 0bd00020e..aaf649a04 100644 --- a/sdk/storage/azure-storage-files-shares/test/share_file_client_test.cpp +++ b/sdk/storage/azure-storage-files-shares/test/share_file_client_test.cpp @@ -69,6 +69,37 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_NO_THROW(client.Create(1024)); } } + { + // DeleteIfExists. + { + auto client = m_shareClient->GetRootShareDirectoryClient().GetShareFileClient( + LowercaseRandomString()); + EXPECT_NO_THROW(client.Create(1024)); + EXPECT_NO_THROW(client.Delete()); + EXPECT_NO_THROW(client.DeleteIfExists()); + } + { + auto client = m_shareClient->GetRootShareDirectoryClient().GetShareFileClient( + LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + { + auto shareClient = Files::Shares::ShareClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + auto client = m_shareClient->GetRootShareDirectoryClient().GetShareFileClient( + LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + { + auto client = m_shareClient->GetRootShareDirectoryClient() + .GetSubShareDirectoryClient(LowercaseRandomString()) + .GetShareFileClient(LowercaseRandomString()); + auto deleteResult = client.DeleteIfExists(); + EXPECT_FALSE(deleteResult->Deleted); + } + } } TEST_F(FileShareFileClientTest, FileMetadata)