diff --git a/sdk/storage/azure-storage-files-datalake/CHANGELOG.md b/sdk/storage/azure-storage-files-datalake/CHANGELOG.md index 09ee9ff68..e936aa4d1 100644 --- a/sdk/storage/azure-storage-files-datalake/CHANGELOG.md +++ b/sdk/storage/azure-storage-files-datalake/CHANGELOG.md @@ -2,6 +2,10 @@ ## 12.0.0-beta.11 (Unreleased) +### New Features + +- Added `DataLakePathClient::SetAccessControlListRecursive()`, `UpdateAccessControlListRecursive()` and `RemoveAccessControlListRecursive()`. + ### Breaking Changes - Renamed `HasMorePages()` in paged response to `HasPage()`. diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp index 62e8fda7e..e7728d949 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp @@ -538,6 +538,42 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { PathAccessConditions AccessConditions; }; + /** + * @brief Optional parameters for DirectoryClient::SetAccessControlListRecursive. + */ + struct SetPathAccessControlListRecursiveOptions + { + /** + * @brief When performing setAccessControlRecursive on a directory, the number of paths that + * are processed with each invocation is limited. If the number of paths to be processed + * exceeds this limit, a continuation token is returned in this response header. When a + * continuation token is returned in the response, it must be specified in a subsequent + * invocation of the setAccessControlRecursive operation to continue the + * setAccessControlRecursive operation on the directory. + */ + Azure::Nullable ContinuationToken; + + /** + * @brief It specifies the maximum number of files or directories on which the acl change will + * be applied. If omitted or greater than 2,000, the request will process up to 2,000 + * items. + */ + Azure::Nullable PageSizeHint; + + /** + * @brief Optional. If set to false, the operation will terminate quickly on encountering user + * errors (4XX). If true, the operation will ignore user errors and proceed with the operation + * on other sub-entities of the directory. Continuation token will only be returned when + * ContinueOnFailure is true in case of user errors. If not set the default value is false for + * this. + */ + Azure::Nullable ContinueOnFailure; + }; + + using UpdatePathAccessControlListRecursiveOptions = SetPathAccessControlListRecursiveOptions; + + using RemovePathAccessControlListRecursiveOptions = SetPathAccessControlListRecursiveOptions; + using CreateFileOptions = CreatePathOptions; using CreateDirectoryOptions = CreatePathOptions; 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 e0f905e68..e9998ee7d 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 @@ -220,6 +220,72 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { const SetPathMetadataOptions& options = SetPathMetadataOptions(), const Azure::Core::Context& context = Azure::Core::Context()) const; + /** + * @brief Sets POSIX access control rights on files and directories under given directory + * recursively. + * @param acls Sets POSIX access control rights on files and directories. Each access control + * entry (ACE) consists of a scope, a type, a user or group identifier, and permissions. + * @param options Optional parameters to set an access control recursively to the resource the + * directory points to. + * @param context Context for cancelling long running operations. + * @return SetPathAccessControlListRecursivePagedResponse containing summary stats of the + * operation. + * @remark This request is sent to dfs endpoint. + */ + SetPathAccessControlListRecursivePagedResponse SetAccessControlListRecursive( + const std::vector& acls, + const SetPathAccessControlListRecursiveOptions& options + = SetPathAccessControlListRecursiveOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const + { + return SetAccessControlListRecursiveInternal( + _detail::PathSetAccessControlRecursiveMode::Set, acls, options, context); + } + + /** + * @brief Updates POSIX access control rights on files and directories under given directory + * recursively. + * @param acls Updates POSIX access control rights on files and directories. Each access control + * entry (ACE) consists of a scope, a type, a user or group identifier, and permissions. + * @param options Optional parameters to set an access control recursively to the resource the + * directory points to. + * @param context Context for cancelling long running operations. + * @return UpdatePathAccessControlListRecursivePagedResponse containing summary stats of the + * operation. + * @remark This request is sent to dfs endpoint. + */ + UpdatePathAccessControlListRecursivePagedResponse UpdateAccessControlListRecursive( + const std::vector& acls, + const UpdatePathAccessControlListRecursiveOptions& options + = UpdatePathAccessControlListRecursiveOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const + { + return SetAccessControlListRecursiveInternal( + _detail::PathSetAccessControlRecursiveMode::Modify, acls, options, context); + } + + /** + * @brief Removes POSIX access control rights on files and directories under given directory + * recursively. + * @param acls Removes POSIX access control rights on files and directories. Each access control + * entry (ACE) consists of a scope, a type, a user or group identifier, and permissions. + * @param options Optional parameters to set an access control recursively to the resource the + * directory points to. + * @param context Context for cancelling long running operations. + * @return RemovePathAccessControlListRecursivePagedResponse containing summary stats of the + * operation. + * @remark This request is sent to dfs endpoint. + */ + RemovePathAccessControlListRecursivePagedResponse RemoveAccessControlListRecursive( + const std::vector& acls, + const RemovePathAccessControlListRecursiveOptions& options + = RemovePathAccessControlListRecursiveOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const + { + return SetAccessControlListRecursiveInternal( + _detail::PathSetAccessControlRecursiveMode::Remove, acls, options, context); + } + protected: Azure::Core::Url m_pathUrl; Blobs::BlobClient m_blobClient; @@ -235,6 +301,12 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { } private: + SetPathAccessControlListRecursivePagedResponse SetAccessControlListRecursiveInternal( + _detail::PathSetAccessControlRecursiveMode mode, + const std::vector& acls, + const SetPathAccessControlListRecursiveOptions& options, + const Azure::Core::Context& context) const; + friend class DataLakeFileSystemClient; friend class DataLakeLeaseClient; }; 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 a91247050..5cb710bb0 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 @@ -284,4 +284,29 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { friend class PagedResponse; }; + class SetPathAccessControlListRecursivePagedResponse + : public Azure::Core::PagedResponse { + public: + int32_t NumberOfSuccessfulDirectories = 0; + int32_t NumberOfSuccessfulFiles = 0; + int32_t NumberOfFailures = 0; + std::vector FailedEntries; + + private: + void OnNextPage(const Azure::Core::Context& context); + + std::shared_ptr m_dataLakePathClient; + SetPathAccessControlListRecursiveOptions m_operationOptions; + std::vector m_acls; + _detail::PathSetAccessControlRecursiveMode m_mode; + + friend class DataLakePathClient; + friend class PagedResponse; + }; + + using UpdatePathAccessControlListRecursivePagedResponse + = SetPathAccessControlListRecursivePagedResponse; + using RemovePathAccessControlListRecursivePagedResponse + = SetPathAccessControlListRecursivePagedResponse; + }}}} // namespace Azure::Storage::Files::DataLake 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 e13915811..039a109b3 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 @@ -426,4 +426,36 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { std::move(ret), std::move(result.RawResponse)); } + SetPathAccessControlListRecursivePagedResponse + DataLakePathClient::SetAccessControlListRecursiveInternal( + _detail::PathSetAccessControlRecursiveMode mode, + const std::vector& acls, + const SetPathAccessControlListRecursiveOptions& options, + const Azure::Core::Context& context) const + { + _detail::DataLakeRestClient::Path::SetAccessControlRecursiveOptions protocolLayerOptions; + protocolLayerOptions.Mode = mode; + protocolLayerOptions.ContinuationToken = options.ContinuationToken; + protocolLayerOptions.MaxRecords = options.PageSizeHint; + protocolLayerOptions.ForceFlag = options.ContinueOnFailure; + protocolLayerOptions.Acl = Models::Acl::SerializeAcls(acls); + auto response = _detail::DataLakeRestClient::Path::SetAccessControlRecursive( + m_pathUrl, *m_pipeline, context, protocolLayerOptions); + + SetPathAccessControlListRecursivePagedResponse pagedResponse; + pagedResponse.NumberOfSuccessfulFiles = response.Value.NumberOfSuccessfulDirectories; + pagedResponse.NumberOfSuccessfulFiles = response.Value.NumberOfSuccessfulFiles; + pagedResponse.NumberOfFailures = response.Value.NumberOfFailures; + pagedResponse.FailedEntries = std::move(response.Value.FailedEntries); + pagedResponse.m_dataLakePathClient = std::make_shared(*this); + pagedResponse.m_operationOptions = options; + pagedResponse.m_acls = acls; + pagedResponse.m_mode = mode; + pagedResponse.CurrentPageToken = options.ContinuationToken.ValueOr(std::string()); + pagedResponse.NextPageToken = response.Value.ContinuationToken; + pagedResponse.RawResponse = std::move(response.RawResponse); + + return pagedResponse; + } + }}}} // namespace Azure::Storage::Files::DataLake diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_responses.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_responses.cpp index ddfb5b090..0433b12b8 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_responses.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_responses.cpp @@ -92,4 +92,27 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { *this = m_onNextPageFunc(NextPageToken.Value(), context); } + void SetPathAccessControlListRecursivePagedResponse::OnNextPage( + const Azure::Core::Context& context) + { + m_operationOptions.ContinuationToken = NextPageToken; + if (m_mode == _detail::PathSetAccessControlRecursiveMode::Set) + { + *this = m_dataLakePathClient->SetAccessControlListRecursive( + m_acls, m_operationOptions, context); + } + else if (m_mode == _detail::PathSetAccessControlRecursiveMode::Modify) + { + *this = m_dataLakePathClient->UpdateAccessControlListRecursive( + m_acls, m_operationOptions, context); + } + else if (m_mode == _detail::PathSetAccessControlRecursiveMode::Remove) + { + *this = m_dataLakePathClient->RemoveAccessControlListRecursive( + m_acls, m_operationOptions, context); + } + // Execution is never expected to reach here + std::abort(); + } + }}}} // namespace Azure::Storage::Files::DataLake 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 aa73897e4..8d76649de 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 @@ -364,6 +364,274 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(DataLakeDirectoryClientTest, DirectoryAccessControlRecursive) + { + // Setup directories. + auto rootDirectoryName = RandomString(); + auto directoryName1 = RandomString(); + auto directoryName2 = RandomString(); + auto rootDirectoryClient = m_fileSystemClient->GetDirectoryClient(rootDirectoryName); + rootDirectoryClient.Create(); + auto directoryClient1 + = m_fileSystemClient->GetDirectoryClient(rootDirectoryName + "/" + directoryName1); + directoryClient1.Create(); + auto directoryClient2 + = m_fileSystemClient->GetDirectoryClient(rootDirectoryName + "/" + directoryName2); + directoryClient2.Create(); + + { + // Set Acls recursive. + std::vector acls = GetValidAcls(); + EXPECT_NO_THROW(rootDirectoryClient.SetAccessControlListRecursive(acls)); + std::vector resultAcls1; + std::vector resultAcls2; + EXPECT_NO_THROW(resultAcls1 = directoryClient1.GetAccessControlList().Value.Acls); + EXPECT_NO_THROW(resultAcls2 = directoryClient2.GetAccessControlList().Value.Acls); + for (const auto& acl : resultAcls2) + { + auto iter = std::find_if( + resultAcls1.begin(), + resultAcls1.end(), + [&acl](const Files::DataLake::Models::Acl& targetAcl) { + return (targetAcl.Type == acl.Type) && (targetAcl.Id == acl.Id) + && (targetAcl.Scope == acl.Scope); + }); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ(iter->Permissions, acl.Permissions); + } + } + { + // Update Acls recursive. + std::vector originalAcls = GetValidAcls(); + Files::DataLake::Models::Acl newAcl; + newAcl.Type = "group"; + newAcl.Id = ""; + newAcl.Permissions = "rw-"; + std::vector acls; + acls.emplace_back(std::move(newAcl)); + EXPECT_NO_THROW(rootDirectoryClient.UpdateAccessControlListRecursive(acls)); + std::vector resultAcls1; + std::vector resultAcls2; + EXPECT_NO_THROW(resultAcls1 = directoryClient1.GetAccessControlList().Value.Acls); + EXPECT_NO_THROW(resultAcls2 = directoryClient2.GetAccessControlList().Value.Acls); + for (const auto& acl : resultAcls2) + { + auto iter = std::find_if( + resultAcls1.begin(), + resultAcls1.end(), + [&acl](const Files::DataLake::Models::Acl& targetAcl) { + return (targetAcl.Type == acl.Type) && (targetAcl.Id == acl.Id) + && (targetAcl.Scope == acl.Scope); + }); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ(iter->Permissions, acl.Permissions); + } + { + // verify group has changed + auto groupFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "group"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), groupFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ("rw-", iter->Permissions); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), groupFinder); + EXPECT_TRUE(iter != resultAcls2.end()); + EXPECT_EQ("rw-", iter->Permissions); + } + { + // verify other has not changed + { + auto otherFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "other"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), otherFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ(originalAcls[3].Permissions, iter->Permissions); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), otherFinder); + EXPECT_TRUE(iter != resultAcls2.end()); + EXPECT_EQ(originalAcls[3].Permissions, iter->Permissions); + } + { + auto userFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "user"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), userFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + if (iter->Id == originalAcls[0].Id) + { + EXPECT_EQ(originalAcls[0].Permissions, iter->Permissions); + } + else + { + EXPECT_EQ(originalAcls[1].Permissions, iter->Permissions); + } + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), userFinder); + EXPECT_TRUE(iter != resultAcls2.end()); + if (iter->Id == originalAcls[0].Id) + { + EXPECT_EQ(originalAcls[0].Permissions, iter->Permissions); + } + else + { + EXPECT_EQ(originalAcls[1].Permissions, iter->Permissions); + } + } + } + } + { + // Remove Acls recursive. + std::vector originalAcls = GetValidAcls(); + Files::DataLake::Models::Acl removeAcl; + removeAcl.Type = "user"; + removeAcl.Id = "72a3f86f-271f-439e-b031-25678907d381"; + std::vector acls; + acls.emplace_back(std::move(removeAcl)); + EXPECT_NO_THROW(rootDirectoryClient.RemoveAccessControlListRecursive(acls)); + std::vector resultAcls1; + std::vector resultAcls2; + EXPECT_NO_THROW(resultAcls1 = directoryClient1.GetAccessControlList().Value.Acls); + EXPECT_NO_THROW(resultAcls2 = directoryClient2.GetAccessControlList().Value.Acls); + for (const auto& acl : resultAcls2) + { + auto iter = std::find_if( + resultAcls1.begin(), + resultAcls1.end(), + [&acl](const Files::DataLake::Models::Acl& targetAcl) { + return (targetAcl.Type == acl.Type) && (targetAcl.Id == acl.Id) + && (targetAcl.Scope == acl.Scope); + }); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ(iter->Permissions, acl.Permissions); + } + { + // verify group policy has been removed. + auto userFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "user" && targetAcl.Id == "72a3f86f-271f-439e-b031-25678907d381"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), userFinder); + EXPECT_TRUE(iter == resultAcls1.end()); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), userFinder); + EXPECT_TRUE(iter == resultAcls2.end()); + } + { + // verify other has not changed + { + auto otherFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "other"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), otherFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ(originalAcls[3].Permissions, iter->Permissions); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), otherFinder); + EXPECT_TRUE(iter != resultAcls2.end()); + EXPECT_EQ(originalAcls[3].Permissions, iter->Permissions); + } + { + auto userFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "user"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), userFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ(originalAcls[1].Id, iter->Id); + EXPECT_EQ(originalAcls[1].Permissions, iter->Permissions); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), userFinder); + EXPECT_TRUE(iter != resultAcls2.end()); + EXPECT_EQ(originalAcls[1].Id, iter->Id); + EXPECT_EQ(originalAcls[1].Permissions, iter->Permissions); + } + } + } + { + // Set Acls recursive, with new set of acls + std::vector acls; + { + Files::DataLake::Models::Acl newAcl; + newAcl.Type = "user"; + newAcl.Permissions = "rw-"; + acls.emplace_back(std::move(newAcl)); + } + { + Files::DataLake::Models::Acl newAcl; + newAcl.Type = "group"; + newAcl.Permissions = "rw-"; + acls.emplace_back(std::move(newAcl)); + } + { + Files::DataLake::Models::Acl newAcl; + newAcl.Type = "other"; + newAcl.Permissions = "rw-"; + acls.emplace_back(std::move(newAcl)); + } + (rootDirectoryClient.SetAccessControlListRecursive(acls)); + std::vector resultAcls1; + std::vector resultAcls2; + EXPECT_NO_THROW(resultAcls1 = directoryClient1.GetAccessControlList().Value.Acls); + EXPECT_NO_THROW(resultAcls2 = directoryClient2.GetAccessControlList().Value.Acls); + for (const auto& acl : resultAcls2) + { + auto iter = std::find_if( + resultAcls1.begin(), + resultAcls1.end(), + [&acl](const Files::DataLake::Models::Acl& targetAcl) { + return (targetAcl.Type == acl.Type) && (targetAcl.Id == acl.Id) + && (targetAcl.Scope == acl.Scope); + }); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ(iter->Permissions, acl.Permissions); + } + { + // verify group has changed + auto groupFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "group"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), groupFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ("rw-", iter->Permissions); + EXPECT_EQ("", iter->Id); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), groupFinder); + EXPECT_EQ("rw-", iter->Permissions); + EXPECT_EQ("", iter->Id); + } + { + // verify other has changed + auto otherFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "other"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), otherFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ("rw-", iter->Permissions); + EXPECT_EQ("", iter->Id); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), otherFinder); + EXPECT_EQ("rw-", iter->Permissions); + EXPECT_EQ("", iter->Id); + } + { + // verify user has only one entry + std::vector originalAcls = GetValidAcls(); + auto userFinder = [&originalAcls](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "user" && targetAcl.Id == originalAcls[0].Id; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), userFinder); + EXPECT_TRUE(iter == resultAcls1.end()); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), userFinder); + EXPECT_TRUE(iter == resultAcls2.end()); + } + { + // verify user has changed + auto userFinder = [](const Files::DataLake::Models::Acl& targetAcl) { + return targetAcl.Type == "user"; + }; + auto iter = std::find_if(resultAcls1.begin(), resultAcls1.end(), userFinder); + EXPECT_TRUE(iter != resultAcls1.end()); + EXPECT_EQ("rw-", iter->Permissions); + EXPECT_EQ("", iter->Id); + iter = std::find_if(resultAcls2.begin(), resultAcls2.end(), userFinder); + EXPECT_EQ("rw-", iter->Permissions); + EXPECT_EQ("", iter->Id); + } + } + } + TEST_F(DataLakeDirectoryClientTest, ConstructorsWorks) { { diff --git a/sdk/storage/azure-storage-files-shares/CHANGELOG.md b/sdk/storage/azure-storage-files-shares/CHANGELOG.md index 2d32467ba..af6f256d4 100644 --- a/sdk/storage/azure-storage-files-shares/CHANGELOG.md +++ b/sdk/storage/azure-storage-files-shares/CHANGELOG.md @@ -2,6 +2,10 @@ ## 12.0.0-beta.11 (Unreleased) +### New Features + +- Added `ShareDirectoryClient::ForceCloseAllHandles()` and `ShareFileClient::ForceCloseAllHandles()`. + ### Breaking Changes - Renamed `HasMorePages()` in paged response to `HasPage()`. 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 0632f2e7e..63a8efba3 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 @@ -212,6 +212,19 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { const ForceCloseDirectoryHandleOptions& options = ForceCloseDirectoryHandleOptions(), const Azure::Core::Context& context = Azure::Core::Context()) const; + /** + * @brief Closes all handles opened on a directory at the service. Optionally supports + * recursively closing handles on subresources. + * @param options Optional parameters to close all this directory's open handles. + * @param context Context for cancelling long running operations. + * @return ForceCloseAllDirectoryHandlesPagedResponse containing the information of the closed + * handles. + */ + ForceCloseAllDirectoryHandlesPagedResponse ForceCloseAllHandles( + const ForceCloseAllDirectoryHandlesOptions& options + = ForceCloseAllDirectoryHandlesOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const; + private: Azure::Core::Url m_shareDirectoryUrl; std::shared_ptr m_pipeline; 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 bce8fa519..54c3624cb 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 @@ -333,6 +333,17 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { const ForceCloseFileHandleOptions& options = ForceCloseFileHandleOptions(), const Azure::Core::Context& context = Azure::Core::Context()) const; + /** + * @brief Closes all handles opened on a file at the service. + * @param options Optional parameters to close all this file's open handles. + * @param context Context for cancelling long running operations. + * @return ForceCloseAllFileHandlesPagedResponse containing the information of the closed + * handles. + */ + ForceCloseAllFileHandlesPagedResponse ForceCloseAllHandles( + const ForceCloseAllFileHandlesOptions& options = ForceCloseAllFileHandlesOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const; + /** * @brief Upload a range from the source URI to this file's specific range. * @param destinationOffset Specifies the starting offset for the content to be written. diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp index b10eceffd..742ec1362 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp @@ -27,7 +27,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { }; /** - * @brief Optional parameters for #Azure::Storage::Files::Shares::ShareServiceClient::ListShares. + * @brief Optional parameters for + * #Azure::Storage::Files::Shares::ShareServiceClient::ListShares. */ struct ListSharesOptions { @@ -40,8 +41,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { /** * A string value that identifies the portion of the list to be returned with the next * list operation. The operation returns a marker value within the response body if the list - * returned was not complete. The marker value may then be used in a subsequent call to request - * the next set of list items. The marker value is opaque to the client. + * returned was not complete. The marker value may then be used in a subsequent call to + * request the next set of list items. The marker value is opaque to the client. */ Azure::Nullable ContinuationToken; @@ -259,8 +260,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { /** * A string value that identifies the portion of the list to be returned with the next * list operation. The operation returns a marker value within the response body if the list - * returned was not complete. The marker value may then be used in a subsequent call to request - * the next set of list items. The marker value is opaque to the client. + * returned was not complete. The marker value may then be used in a subsequent call to + * request the next set of list items. The marker value is opaque to the client. */ Azure::Nullable ContinuationToken; @@ -281,8 +282,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { /** * A string value that identifies the portion of the list to be returned with the next * list operation. The operation returns a marker value within the response body if the list - * returned was not complete. The marker value may then be used in a subsequent call to request - * the next set of list items. The marker value is opaque to the client. + * returned was not complete. The marker value may then be used in a subsequent call to + * request the next set of list items. The marker value is opaque to the client. */ Azure::Nullable ContinuationToken; @@ -308,6 +309,27 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { }; + /** + * @brief Optional parameters for + * #Azure::Storage::Files::Shares::ShareDirectoryClient::ForceCloseAllHandles. + */ + struct ForceCloseAllDirectoryHandlesOptions + { + /** + * A string value that identifies the portion of the list to be returned with the next + * close operation. The operation returns a marker value within the response body if the force + * close was not complete. The marker value may then be used in a subsequent call to + * close the next handle. The marker value is opaque to the client. + */ + Azure::Nullable ContinuationToken; + + /** + * @brief Specifies operation should apply to the directory specified in the URI, its files, its + * subdirectories and their files. + */ + Azure::Nullable Recursive; + }; + /** * @brief Optional parameters for #Azure::Storage::Files::Shares::ShareFileClient::Create. */ @@ -571,6 +593,21 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { }; + /** + * @brief Optional parameters for + * #Azure::Storage::Files::Shares::ShareFileClient::ForceCloseAllHandles. + */ + struct ForceCloseAllFileHandlesOptions + { + /** + * A string value that identifies the portion of the list to be returned with the next + * close operation. The operation returns a marker value within the response body if the force + * close was not complete. The marker value may then be used in a subsequent call to + * close the next handle. The marker value is opaque to the client. + */ + Azure::Nullable ContinuationToken; + }; + /** * @brief Optional parameters for #Azure::Storage::Files::Shares::ShareFileClient::DownloadTo. */ 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 2b265a4d1..f94072bbc 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 @@ -244,6 +244,22 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { friend class PagedResponse; }; + class ForceCloseAllFileHandlesPagedResponse + : public Azure::Core::PagedResponse { + public: + int32_t NumberOfHandlesClosed = 0; + int32_t NumberOfHandlesFailedToClose = 0; + + private: + void OnNextPage(const Azure::Core::Context& context); + + std::shared_ptr m_shareFileClient; + ForceCloseAllFileHandlesOptions m_operationOptions; + + friend class ShareFileClient; + friend class PagedResponse; + }; + class ListDirectoryHandlesPagedResponse : public Azure::Core::PagedResponse { public: @@ -259,4 +275,20 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { friend class PagedResponse; }; + class ForceCloseAllDirectoryHandlesPagedResponse + : public Azure::Core::PagedResponse { + public: + int32_t NumberOfHandlesClosed = 0; + int32_t NumberOfHandlesFailedToClose = 0; + + private: + void OnNextPage(const Azure::Core::Context& context); + + std::shared_ptr m_shareDirectoryClient; + ForceCloseAllDirectoryHandlesOptions m_operationOptions; + + friend class ShareDirectoryClient; + friend class PagedResponse; + }; + }}}} // namespace Azure::Storage::Files::Shares 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 19bd853da..f9901e46a 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 @@ -361,4 +361,31 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { std::move(ret), std::move(result.RawResponse)); } + ForceCloseAllDirectoryHandlesPagedResponse ShareDirectoryClient::ForceCloseAllHandles( + const ForceCloseAllDirectoryHandlesOptions& options, + const Azure::Core::Context& context) const + { + auto protocolLayerOptions = _detail::ShareRestClient::Directory::ForceCloseHandlesOptions(); + protocolLayerOptions.HandleId = FileAllHandles; + if (options.ContinuationToken.HasValue() && !options.ContinuationToken.Value().empty()) + { + protocolLayerOptions.ContinuationToken = options.ContinuationToken; + } + protocolLayerOptions.Recursive = options.Recursive; + auto response = _detail::ShareRestClient::Directory::ForceCloseHandles( + m_shareDirectoryUrl, *m_pipeline, context, protocolLayerOptions); + + ForceCloseAllDirectoryHandlesPagedResponse pagedResponse; + + pagedResponse.NumberOfHandlesClosed = response.Value.NumberOfHandlesClosed; + pagedResponse.NumberOfHandlesFailedToClose = response.Value.NumberOfHandlesFailedToClose; + pagedResponse.m_shareDirectoryClient = std::make_shared(*this); + pagedResponse.m_operationOptions = options; + pagedResponse.CurrentPageToken = options.ContinuationToken.ValueOr(std::string()); + pagedResponse.NextPageToken = response.Value.ContinuationToken.ValueOr(std::string()); + pagedResponse.RawResponse = std::move(response.RawResponse); + + return pagedResponse; + } + }}}} // namespace Azure::Storage::Files::Shares 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 1b97b72ec..07f6996a9 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 @@ -621,6 +621,32 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Models::ForceCloseFileHandleResult(), std::move(result.RawResponse)); } + ForceCloseAllFileHandlesPagedResponse ShareFileClient::ForceCloseAllHandles( + const ForceCloseAllFileHandlesOptions& options, + const Azure::Core::Context& context) const + { + auto protocolLayerOptions = _detail::ShareRestClient::File::ForceCloseHandlesOptions(); + protocolLayerOptions.HandleId = FileAllHandles; + if (options.ContinuationToken.HasValue() && !options.ContinuationToken.Value().empty()) + { + protocolLayerOptions.ContinuationToken = options.ContinuationToken; + } + auto response = _detail::ShareRestClient::File::ForceCloseHandles( + m_shareFileUrl, *m_pipeline, context, protocolLayerOptions); + + ForceCloseAllFileHandlesPagedResponse pagedResponse; + + pagedResponse.NumberOfHandlesClosed = response.Value.NumberOfHandlesClosed; + pagedResponse.NumberOfHandlesFailedToClose = response.Value.NumberOfHandlesFailedToClose; + pagedResponse.m_shareFileClient = std::make_shared(*this); + pagedResponse.m_operationOptions = options; + pagedResponse.CurrentPageToken = options.ContinuationToken.ValueOr(std::string()); + pagedResponse.NextPageToken = response.Value.ContinuationToken.ValueOr(std::string()); + pagedResponse.RawResponse = std::move(response.RawResponse); + + return pagedResponse; + } + Azure::Response ShareFileClient::DownloadTo( uint8_t* buffer, std::size_t bufferSize, diff --git a/sdk/storage/azure-storage-files-shares/src/share_responses.cpp b/sdk/storage/azure-storage-files-shares/src/share_responses.cpp index 9646bf473..b2ab56e3e 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_responses.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_responses.cpp @@ -79,10 +79,22 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { *this = m_shareFileClient->ListHandles(m_operationOptions, context); } + void ForceCloseAllFileHandlesPagedResponse::OnNextPage(const Azure::Core::Context& context) + { + m_operationOptions.ContinuationToken = NextPageToken; + *this = m_shareFileClient->ForceCloseAllHandles(m_operationOptions, context); + } + void ListDirectoryHandlesPagedResponse::OnNextPage(const Azure::Core::Context& context) { m_operationOptions.ContinuationToken = NextPageToken; *this = m_shareDirectoryClient->ListHandles(m_operationOptions, context); } + void ForceCloseAllDirectoryHandlesPagedResponse::OnNextPage(const Azure::Core::Context& context) + { + m_operationOptions.ContinuationToken = NextPageToken; + *this = m_shareDirectoryClient->ForceCloseAllHandles(m_operationOptions, context); + } + }}}} // namespace Azure::Storage::Files::Shares 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 b89230ced..6507f2cc9 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 @@ -440,5 +440,10 @@ namespace Azure { namespace Storage { namespace Test { pageResult.MoveToNextPage()) { } + EXPECT_NO_THROW(m_fileShareDirectoryClient->ForceCloseAllHandles()); + for (auto pageResult = m_fileShareDirectoryClient->ForceCloseAllHandles(); pageResult.HasPage(); + pageResult.MoveToNextPage()) + { + } } }}} // namespace Azure::Storage::Test 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 b24e3d45d..37171a774 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 @@ -268,6 +268,13 @@ namespace Azure { namespace Storage { namespace Test { pageResult.MoveToNextPage()) { } + + EXPECT_NO_THROW(m_fileClient->ForceCloseAllHandles()); + + for (auto pageResult = m_fileClient->ForceCloseAllHandles(); pageResult.HasPage(); + pageResult.MoveToNextPage()) + { + } } TEST_F(FileShareFileClientTest, LeaseRelated)