Skip to content

Commit

Permalink
- Added support for AccountController.SetBlobServiceProperties to del…
Browse files Browse the repository at this point in the history
…egate the account properties to all accounts to enable CORS

- Fixed bug in ContainerController.SetContainerAcl so that if no XML content is supplied, the handler does not blow up.
- Added new mechanism for XMLHttpRequest clients to indicate that they can handle DASH redirection responses.

Related Work Items: #31441
  • Loading branch information
jamesbak committed Jul 1, 2016
1 parent bdcb4a3 commit 03c4bb5
Show file tree
Hide file tree
Showing 111 changed files with 4,282 additions and 203 deletions.
50 changes: 7 additions & 43 deletions DashCommon/Handlers/ContainerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,54 +49,18 @@ public static async Task<SimpleHttpResponse> DoForDataContainersAsync(string con
return await DoForContainersAsync(container, successStatus, action, DashConfiguration.DataAccounts, ignoreNotFound, excludeAccounts);
}

private static async Task<SimpleHttpResponse> DoForContainersAsync(string container,
private static Task<SimpleHttpResponse> DoForContainersAsync(string container,
HttpStatusCode successStatus,
Func<CloudBlobContainer, Task> action,
IEnumerable<CloudStorageAccount> accounts,
bool ignoreNotFound,
IEnumerable<CloudStorageAccount> excludeAccounts)
{
SimpleHttpResponse retval = new SimpleHttpResponse
{
StatusCode = successStatus,
};
if (excludeAccounts != null)
{
accounts = accounts.Except(excludeAccounts);
}
var actionTasks = accounts
.Select(account => action(NamespaceHandler.GetContainerByName(account, container)));
try
{
await Task.WhenAll(actionTasks.ToArray());
}
catch (AggregateException aggEx)
{
aggEx.Handle(ex => ProcessException(ex, ignoreNotFound, retval));
}
catch (Exception ex)
{
if (!ProcessException(ex, ignoreNotFound, retval))
{
throw;
}
}
return retval;
}

private static bool ProcessException(Exception ex, bool ignoreNotFound, SimpleHttpResponse response)
{
if (ex is StorageException)
{
var storeEx = (StorageException)ex;
if (!ignoreNotFound || storeEx.RequestInformation.HttpStatusCode != (int)HttpStatusCode.NotFound)
{
response.StatusCode = (HttpStatusCode)storeEx.RequestInformation.HttpStatusCode;
response.ReasonPhrase = storeEx.RequestInformation.HttpStatusMessage;
}
return true;
}
return false;
{
return NamespaceHandler.DoForAccountsAsync(successStatus,
(client) => action(client.GetContainerReference(container)),
accounts,
ignoreNotFound,
excludeAccounts);
}
}
}
51 changes: 51 additions & 0 deletions DashCommon/Handlers/NamespaceHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
Expand Down Expand Up @@ -112,5 +114,54 @@ public static ICloudBlob GetBlobByName(CloudStorageAccount account, string conta
}
return container.GetBlockBlobReference(blobName);
}

public static async Task<SimpleHttpResponse> DoForAccountsAsync(HttpStatusCode successStatus,
Func<CloudBlobClient, Task> action,
IEnumerable<CloudStorageAccount> accounts,
bool ignoreNotFound,
IEnumerable<CloudStorageAccount> excludeAccounts = null)
{
SimpleHttpResponse retval = new SimpleHttpResponse
{
StatusCode = successStatus,
};
if (excludeAccounts != null)
{
accounts = accounts.Except(excludeAccounts);
}
var actionTasks = accounts
.Select(account => action(account.CreateCloudBlobClient()));
try
{
await Task.WhenAll(actionTasks.ToArray());
}
catch (AggregateException aggEx)
{
aggEx.Handle(ex => ProcessException(ex, ignoreNotFound, retval));
}
catch (Exception ex)
{
if (!ProcessException(ex, ignoreNotFound, retval))
{
throw;
}
}
return retval;
}

private static bool ProcessException(Exception ex, bool ignoreNotFound, SimpleHttpResponse response)
{
if (ex is StorageException)
{
var storeEx = (StorageException)ex;
if (!ignoreNotFound || storeEx.RequestInformation.HttpStatusCode != (int)HttpStatusCode.NotFound)
{
response.StatusCode = (HttpStatusCode)storeEx.RequestInformation.HttpStatusCode;
response.ReasonPhrase = storeEx.RequestInformation.HttpStatusMessage;
}
return true;
}
return false;
}
}
}
150 changes: 150 additions & 0 deletions DashServer.Tests/AccountControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,155 @@ public void GetServicePropertiesControllerTest()
Assert.IsNotNull(serviceProps.Element("Cors"));
}

[TestMethod]
public void SetServicePropertiesControllerTest()
{
string propsUri = "http://localhost/account?restype=service&comp=properties";
string apiVersion = "2014-02-14";
// Version 2013-08-15 - full document
var body = new XDocument(
new XElement("StorageServiceProperties",
new XElement("Logging",
new XElement("Version", "1.0"),
new XElement("Delete", true),
new XElement("Read", true),
new XElement("Write", true),
new XElement("RetentionPolicy",
new XElement("Enabled", true),
new XElement("Days", 10)
)
),
new XElement("HourMetrics",
new XElement("Version", "1.0"),
new XElement("Enabled", true),
new XElement("IncludeAPIs", true),
new XElement("RetentionPolicy",
new XElement("Enabled", true),
new XElement("Days", 10)
)
),
new XElement("MinuteMetrics",
new XElement("Version", "1.0"),
new XElement("Enabled", true),
new XElement("IncludeAPIs", true),
new XElement("RetentionPolicy",
new XElement("Enabled", true),
new XElement("Days", 10)
)
),
new XElement("Cors",
new XElement("CorsRule",
new XElement("AllowedOrigins", "http://origin1.com,https://origin2.com"),
new XElement("AllowedMethods", "GET,PUT,DELETE,OPTIONS"),
new XElement("ExposedHeaders", "*"),
new XElement("AllowedHeaders", "x-ms-header1,x-ms-header2"),
new XElement("MaxAgeInSeconds", 1800)
),
new XElement("CorsRule",
new XElement("AllowedOrigins", "*"),
new XElement("AllowedMethods", "GET,PUT,DELETE,OPTIONS"),
new XElement("ExposedHeaders", "*"),
new XElement("AllowedHeaders", "*"),
new XElement("MaxAgeInSeconds", 1800)
)
),
new XElement("DefaultServiceVersion", apiVersion)
)
);
string requestGuid = Guid.NewGuid().ToString("N");
var customHeaders = new List<Tuple<string, string>>
{
new Tuple<string, string>("x-ms-client-request-id", requestGuid),
new Tuple<string, string>("x-ms-version", apiVersion),
};
var results = _ctx.Runner.ExecuteRequest(propsUri,
"PUT",
body,
customHeaders,
HttpStatusCode.Accepted);
Assert.AreEqual(requestGuid, results.Headers.GetValues("x-ms-request-id").First());
Assert.AreEqual(apiVersion, results.Headers.GetValues("x-ms-version").First());
// Read the properties back
results = _ctx.Runner.ExecuteRequestWithHeaders(propsUri,
"GET",
null,
new[] {
Tuple.Create("x-ms-version", apiVersion)
},
expectedStatusCode: HttpStatusCode.OK);
var doc = XDocument.Load(results.Content.ReadAsStreamAsync().Result);
var serviceProps = doc.Root;
Assert.AreEqual(2, serviceProps.Element("Cors").Elements().Count());
var corsRule = serviceProps.Element("Cors").Element("CorsRule");
Assert.AreEqual(2, corsRule.Element("AllowedOrigins").Value.Split(',').Length);
Assert.AreEqual(4, corsRule.Element("AllowedMethods").Value.Split(',').Length);
Assert.AreEqual(2, corsRule.Element("AllowedHeaders").Value.Split(',').Length);
Assert.AreEqual("*", corsRule.Element("ExposedHeaders").Value);
Assert.AreEqual(1800, (int)corsRule.Element("MaxAgeInSeconds"));
Assert.AreEqual(apiVersion, (string)serviceProps.Element("DefaultServiceVersion"));

// Version 2013-08-15 - minimal document
body = new XDocument(
new XElement("StorageServiceProperties",
new XElement("Cors",
new XElement("CorsRule",
new XElement("AllowedOrigins", "*"),
new XElement("AllowedMethods", "GET,PUT,DELETE,OPTIONS"),
new XElement("ExposedHeaders", "*"),
new XElement("AllowedHeaders", "*"),
new XElement("MaxAgeInSeconds", 1800)
)
)
)
);
results = _ctx.Runner.ExecuteRequest(propsUri,
"PUT",
body,
customHeaders,
HttpStatusCode.Accepted);
Assert.AreEqual(requestGuid, results.Headers.GetValues("x-ms-request-id").First());
Assert.AreEqual(apiVersion, results.Headers.GetValues("x-ms-version").First());

// 2012-02-12
apiVersion = "2012-02-12";
customHeaders = new List<Tuple<string, string>>
{
new Tuple<string, string>("x-ms-client-request-id", requestGuid),
new Tuple<string, string>("x-ms-version", apiVersion),
};
body = new XDocument(
new XElement("StorageServiceProperties",
new XElement("Logging",
new XElement("Version", "1.0"),
new XElement("Delete", true),
new XElement("Read", true),
new XElement("Write", true),
new XElement("RetentionPolicy",
new XElement("Enabled", true),
new XElement("Days", 10)
)
),
new XElement("Metrics",
new XElement("Version", "1.0"),
new XElement("Enabled", true),
new XElement("IncludeAPIs", true),
new XElement("RetentionPolicy",
new XElement("Enabled", true),
new XElement("Days", 10)
)
),
new XElement("DefaultServiceVersion", apiVersion)
)
);
results = _ctx.Runner.ExecuteRequest(propsUri,
"PUT",
body,
customHeaders,
HttpStatusCode.Accepted);
Assert.AreEqual(requestGuid, results.Headers.GetValues("x-ms-request-id").First());
Assert.AreEqual(apiVersion, results.Headers.GetValues("x-ms-version").First());

}

}
}
1 change: 1 addition & 0 deletions DashServer.Tests/BlobControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ public void PutBlobBlockControllerTest()
response = _ctx.Runner.ExecuteRequest(blobUri + "?comp=blocklist",
"PUT",
blockList,
null,
HttpStatusCode.Created);

// Read back the complete blob
Expand Down
1 change: 1 addition & 0 deletions DashServer.Tests/BlobReplicationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public void ReplicateWithAllOperationsControllerTest()
new XElement("Latest", blockId)
)
),
null,
HttpStatusCode.Created);
AssertReplicationMessageIsEnqueued(MessageTypes.BeginReplicate, _ctx.ContainerName, blobName, dataAccountName);
// Set metadata - should trigger replication
Expand Down
2 changes: 1 addition & 1 deletion DashServer.Tests/RequestAuthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static void Init(TestContext ctx)
new XElement("Id", StoredPolicyNoDates),
new XElement("AccessPolicy",
new XElement("Permission", "r")))));
_ctx.Runner.ExecuteRequest(_ctx.GetContainerUri() + "&comp=acl", "PUT", body, HttpStatusCode.OK);
_ctx.Runner.ExecuteRequest(_ctx.GetContainerUri() + "&comp=acl", "PUT", body, null, HttpStatusCode.OK);
}

[ClassCleanup]
Expand Down
4 changes: 2 additions & 2 deletions DashServer.Tests/WebApiTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,15 @@ public void AddHeaders(HttpRequestMessage request, IEnumerable<Tuple<string, str
}
}

public HttpResponseMessage ExecuteRequest(string uri, string method, XDocument body = null, HttpStatusCode expectedStatusCode = HttpStatusCode.Unused)
public HttpResponseMessage ExecuteRequest(string uri, string method, XDocument body = null, IEnumerable<Tuple<string, string>> headers = null, HttpStatusCode expectedStatusCode = HttpStatusCode.Unused)
{
HttpContent bodycontent = null;
if (body != null)
{
bodycontent = new StringContent(body.ToString(SaveOptions.OmitDuplicateNamespaces));
bodycontent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/xml");
}
return ExecuteRequest(uri, method, bodycontent, expectedStatusCode);
return ExecuteRequestWithHeaders(uri, method, bodycontent, headers, expectedStatusCode);
}

public XDocument ExecuteRequestResponse(string uri, string method, XDocument body = null, HttpStatusCode expectedStatusCode = HttpStatusCode.Unused)
Expand Down
3 changes: 2 additions & 1 deletion DashServer/App_Start/WebApiConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Cors;

namespace Microsoft.Dash.Server
{
Expand All @@ -16,14 +17,14 @@ public static void Register(HttpConfiguration config)
// Web API routes
config.MapHttpAttributeRoutes();


config.Routes.MapHttpRoute(
name: "BlobsAndContainers",
routeTemplate: "{controller}/{container}/{*blob}");
config.Routes.MapHttpRoute(
name: "Account",
routeTemplate: "{controller}");

config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
}
}
}
4 changes: 0 additions & 4 deletions DashServer/Authorization/Anonymous.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ public static async Task<bool> IsAuthorizedAsync(IHttpRequestWrapper request)
bool retval = false;
var requestUriParts = request.UriParts;
var requestOperation = StorageOperations.GetBlobOperation(request.HttpMethod, requestUriParts, request.QueryParameters, request.Headers);
if (request.HttpMethod == HttpMethod.Options.ToString())
{
return true;
}
switch (requestOperation)
{
case StorageOperationTypes.GetContainerProperties:
Expand Down
Loading

0 comments on commit 03c4bb5

Please sign in to comment.