diff --git a/sdk/storage/inc/shares/protocol/share_rest_client.hpp b/sdk/storage/inc/shares/protocol/share_rest_client.hpp index fba4ac3c4..1a66f6cce 100644 --- a/sdk/storage/inc/shares/protocol/share_rest_client.hpp +++ b/sdk/storage/inc/shares/protocol/share_rest_client.hpp @@ -416,7 +416,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { }; // The retention policy. - struct RetentionPolicy + struct ShareRetentionPolicy { bool Enabled; // Indicates whether a retention policy is enabled for the File service. If false, // metrics data is retained, and the user is responsible for deleting it. @@ -432,7 +432,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { bool Enabled; // Indicates whether metrics are enabled for the File service. bool IncludeAPIs; // Indicates whether metrics should generate summary statistics for called API // operations. - RetentionPolicy ShareRetentionPolicy; + ShareRetentionPolicy RetentionPolicy; }; // An Azure Storage file range. @@ -1116,6 +1116,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto body = Azure::Core::Http::MemoryBodyStream( reinterpret_cast(xml_body.data()), xml_body.length()); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, &body); + request.AddHeader("Content-Length", std::to_string(body.Length())); request.AddQueryParameter(Details::c_QueryRestype, "service"); request.AddQueryParameter(Details::c_QueryComp, "properties"); if (setPropertiesOptions.Timeout.HasValue()) @@ -1240,10 +1241,10 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { } } - static void RetentionPolicyToXml(XmlWriter& writer, const RetentionPolicy& object) + static void ShareRetentionPolicyToXml(XmlWriter& writer, const ShareRetentionPolicy& object) { writer.Write(XmlNode{XmlNodeType::StartTag, "Enabled"}); - writer.Write(XmlNode{XmlNodeType::Text, nullptr, std::to_string(object.Enabled).data()}); + writer.Write(XmlNode{XmlNodeType::Text, nullptr, object.Enabled == 0 ? "false" : "true"}); writer.Write(XmlNode{XmlNodeType::EndTag}); writer.Write(XmlNode{XmlNodeType::StartTag, "Days"}); writer.Write(XmlNode{XmlNodeType::Text, nullptr, std::to_string(object.Days).data()}); @@ -1256,19 +1257,20 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { writer.Write(XmlNode{XmlNodeType::Text, nullptr, object.Version.data()}); writer.Write(XmlNode{XmlNodeType::EndTag}); writer.Write(XmlNode{XmlNodeType::StartTag, "Enabled"}); - writer.Write(XmlNode{XmlNodeType::Text, nullptr, std::to_string(object.Enabled).data()}); + writer.Write(XmlNode{XmlNodeType::Text, nullptr, object.Enabled == 0 ? "false" : "true"}); writer.Write(XmlNode{XmlNodeType::EndTag}); writer.Write(XmlNode{XmlNodeType::StartTag, "IncludeAPIs"}); writer.Write( - XmlNode{XmlNodeType::Text, nullptr, std::to_string(object.IncludeAPIs).data()}); + XmlNode{XmlNodeType::Text, nullptr, object.IncludeAPIs == 0 ? "false" : "true"}); writer.Write(XmlNode{XmlNodeType::EndTag}); writer.Write(XmlNode{XmlNodeType::StartTag, "RetentionPolicy"}); - RetentionPolicyToXml(writer, object.ShareRetentionPolicy); + ShareRetentionPolicyToXml(writer, object.RetentionPolicy); writer.Write(XmlNode{XmlNodeType::EndTag}); }; static void CorsRuleToXml(XmlWriter& writer, const CorsRule& object) { + writer.Write(XmlNode{XmlNodeType::StartTag, "CorsRule"}); writer.Write(XmlNode{XmlNodeType::StartTag, "AllowedOrigins"}); writer.Write(XmlNode{XmlNodeType::Text, nullptr, object.AllowedOrigins.data()}); writer.Write(XmlNode{XmlNodeType::EndTag}); @@ -1285,22 +1287,28 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { writer.Write( XmlNode{XmlNodeType::Text, nullptr, std::to_string(object.MaxAgeInSeconds).data()}); writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::EndTag}); }; static void StorageServicePropertiesToXml( XmlWriter& writer, const StorageServiceProperties& object) { + writer.Write(XmlNode{XmlNodeType::StartTag, "StorageServiceProperties"}); writer.Write(XmlNode{XmlNodeType::StartTag, "HourMetrics"}); MetricsToXml(writer, object.HourMetrics); writer.Write(XmlNode{XmlNodeType::EndTag}); writer.Write(XmlNode{XmlNodeType::StartTag, "MinuteMetrics"}); MetricsToXml(writer, object.MinuteMetrics); writer.Write(XmlNode{XmlNodeType::EndTag}); - writer.Write(XmlNode{XmlNodeType::StartTag, "Cors"}); - for (const auto& item : object.Cors) + if (object.Cors.size() > 0) { - CorsRuleToXml(writer, item); + writer.Write(XmlNode{XmlNodeType::StartTag, "Cors"}); + for (const auto& item : object.Cors) + { + CorsRuleToXml(writer, item); + } + writer.Write(XmlNode{XmlNodeType::EndTag}); } writer.Write(XmlNode{XmlNodeType::EndTag}); }; @@ -1328,9 +1336,9 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { } } - static RetentionPolicy RetentionPolicyFromXml(XmlReader& reader) + static ShareRetentionPolicy ShareRetentionPolicyFromXml(XmlReader& reader) { - auto result = RetentionPolicy(); + auto result = ShareRetentionPolicy(); enum class XmlTagName { c_Unknown, @@ -1445,7 +1453,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { if (path.size() == 1 && path[0] == XmlTagName::c_RetentionPolicy) { - result.ShareRetentionPolicy = RetentionPolicyFromXml(reader); + result.RetentionPolicy = ShareRetentionPolicyFromXml(reader); path.pop_back(); } } @@ -1564,6 +1572,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { c_StorageServiceProperties, c_HourMetrics, c_MinuteMetrics, + c_CorsRule, c_Cors, }; std::vector path; @@ -1601,6 +1610,10 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { path.emplace_back(XmlTagName::c_MinuteMetrics); } + else if (std::strcmp(node.Name, "CorsRule") == 0) + { + path.emplace_back(XmlTagName::c_CorsRule); + } else if (std::strcmp(node.Name, "Cors") == 0) { path.emplace_back(XmlTagName::c_Cors); @@ -1624,8 +1637,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { path.pop_back(); } else if ( - path.size() == 2 && path[0] == XmlTagName::c_StorageServiceProperties - && path[1] == XmlTagName::c_Cors) + path.size() == 3 && path[0] == XmlTagName::c_StorageServiceProperties + && path[1] == XmlTagName::c_Cors && path[2] == XmlTagName::c_CorsRule) { result.Cors.emplace_back(CorsRuleFromXml(reader)); path.pop_back(); @@ -2421,6 +2434,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto body = Azure::Core::Http::MemoryBodyStream( reinterpret_cast(xml_body.data()), xml_body.length()); auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, url, &body); + request.AddHeader("Content-Length", std::to_string(body.Length())); request.AddQueryParameter(Details::c_QueryRestype, "share"); request.AddQueryParameter(Details::c_QueryComp, "acl"); if (setAccessPolicyOptions.Timeout.HasValue()) @@ -2949,6 +2963,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { static void AccessPolicyToXml(XmlWriter& writer, const AccessPolicy& object) { + writer.Write(XmlNode{XmlNodeType::StartTag, "AccessPolicy"}); writer.Write(XmlNode{XmlNodeType::StartTag, "Start"}); writer.Write(XmlNode{XmlNodeType::Text, nullptr, object.Start.data()}); writer.Write(XmlNode{XmlNodeType::EndTag}); @@ -2958,16 +2973,19 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { writer.Write(XmlNode{XmlNodeType::StartTag, "Permission"}); writer.Write(XmlNode{XmlNodeType::Text, nullptr, object.Permission.data()}); writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::EndTag}); }; static void SignedIdentifierToXml(XmlWriter& writer, const SignedIdentifier& object) { + writer.Write(XmlNode{XmlNodeType::StartTag, "SignedIdentifier"}); writer.Write(XmlNode{XmlNodeType::StartTag, "Id"}); writer.Write(XmlNode{XmlNodeType::Text, nullptr, object.Id.data()}); writer.Write(XmlNode{XmlNodeType::EndTag}); writer.Write(XmlNode{XmlNodeType::StartTag, "AccessPolicy"}); AccessPolicyToXml(writer, object.Policy); writer.Write(XmlNode{XmlNodeType::EndTag}); + writer.Write(XmlNode{XmlNodeType::EndTag}); }; static void SignedIdentifiersToXml( diff --git a/sdk/storage/inc/shares/service_client.hpp b/sdk/storage/inc/shares/service_client.hpp index 2a07e0541..606cf6a5a 100644 --- a/sdk/storage/inc/shares/service_client.hpp +++ b/sdk/storage/inc/shares/service_client.hpp @@ -85,6 +85,24 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Azure::Core::Response ListSharesSegment( const ListSharesOptions& options = ListSharesOptions()) const; + /** + * @brief Set the service's properties. + * @param properties The properties of the service that is to be set. + * @param options Optional parameters to set the properties of the service. + * @return Azure::Core::Response The service's properties. + */ + Azure::Core::Response SetProperties( + StorageServiceProperties properties, + const SetServicePropertiesOptions& options = SetServicePropertiesOptions()) const; + + /** + * @brief Get the service's properties. + * @param options Optional parameters to get the properties of the service. + * @return Azure::Core::Response The service's properties. + */ + Azure::Core::Response GetProperties( + const GetServicePropertiesOptions& options = GetServicePropertiesOptions()) const; + private: UriBuilder m_serviceUri; std::shared_ptr m_pipeline; diff --git a/sdk/storage/inc/shares/share_options.hpp b/sdk/storage/inc/shares/share_options.hpp index 9fefb1276..e377c65de 100644 --- a/sdk/storage/inc/shares/share_options.hpp +++ b/sdk/storage/inc/shares/share_options.hpp @@ -66,6 +66,22 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Azure::Core::Nullable ListSharesInclude; }; + struct SetServicePropertiesOptions + { + /** + * @brief Context for cancelling long running operations. + */ + Azure::Core::Context Context; + }; + + struct GetServicePropertiesOptions + { + /** + * @brief Context for cancelling long running operations. + */ + Azure::Core::Context Context; + }; + struct CreateShareOptions { /** diff --git a/sdk/storage/inc/shares/share_responses.hpp b/sdk/storage/inc/shares/share_responses.hpp index 6880c88de..ee3e1904e 100644 --- a/sdk/storage/inc/shares/share_responses.hpp +++ b/sdk/storage/inc/shares/share_responses.hpp @@ -10,6 +10,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { // ServiceClient models: using ListSharesSegmentResult = ServiceListSharesSegmentResponse; + using SetServicePropertiesInfo = ServiceSetPropertiesResponse; // ShareClient models: using ShareInfo = ShareCreateResponse; diff --git a/sdk/storage/src/shares/service_client.cpp b/sdk/storage/src/shares/service_client.cpp index e145f3a20..adea54fd7 100644 --- a/sdk/storage/src/shares/service_client.cpp +++ b/sdk/storage/src/shares/service_client.cpp @@ -126,4 +126,28 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { m_serviceUri.ToString(), *m_pipeline, options.Context, protocolLayerOptions); } + Azure::Core::Response ServiceClient::SetProperties( + StorageServiceProperties properties, + const SetServicePropertiesOptions& options) const + { + auto protocolLayerOptions = ShareRestClient::Service::SetPropertiesOptions(); + protocolLayerOptions.ServiceProperties = std::move(properties); + return ShareRestClient::Service::SetProperties( + m_serviceUri.ToString(), *m_pipeline, options.Context, protocolLayerOptions); + } + + Azure::Core::Response ServiceClient::GetProperties( + const GetServicePropertiesOptions& options) const + { + auto protocolLayerOptions = ShareRestClient::Service::GetPropertiesOptions(); + auto result = ShareRestClient::Service::GetProperties( + m_serviceUri.ToString(), *m_pipeline, options.Context, protocolLayerOptions); + StorageServiceProperties ret; + ret.Cors = std::move(result->Cors); + ret.HourMetrics = std::move(result->HourMetrics); + ret.MinuteMetrics = std::move(result->MinuteMetrics); + return Azure::Core::Response( + std::move(ret), result.ExtractRawResponse()); + } + }}}} // namespace Azure::Storage::Files::Shares diff --git a/sdk/storage/test/shares/service_client_test.cpp b/sdk/storage/test/shares/service_client_test.cpp index da5b2fe9b..478d58978 100644 --- a/sdk/storage/test/shares/service_client_test.cpp +++ b/sdk/storage/test/shares/service_client_test.cpp @@ -4,6 +4,7 @@ #include "service_client_test.hpp" #include +#include namespace Azure { namespace Storage { namespace Test { @@ -124,4 +125,95 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(FileShareServiceClientTest, GetProperties) + { + auto ret = m_fileShareServiceClient->GetProperties(); + auto properties = *ret; + auto hourMetrics = properties.HourMetrics; + if (hourMetrics.Enabled) + { + EXPECT_FALSE(hourMetrics.Version.empty()); + } + auto minuteMetrics = properties.HourMetrics; + if (minuteMetrics.Enabled) + { + EXPECT_FALSE(minuteMetrics.Version.empty()); + } + } + + TEST_F(FileShareServiceClientTest, SetProperties) + { + auto properties = *m_fileShareServiceClient->GetProperties(); + auto originalProperties = properties; + + properties.HourMetrics.Enabled = true; + properties.HourMetrics.RetentionPolicy.Enabled = true; + properties.HourMetrics.RetentionPolicy.Days = 4; + properties.HourMetrics.IncludeAPIs = true; + + properties.MinuteMetrics.Enabled = true; + properties.MinuteMetrics.RetentionPolicy.Enabled = true; + properties.MinuteMetrics.RetentionPolicy.Days = 3; + properties.MinuteMetrics.IncludeAPIs = true; + + Files::Shares::CorsRule corsRule; + corsRule.AllowedOrigins = "http://www.example1.com"; + corsRule.AllowedMethods = "GET,PUT"; + corsRule.AllowedHeaders = "x-ms-header1,x-ms-header2"; + corsRule.ExposedHeaders = "x-ms-header3"; + corsRule.MaxAgeInSeconds = 10; + properties.Cors.emplace_back(corsRule); + + corsRule.AllowedOrigins = "http://www.example2.com"; + corsRule.AllowedMethods = "DELETE"; + corsRule.AllowedHeaders = "x-ms-header1"; + corsRule.ExposedHeaders = "x-ms-header2,x-ms-header3"; + corsRule.MaxAgeInSeconds = 20; + properties.Cors.emplace_back(corsRule); + + EXPECT_NO_THROW(m_fileShareServiceClient->SetProperties(properties)); + // It takes some time before the new properties comes into effect. + using namespace std::chrono_literals; + std::this_thread::sleep_for(10s); + auto downloadedProperties = *m_fileShareServiceClient->GetProperties(); + + EXPECT_EQ(downloadedProperties.HourMetrics.Version, properties.HourMetrics.Version); + EXPECT_EQ(downloadedProperties.HourMetrics.Enabled, properties.HourMetrics.Enabled); + EXPECT_EQ(downloadedProperties.HourMetrics.IncludeAPIs, properties.HourMetrics.IncludeAPIs); + EXPECT_EQ( + downloadedProperties.HourMetrics.RetentionPolicy.Enabled, + properties.HourMetrics.RetentionPolicy.Enabled); + EXPECT_EQ( + downloadedProperties.HourMetrics.RetentionPolicy.Days, + properties.HourMetrics.RetentionPolicy.Days); + + EXPECT_EQ(downloadedProperties.MinuteMetrics.Version, properties.MinuteMetrics.Version); + EXPECT_EQ(downloadedProperties.MinuteMetrics.Enabled, properties.MinuteMetrics.Enabled); + EXPECT_EQ(downloadedProperties.MinuteMetrics.IncludeAPIs, properties.MinuteMetrics.IncludeAPIs); + EXPECT_EQ( + downloadedProperties.MinuteMetrics.RetentionPolicy.Enabled, + properties.MinuteMetrics.RetentionPolicy.Enabled); + EXPECT_EQ( + downloadedProperties.MinuteMetrics.RetentionPolicy.Days, + properties.MinuteMetrics.RetentionPolicy.Days); + + EXPECT_EQ(downloadedProperties.Cors.size(), properties.Cors.size()); + for (const auto& cors : downloadedProperties.Cors) + { + auto iter = std::find_if( + properties.Cors.begin(), + properties.Cors.end(), + [&cors](const Files::Shares::CorsRule& rule) { + return rule.AllowedOrigins == cors.AllowedOrigins; + }); + EXPECT_EQ(iter->AllowedMethods, cors.AllowedMethods); + EXPECT_EQ(iter->AllowedHeaders, cors.AllowedHeaders); + EXPECT_EQ(iter->ExposedHeaders, cors.ExposedHeaders); + EXPECT_EQ(iter->MaxAgeInSeconds, cors.MaxAgeInSeconds); + EXPECT_NE(properties.Cors.end(), iter); + } + + m_fileShareServiceClient->SetProperties(originalProperties); + } + }}} // namespace Azure::Storage::Test