Freigeben über


Windows Azure Blobs: Improved HTTP Headers for Resume on Download and a Change in If-Match Conditions

In the new 2011-08-18 version of the Windows Azure Blob service, we have made some changes to improve browser download and streaming for some media players. We also provided an extension to Blob Service settings to allow anonymous and un-versioned requests to benefit from these changes. The motivation to provide these features are:

  1. Allow browsers to resume download if interrupted. Some browsers require the following:
    • ETag returned as part of the response must be quoted to conform to the HTTP spec.
    • Return Accept-Ranges in the response header to indicate that range requests are accepted by the service. Though this is not mandatory according to the spec, some browsers still require this.
  2. Support more range formats for range requests. Certain media players request a range of format “Range:bytes=0-“. The Windows Azure Blob service use to ignore this header format. Now, with the new 2011-08-18 version, we will return the entire blob in the format of a range response. This allows such media players to resume playing as soon as response packets arrive rather than waiting for the entire blob to download.
  3. Allow un-versioned requests to be processed using semantics of 2011-08-18 version. Since the above two changes impact un-versioned browser/media player requests and the changes made are versioned, we need to allow such requests to take advantage of the changes made. To allow un-versioned requests to be processed using semantics of 2011-08-18 version, we now take an extra property in “Set Blob Service Properties”, which makes it possible to define the default version for the blob service to use for un-versioned requests to your account.

In addition, another change for the blob service is that we now return “Pre-Condition Failure” (412) for PUT if you do a PUT request with conditional If-Match and the blob does not exist. Previously, we would have recreated this blob. This change is effective for all versions starting with 2009-09-19 version.

We will now cover the changes in more detail.

In this section we will cover the header related changes that we have done in the Windows Azure Blob service for 2011-08-18 version.

Quoted ETags

ETags returned in response headers for all APIs are now quoted to conform to RFC 2616 specification. ETags returned in the listing operations as part of XML in response body will remain as is. As mentioned above, this allows browsers to resume download using the ETag. Unquoted ETags were ignored by certain browsers, while all standards-compliant browsers honor quoted ETags. ETags are required by a browser when using a conditional Range GET to resume a download on the blob and it needs to ensure that the partial content it is requesting has not been modified.

With version 2011-08-18, we now support this header format.

Sample GET Blob ResponseHTTP/1.1 200 OK

 x-ms-blob-type: BlockBlob
x-ms-lease-status: unlocked
x-ms-meta-m1: v1
x-ms-meta-m2: v2
Content-Length: 11
Content-Type: text/plain; charset=UTF-8
Date: Sun, 25 Sep 2011 22:49:18 GMT
ETag: “0x8CB171DBEAD6A6B”
Last-Modified: Sun, 25 Sep 2011 22:48:29 GMT
x-ms-version: 2011-08-18
Accept-Ranges: bytes
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0

 

Return Accept-Ranges Header

Get Blob” requests now return “Accept-Ranges” in response. Though clients should not expect this header to infer that range requests are allowed or not, certain browsers still expect it. For those browsers, if this header is missing, an interrupted download will resume from the beginning rather than resuming from where it was interrupted.

With version 2011-08-18, we now support this header format. The sample REST request above also shows the presence of this new header.

Additional Range Format

Certain media players issue a range request for the entire blob using the format:

Range: bytes=0-

It expects a status code of 206 (i.e. Partial Content) with entire content being returned and Content-Range header set to:

Content-Range: bytes 0-10240779/10240780 (assuming the blob was of length 10240780).

In receiving the Content-Range, the media player would then start streaming the blob rather than waiting for the entire blob to be downloaded first.

With version 2011-08-18, we now support this header format.

Sample Range GET Blob Request
 GET https://cohowinery.blob.core.windows.net/videos/build.wmv?timeout=60 HTTP/1.1
User-Agent: WA-Storage/6.0.6002.18312
Range: bytes=100-Host:10.200.30.18

Sample Range GET Blob Repsonse
 HTTP/1.1 206 Partial Content
Content-Length: 1048476
Content-Type: application/octet-stream
Content-Range: bytes 100-1048575/1048576
Last-Modified: Thu, 08 Sep 2011 23:39:47 GMT
Accept-Ranges: bytes
ETag: "0x8CE4217E34E31F0"
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 387a38ae-fa0c-4fe2-8e60-d6afa2373e56
x-ms-version: 2011-08-18
x-ms-lease-status: unlocked
x-ms-blob-type: BlockBlob
Date: Thu, 08 Sep 2011 23:39:46 GMT

<content …>

If-Match Condition on Non-Existent Blob

PUT Blob API with If-Match precondition set to a value other than “*” would have succeeded before, even when the blob did not exist. This should not have succeeded, since it violates the HTTP specification. Therefore, we changed this to return “Precondition failed” (i.e. HTTP Status 412). The breaking change was done to prevent users from inadvertently recreating a deleted blob. This should not impact your service since:

  1. If the application really intendeds to create the blob, then it will send a PUT request without a ETag, since providing the ETag shows that the caller expects the blob to exist.
  2. If an application sends an ETag, then the intent to just update is made explicit – so if a blob does not exist, the request must fail.
  3. The current behavior is unexpected since we recreate the blob when the intent was to just update it. Because of these semantics, no application should be expecting the blob to be recreated.

We have made this change effective in all versions starting with 2009-09-19.

Blob Service Settings and DefaultServiceVersion

Before we get into the changes to the blob service settings, let us understand how versioning works in Windows Azure Storage. The Windows Azure Storage Service accepts the version that should be used to process the request in a “x-ms-version” header. The list of supported versions is explained here. However, in certain cases, versions can be omitted:

  1. Anonymous browser requests do not send a version header since there is no way to add this custom header
  2. The PDC 2008 version of the request also did not require a version header

When requests do not have the version header associated, we call it un-versioned requests. However, the service still needs to associate a version with these requests and the rule was as follows:

  1. If a request is versioned, then we use the version specified in the request header
  2. If the version header is not set, then if the ACL for the blob container was set using version 2009-09-19 or later, we will use the 2009-09-19 version to execute the API
  3. Otherwise we will use the PDC 2008 version of the API (which will be deprecated in the future)

Because of the above rules, the above changes such as ETag, Accept-Ranges header etc, done for un-versioned requests would not have taken effect for the intended scenarios (e.g., anonymous requests). Hence, we now allow a DefaultServiceVersion property that can be set for the blob service for your storage account. This is used only for un-versioned requests and the new version precedence rules for requests are:

  1. If a request is versioned, then we use the version specified in the request header
  2. If a version header is not present and the user has set DefaultServiceVersion in “Set Blob Service Properties”to a valid version (2009-09-19 or 2011-08-18)”, then we will use that default version for this request.
  3. If the version header is not set (explicitly or via the DefaultServiceVersion property), then if the ACL for the container was set using version 2009-09-19 or later, we will use 2009-09-19 version to execute the API
  4. Otherwise, we will use the PDC 2008 version of the API, which will be deprecated in the future.

For users who are targeting their blobs to be downloaded via browsers or media players, we recommend setting this default service version to 2011-08-18 so that the improvements can take effect. We also recommend setting this for your service, since we will be deprecating the PDC 2008 version at some point in the future.

Set DefaultServiceVersion property

The existing “Set Blob Service Properties” has been extended in 2011-08-18 version to include a new DefaultServiceVersion property. This is an optional property and accepted only if it is set to a valid version value. It only applies to the Windows Azure Blob service. The possible values are:

  • 2009-09-19
  • 2011-08-18

When set, this version is used for all un-versioned requests. Please note that the “Set Blob Service Properties” request to set DefaultServiceVersion must be made with version 2011-08-18, regardless of which version you are setting DefaultServiceVersion to. An example REST request looks like the following:

Sample REST Request
 PUT https://cohowinery.blob.core.windows.net/?restype=service&comp=properties HTTP/1.1
x-ms-version: 2011-08-18
x-ms-date: Sat, 10 Sep 2011 04:28:19 GMT
Authorization: SharedKey cohowinery:Z1lTLDwtq5o1UYQluucdsXk6/iB7YxEu0m6VofAEkUE=
Host: cohowinery.blob.core.windows.net
Content-Length: 200
<?xml version="1.0" encoding="utf-8"?>
<StorageServiceProperties>
    <Logging>
        <Version>1.0</Version>
        <Delete>true</Delete>
        <Read>false</Read>
        <Write>true</Write>
        <RetentionPolicy>
            <Enabled>true</Enabled>
            <Days>7</Days>
        </RetentionPolicy>
    </Logging>
    <Metrics>
        <Version>1.0</Version>
        <Enabled>true</Enabled>
        <IncludeAPIs>false</IncludeAPIs>
        <RetentionPolicy>
            <Enabled>true</Enabled>
            <Days>7</Days>
        </RetentionPolicy>
    </Metrics>
    <DefaultServiceVersion>2011-08-18</DefaultServiceVersion>
</StorageServiceProperties>

Get Storage Service Properties

Using the 2011-08-18 version, this API will now return the DefaultServiceVersion if it has been set.

Sample REST Request
 GET https://cohowinery.blob.core.windows.net/?restype=service&comp=properties HTTP/1.1
x-ms-version: 2011-08-18
x-ms-date: Sat, 10 Sep 2011 04:28:19 GMT
Authorization: SharedKey cohowinery:Z1lTLDwtq5o1UYQluucdsXk6/iB7YxEu0m6VofAEkUE=
Host: cohowinery.blob.core.windows.net

Sample Library and Usage

We provide sample code that can be used to set these service settings. It is very similar to the example provided in the Analytics blog but it uses the new DefaultServiceVersion and we have renamed some classes in which we have used “ServiceSettings” in place of “AnalyticsSettings” in class names and method names.

  • Class SettingsSerializerHelper handles serialization and deserialization of settings.
  • Class ServiceSettings represents the service settings. It also contains DefaultServiceVersion property which should be set only for blob service. Windows Azure Queue and Table service will return a 400 HTTP (“Bad Request”) status error code.
  • Class ServiceSettingsExtension implements extension methods that can be used to set/get service settings.

The way to use the code is still the same except for the new DefaultServiceVersion property:

 CloudStorageAccount account = CloudStorageAccount.Parse(ConnectionString);
CloudBlobClient blobClient = account.CreateCloudBlobClient();
ServiceSettings settings = new ServiceSettings()
        {
            LogType = LoggingLevel.Delete | LoggingLevel.Read | LoggingLevel.Write,
            IsLogRetentionPolicyEnabled = false,
            LogRetentionInDays = 7,
            IsMetricsRetentionPolicyEnabled = true,
            MetricsRetentionInDays = 3,
            MetricsType = MetricsType.All,
            DefaultServiceVersion = "2011-08-18"
        };

blobClient.SetServiceSettings(settings);

Here are the rest of the utility classes.

 using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

/// <summary>
/// This class handles the serialization and deserialization of service settings
/// </summary>
public static class SettingsSerializerHelper
{
    private const string RootPropertiesElementName = "StorageServiceProperties";
    private const string VersionElementName = "Version";
    private const string RetentionPolicyElementName = "RetentionPolicy";
    private const string RetentionPolicyEnabledElementName = "Enabled";
    private const string RetentionPolicyDaysElementName = "Days";
    private const string DefaultServiceVersionElementName = "DefaultServiceVersion";

    private const string LoggingElementName = "Logging";
    private const string ApiTypeDeleteElementName = "Delete";
    private const string ApiTypeReadElementName = "Read";
    private const string ApiTypeWriteElementName = "Write";

    private const string MetricsElementName = "Metrics";
    private const string IncludeApiSummaryElementName = "IncludeAPIs";
    private const string MetricsEnabledElementName = "Enabled";

    private const int MaximumRetentionDays = 365;

    /// <summary>
    /// Reads the settings provided from stream
    /// </summary>
    /// <param name="xmlReader"></param>
    /// <returns></returns>
    internal static ServiceSettings DeserializeServiceSettings(XmlReader xmlReader)
    {
        // Read the root and check if it is empty or invalid
        xmlReader.Read();
        xmlReader.ReadStartElement(SettingsSerializerHelper.RootPropertiesElementName);

        ServiceSettings settings = new ServiceSettings();

        while (true)
        {
            if (xmlReader.IsStartElement(SettingsSerializerHelper.LoggingElementName))
            {
                DeserializeLoggingElement(xmlReader, settings);
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.MetricsElementName))
            {
                DeserializeMetricsElement(xmlReader, settings);
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.DefaultServiceVersionElementName))
            {
                settings.DefaultServiceVersion = xmlReader.ReadElementString(SettingsSerializerHelper.DefaultServiceVersionElementName);
            }
            else
            {
                break;
            }
        }

        xmlReader.ReadEndElement();

        return settings;
    }


    /// <summary>
    /// Write the settings provided to stream
    /// </summary>
    /// <param name="xmlWriter"></param>
    /// <param name="settings"></param>
    /// <returns></returns>
    internal static void SerializeServiceSettings(XmlWriter xmlWriter, ServiceSettings settings)
    {
        xmlWriter.WriteStartDocument();
        xmlWriter.WriteStartElement(SettingsSerializerHelper.RootPropertiesElementName);

        //LOGGING STARTS HERE
        xmlWriter.WriteStartElement(SettingsSerializerHelper.LoggingElementName);

        xmlWriter.WriteStartElement(SettingsSerializerHelper.VersionElementName);
        xmlWriter.WriteValue(settings.LogVersion);
        xmlWriter.WriteEndElement();

        bool isReadEnabled = (settings.LogType & LoggingLevel.Read) != LoggingLevel.None;
        xmlWriter.WriteStartElement(SettingsSerializerHelper.ApiTypeReadElementName);
        xmlWriter.WriteValue(isReadEnabled);
        xmlWriter.WriteEndElement();

        bool isWriteEnabled = (settings.LogType & LoggingLevel.Write) != LoggingLevel.None;
        xmlWriter.WriteStartElement(SettingsSerializerHelper.ApiTypeWriteElementName);
        xmlWriter.WriteValue(isWriteEnabled);
        xmlWriter.WriteEndElement();

        bool isDeleteEnabled = (settings.LogType & LoggingLevel.Delete) != LoggingLevel.None;
        xmlWriter.WriteStartElement(SettingsSerializerHelper.ApiTypeDeleteElementName);
        xmlWriter.WriteValue(isDeleteEnabled);
        xmlWriter.WriteEndElement();

        SerializeRetentionPolicy(xmlWriter, settings.IsLogRetentionPolicyEnabled, settings.LogRetentionInDays);
        xmlWriter.WriteEndElement(); // logging element

        //METRICS STARTS HERE
        xmlWriter.WriteStartElement(SettingsSerializerHelper.MetricsElementName);

        xmlWriter.WriteStartElement(SettingsSerializerHelper.VersionElementName);
        xmlWriter.WriteValue(settings.MetricsVersion);
        xmlWriter.WriteEndElement();

        bool isServiceSummaryEnabled = (settings.MetricsType & MetricsType.ServiceSummary) != MetricsType.None;
        xmlWriter.WriteStartElement(SettingsSerializerHelper.MetricsEnabledElementName);
        xmlWriter.WriteValue(isServiceSummaryEnabled);
        xmlWriter.WriteEndElement();

        if (isServiceSummaryEnabled)
        {
            bool isApiSummaryEnabled = (settings.MetricsType & MetricsType.ApiSummary) != MetricsType.None;
            xmlWriter.WriteStartElement(SettingsSerializerHelper.IncludeApiSummaryElementName);
            xmlWriter.WriteValue(isApiSummaryEnabled);
            xmlWriter.WriteEndElement();
        }

        SerializeRetentionPolicy(
            xmlWriter,
            settings.IsMetricsRetentionPolicyEnabled,
            settings.MetricsRetentionInDays);
        xmlWriter.WriteEndElement(); // metrics 

        // Save default service version if provided. NOTE - this should be set only for blob service
        if (!string.IsNullOrEmpty(settings.DefaultServiceVersion))
        {
            xmlWriter.WriteStartElement(SettingsSerializerHelper.DefaultServiceVersionElementName);
            xmlWriter.WriteValue(settings.DefaultServiceVersion);
            xmlWriter.WriteEndElement();
        }

        xmlWriter.WriteEndElement(); // root element
        xmlWriter.WriteEndDocument();
    }

    private static void SerializeRetentionPolicy(XmlWriter xmlWriter, bool isRetentionEnabled, int days)
    {
        xmlWriter.WriteStartElement(SettingsSerializerHelper.RetentionPolicyElementName);

        xmlWriter.WriteStartElement(SettingsSerializerHelper.RetentionPolicyEnabledElementName);
        xmlWriter.WriteValue(isRetentionEnabled);
        xmlWriter.WriteEndElement();

        if (isRetentionEnabled)
        {
            xmlWriter.WriteStartElement(SettingsSerializerHelper.RetentionPolicyDaysElementName);
            xmlWriter.WriteValue(days);
            xmlWriter.WriteEndElement();
        }

        xmlWriter.WriteEndElement(); // Retention policy for logs
    }

    /// <summary>
    /// Reads the logging element and fills in the values in settings instance
    /// </summary>
    /// <param name="xmlReader"></param>
    /// <param name="settings"></param>
    private static void DeserializeLoggingElement(
        XmlReader xmlReader,
        ServiceSettings settings)
    {
        // Read logging element
        xmlReader.ReadStartElement(SettingsSerializerHelper.LoggingElementName);

        while (true)
        {
            if (xmlReader.IsStartElement(SettingsSerializerHelper.VersionElementName))
            {
                settings.LogVersion = xmlReader.ReadElementString(SettingsSerializerHelper.VersionElementName);
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.ApiTypeReadElementName))
            {
                if (DeserializeBooleanElementValue(
                    xmlReader,
                    SettingsSerializerHelper.ApiTypeReadElementName))
                {
                    settings.LogType = settings.LogType | LoggingLevel.Read;
                }
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.ApiTypeWriteElementName))
            {
                if (DeserializeBooleanElementValue(
                    xmlReader,
                    SettingsSerializerHelper.ApiTypeWriteElementName))
                {
                    settings.LogType = settings.LogType | LoggingLevel.Write;
                }
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.ApiTypeDeleteElementName))
            {
                if (DeserializeBooleanElementValue(
                    xmlReader,
                    SettingsSerializerHelper.ApiTypeDeleteElementName))
                {
                    settings.LogType = settings.LogType | LoggingLevel.Delete;
                }
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.RetentionPolicyElementName))
            {
                // read retention policy for logging
                bool isRetentionEnabled = false;
                int retentionDays = 0;
                DeserializeRetentionPolicy(xmlReader, ref isRetentionEnabled, ref retentionDays);
                settings.IsLogRetentionPolicyEnabled = isRetentionEnabled;
                settings.LogRetentionInDays = retentionDays;
            }
            else
            {
                break;
            }
        }

        xmlReader.ReadEndElement();// end Logging element
    }

    /// <summary>
    /// Reads the metrics element and fills in the values in settings instance
    /// </summary>
    /// <param name="xmlReader"></param>
    /// <param name="settings"></param>
    private static void DeserializeMetricsElement(
        XmlReader xmlReader,
        ServiceSettings settings)
    {
        bool includeAPIs = false;

        // read the next element - it should be metrics. 
        xmlReader.ReadStartElement(SettingsSerializerHelper.MetricsElementName);

        while (true)
        {
            if (xmlReader.IsStartElement(SettingsSerializerHelper.VersionElementName))
            {
                settings.MetricsVersion = xmlReader.ReadElementString(SettingsSerializerHelper.VersionElementName);

            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.MetricsEnabledElementName))
            {
                if (DeserializeBooleanElementValue(
                    xmlReader,
                    SettingsSerializerHelper.MetricsEnabledElementName))
                {
                    // only if metrics is enabled will we read include API
                    settings.MetricsType = settings.MetricsType | MetricsType.ServiceSummary;
                }
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.IncludeApiSummaryElementName))
            {
                if (DeserializeBooleanElementValue(
                    xmlReader,
                    SettingsSerializerHelper.IncludeApiSummaryElementName))
                {
                    includeAPIs = true;
                }
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.RetentionPolicyElementName))
            {
                // read retention policy for metrics
                bool isRetentionEnabled = false;
                int retentionDays = 0;
                DeserializeRetentionPolicy(xmlReader, ref isRetentionEnabled, ref retentionDays);
                settings.IsMetricsRetentionPolicyEnabled = isRetentionEnabled;
                settings.MetricsRetentionInDays = retentionDays;
            }
            else
            {
                break;
            }
        }

        if ((settings.MetricsType & MetricsType.ServiceSummary) != MetricsType.None)
        {
            // If Metrics is enabled, IncludeAPIs must be included.
            if (includeAPIs)
            {
                settings.MetricsType = settings.MetricsType | MetricsType.ApiSummary;
            }
        }

        xmlReader.ReadEndElement();// end metrics element
    }


    /// <summary>
    /// Reads the retention policy in logging and metrics elements 
    /// and fills in the values in settings instance.
    /// </summary>
    /// <param name="xmlReader"></param>
    /// <param name="isRetentionEnabled"></param>
    /// <param name="retentionDays"></param>
    private static void DeserializeRetentionPolicy(
        XmlReader xmlReader,
        ref bool isRetentionEnabled,
        ref int retentionDays)
    {
        xmlReader.ReadStartElement(SettingsSerializerHelper.RetentionPolicyElementName);

        while (true)
        {
            if (xmlReader.IsStartElement(SettingsSerializerHelper.RetentionPolicyEnabledElementName))
            {
                isRetentionEnabled = DeserializeBooleanElementValue(
                    xmlReader,
                    SettingsSerializerHelper.RetentionPolicyEnabledElementName);
            }
            else if (xmlReader.IsStartElement(SettingsSerializerHelper.RetentionPolicyDaysElementName))
            {
                string intValue = xmlReader.ReadElementString(
                    SettingsSerializerHelper.RetentionPolicyDaysElementName);
                retentionDays = int.Parse(intValue);
            }
            else
            {
                break;
            }
        }

        xmlReader.ReadEndElement(); // end reading retention policy
    }

    /// <summary>
    /// Read a boolean value for xml element
    /// </summary>
    /// <param name="xmlReader"></param>
    /// <param name="elementToRead"></param>
    /// <returns></returns>
    private static bool DeserializeBooleanElementValue(
        XmlReader xmlReader,
        string elementToRead)
    {
        string boolValue = xmlReader.ReadElementString(elementToRead);
        return bool.Parse(boolValue);
    }
}

[Flags]
public enum LoggingLevel
{
    None = 0,
    Delete = 2,
    Write = 4,
    Read = 8,
}

[Flags]
public enum MetricsType
{
    None = 0x0,
    ServiceSummary = 0x1,
    ApiSummary = 0x2,
    All = ServiceSummary | ApiSummary,
}

/// <summary>
/// The service settings that can set/get
/// </summary>
public class ServiceSettings
{
    public static string Version = "1.0";

    public ServiceSettings()
    {
        this.LogType = LoggingLevel.None;
        this.LogVersion = ServiceSettings.Version;
        this.IsLogRetentionPolicyEnabled = false;
        this.LogRetentionInDays = 0;

        this.MetricsType = MetricsType.None;
        this.MetricsVersion = ServiceSettings.Version;
        this.IsMetricsRetentionPolicyEnabled = false;
        this.MetricsRetentionInDays = 0;
    }

    /// <summary>
    /// The default service version to use for un-versioned requests
    /// NOTE: This can be set only for blob service. 
    /// Possible values: 2009-09-19 or 2011-08-18. 
    /// </summary>
    public string DefaultServiceVersion { get; set; }

    /// <summary>
    /// The type of logs subscribed for
    /// </summary>
    public LoggingLevel LogType { get; set; }

    /// <summary>
    /// The version of the logs
    /// </summary>
    public string LogVersion { get; set; }

    /// <summary>
    /// Flag indicating if retention policy is set for logs in $logs
    /// </summary>
    public bool IsLogRetentionPolicyEnabled { get; set; }

    /// <summary>
    /// The number of days to retain logs for under $logs container
    /// </summary>
    public int LogRetentionInDays { get; set; }

    /// <summary>
    /// The metrics version
    /// </summary>
    public string MetricsVersion { get; set; }

    /// <summary>
    /// A flag indicating if retention policy is enabled for metrics
    /// </summary>
    public bool IsMetricsRetentionPolicyEnabled { get; set; }

    /// <summary>
    /// The number of days to retain metrics data
    /// </summary>
    public int MetricsRetentionInDays { get; set; }

    private MetricsType metricsType = MetricsType.None;

    /// <summary>
    /// The type of metrics subscribed for
    /// </summary>
    public MetricsType MetricsType
    {
        get
        {
            return metricsType;
        }

        set
        {
            if (value == MetricsType.ApiSummary)
            {
                throw new ArgumentException("Including just ApiSummary is invalid.");
            }

            this.metricsType = value;
        }
    }
}


/// <summary>
/// Extension methods for setting service settings
/// </summary>
public static class ServiceSettingsExtension
{
    static string RequestIdHeaderName = "x-ms-request-id";
    static string VersionHeaderName = "x-ms-version";
    static string VersionToUse = "2011-08-18";
    static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);

    #region ServiceSettings
    /// <summary>
    /// Set blob service settings
    /// </summary>
    /// <param name="client"></param>
    /// <param name="settings"></param>
    public static void SetServiceSettings(this CloudBlobClient client, ServiceSettings settings)
    {
        SetSettings(client.BaseUri, client.Credentials, settings, false /* useSharedKeyLite */);
    }

    /// <summary>
    /// Set queue service settings
    /// </summary>
    /// <param name="client"></param>
    /// <param name="baseUri"></param>
    /// <param name="settings"></param>
    public static void SetServiceSettings(this CloudQueueClient client, Uri baseUri, ServiceSettings settings)
    {
        SetSettings(baseUri, client.Credentials, settings, false /* useSharedKeyLite */);
    }

    /// <summary>
    /// Set blob service settings
    /// </summary>
    /// <param name="client"></param>
    /// <param name="settings"></param>
    public static void SetServiceSettings(this CloudTableClient client, ServiceSettings settings)
    {
        SetSettings(client.BaseUri, client.Credentials, settings, true /* useSharedKeyLite */);
    }

    /// <summary>
    /// Set service settings
    /// </summary>
    /// <param name="baseUri"></param>
    /// <param name="credentials"></param>
    /// <param name="settings"></param>
    /// <param name="useSharedKeyLite"></param>
    internal static void SetSettings(
        Uri baseUri, 
        StorageCredentials credentials, 
        ServiceSettings settings, 
        bool useSharedKeyLite)
    {
        UriBuilder builder = new UriBuilder(baseUri);
        builder.Query = string.Format(
            CultureInfo.InvariantCulture,
            "comp=properties&restype=service&timeout={0}",
            DefaultTimeout.TotalSeconds);

        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(builder.Uri);
        request.Headers.Add(VersionHeaderName, VersionToUse);
        request.Method = "PUT";

        StorageCredentialsAccountAndKey accountAndKey = credentials as StorageCredentialsAccountAndKey;
        using (MemoryStream buffer = new MemoryStream())
        {
            XmlTextWriter writer = new XmlTextWriter(buffer, Encoding.UTF8);
            SettingsSerializerHelper.SerializeServiceSettings(writer, settings);
            writer.Flush();
            buffer.Seek(0, SeekOrigin.Begin);
            request.ContentLength = buffer.Length;

            if (useSharedKeyLite)
            {
                credentials.SignRequestLite(request);
            }
            else
            {
                credentials.SignRequest(request);
            }

            using (Stream stream = request.GetRequestStream())
            {
                stream.Write(buffer.GetBuffer(), 0, (int)buffer.Length);
            }

            try
            {
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    Console.WriteLine("Response Request Id = {0} Status={1}", response.Headers[RequestIdHeaderName], response.StatusCode);
                    if (HttpStatusCode.Accepted != response.StatusCode)
                    {
                        throw new Exception("Request failed with incorrect response status.");
                    }
                }
            }
            catch (WebException e)
            {
                Console.WriteLine(
                    "Response Request Id={0} Status={1}",
                    e.Response != null ? e.Response.Headers[RequestIdHeaderName] : "Response is null",
                    e.Status);
                throw;
            }

        }
    }

    /// <summary>
    /// Get blob service settings
    /// </summary>
    /// <param name="client"></param>
    /// <returns></returns>
    public static ServiceSettings GetServiceSettings(this CloudBlobClient client)
    {
        return GetSettings(client.BaseUri, client.Credentials, false /* useSharedKeyLite */);
    }

    /// <summary>
    /// Get queue service settings
    /// </summary>
    /// <param name="client"></param>
    /// <param name="baseUri"></param>
    /// <returns></returns>
    public static ServiceSettings GetServiceSettings(this CloudQueueClient client, Uri baseUri)
    {
        return GetSettings(baseUri, client.Credentials, false /* useSharedKeyLite */);
    }

    /// <summary>
    /// Get table service settings
    /// </summary>
    /// <param name="client"></param>
    /// <returns></returns>
    public static ServiceSettings GetServiceSettings(this CloudTableClient client)
    {
        return GetSettings(client.BaseUri, client.Credentials, true /* useSharedKeyLite */);
    }

    /// <summary>
    /// Get service settings
    /// </summary>
    /// <param name="baseUri"></param>
    /// <param name="credentials"></param>
    /// <param name="useSharedKeyLite"></param>
    /// <returns></returns>
    public static ServiceSettings GetSettings(
        Uri baseUri, 
        StorageCredentials credentials, 
        bool useSharedKeyLite)
    {
        UriBuilder builder = new UriBuilder(baseUri);
        builder.Query = string.Format(
            CultureInfo.InvariantCulture,
            "comp=properties&restype=service&timeout={0}",
            DefaultTimeout.TotalSeconds);

        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(builder.Uri);
        request.Headers.Add(VersionHeaderName, VersionToUse);
        request.Method = "GET";

        StorageCredentialsAccountAndKey accountAndKey = credentials as StorageCredentialsAccountAndKey;

        if (useSharedKeyLite)
        {
            credentials.SignRequestLite(request);
        }
        else
        {
            credentials.SignRequest(request);
        }

        try
        {
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                Console.WriteLine("Response Request Id={0} Status={1}", response.Headers[RequestIdHeaderName], response.StatusCode);

                if (HttpStatusCode.OK != response.StatusCode)
                {
                    throw new Exception("expected HttpStatusCode.OK");
                }

                using (Stream stream = response.GetResponseStream())
                {
                    using (StreamReader streamReader = new StreamReader(stream))
                    {
                        string responseString = streamReader.ReadToEnd();
                        Console.WriteLine(responseString);

                        XmlReader reader = XmlReader.Create(new MemoryStream(ASCIIEncoding.UTF8.GetBytes(responseString)));
                        return SettingsSerializerHelper.DeserializeServiceSettings(reader);
                    }
                }
            }
        }
        catch (WebException e)
        {
            Console.WriteLine(
                "Response Request Id={0} Status={1}",
                e.Response != null ? e.Response.Headers[RequestIdHeaderName] : "Response is null",
                e.Status);
            throw;
        }
    }
    #endregion
}

Jai Haridas