Adding support for creating, getting and deleting folders via the storage client

Implements: blueprint storage-folder-support

Change-Id: Ie666b995cfb3dcf45dcf684908c9f0e455596d06
This commit is contained in:
Wayne Foley 2014-03-28 15:53:49 -07:00
parent a469ad406c
commit b17cddd189
21 changed files with 1328 additions and 19 deletions

View File

@ -14,6 +14,8 @@
// limitations under the License.
// ============================================================================ */
using System.IO;
namespace Openstack.Test.Storage
{
using System;
@ -213,7 +215,7 @@ namespace Openstack.Test.Storage
}
[TestMethod]
public void CanConvertFolders()
public void CanConvertFoldersWithObjects()
{
var objects = new List<StorageObject>()
{
@ -261,5 +263,170 @@ namespace Openstack.Test.Storage
var converter = new StorageFolderPayloadConverter();
var resp = converter.Convert(null);
}
[TestMethod]
public void CanConvertFolderWithValidJsonAndNoSubFoldersOrFolderObject()
{
var containerName = "container";
var folderName = "a/b/c/";
var payload = @"[
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/BLAH"",
""content_type"": ""application/octet-stream""
}]";
var converter = new StorageFolderPayloadConverter();
var resp = converter.Convert(containerName, folderName, payload);
Assert.AreEqual(1, resp.Objects.Count);
Assert.AreEqual(0, resp.Folders.Count);
Assert.AreEqual("a/b/c", resp.FullName);
Assert.AreEqual("c", resp.Name);
var obj = resp.Objects.First();
Assert.AreEqual("a/b/c/BLAH", obj.Name);
}
[TestMethod]
[ExpectedException(typeof(InvalidDataException))]
public void CannotConvertEmptyJsonArrayPayload()
{
var containerName = "container";
var folderName = "a/b/c/";
var payload = @"[]";
var converter = new StorageFolderPayloadConverter();
converter.Convert(containerName, folderName, payload);
}
[TestMethod]
public void CanConvertFolderWithValidJsonFolderObjectAndNoSubFolders()
{
var containerName = "container";
var folderName = "a/b/c/";
var payload = @"[
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/"",
""content_type"": ""application/octet-stream""
},
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/BLAH"",
""content_type"": ""application/octet-stream""
}]";
var converter = new StorageFolderPayloadConverter();
var resp = converter.Convert(containerName, folderName, payload);
Assert.AreEqual(1, resp.Objects.Count);
Assert.AreEqual(0, resp.Folders.Count);
Assert.AreEqual("a/b/c", resp.FullName);
Assert.AreEqual("c", resp.Name);
var obj = resp.Objects.First();
Assert.AreEqual("a/b/c/BLAH", obj.Name);
}
[TestMethod]
public void CanConvertFolderWithValidJsonSubFoldersAndNoFolderObject()
{
var containerName = "container";
var folderName = "a/b/c/";
var payload = @"[
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/BLAH"",
""content_type"": ""application/octet-stream""
},
{
""subdir"": ""a/b/c/d/""
},
{
""subdir"": ""a/b/c/x/""
}]";
var converter = new StorageFolderPayloadConverter();
var resp = converter.Convert(containerName, folderName, payload);
Assert.AreEqual(1, resp.Objects.Count);
Assert.AreEqual(2, resp.Folders.Count);
Assert.AreEqual("a/b/c", resp.FullName);
Assert.AreEqual("c", resp.Name);
var obj = resp.Objects.First();
Assert.AreEqual("a/b/c/BLAH", obj.Name);
var dNode = resp.Folders.First(f => f.FullName == "a/b/c/d");
var xNode = resp.Folders.First(f => f.FullName == "a/b/c/x");
Assert.AreEqual("d", dNode.Name);
Assert.AreEqual(0, dNode.Folders.Count);
Assert.AreEqual(0, dNode.Objects.Count);
Assert.AreEqual("x", xNode.Name);
Assert.AreEqual(0, xNode.Folders.Count);
Assert.AreEqual(0, xNode.Objects.Count);
}
[TestMethod]
public void CanConvertFolderWithValidJsonFolderObjectAndSubFolders()
{
var containerName = "container";
var folderName = "a/b/c/";
var payload = @"[
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/"",
""content_type"": ""application/octet-stream""
},
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/BLAH"",
""content_type"": ""application/octet-stream""
},
{
""subdir"": ""a/b/c/d/""
},
{
""subdir"": ""a/b/c/x/""
}
]";
var converter = new StorageFolderPayloadConverter();
var resp = converter.Convert(containerName, folderName, payload);
Assert.AreEqual("c", resp.Name);
Assert.AreEqual("a/b/c", resp.FullName);
Assert.AreEqual(1, resp.Objects.Count);
Assert.AreEqual(2, resp.Folders.Count);
var obj = resp.Objects.First();
Assert.AreEqual("a/b/c/BLAH", obj.Name);
var dNode = resp.Folders.First(f => f.FullName == "a/b/c/d");
var xNode = resp.Folders.First(f => f.FullName == "a/b/c/x");
Assert.AreEqual("d", dNode.Name);
Assert.AreEqual(0, dNode.Folders.Count);
Assert.AreEqual(0, dNode.Objects.Count);
Assert.AreEqual("x", xNode.Name);
Assert.AreEqual(0, xNode.Folders.Count);
Assert.AreEqual(0, xNode.Objects.Count);
}
}
}

View File

@ -373,17 +373,15 @@ namespace Openstack.Test.Storage
if (objectName != null)
{
if (!this.Objects.ContainsKey(objectName))
{
return TestHelper.CreateResponse(HttpStatusCode.NotFound);
}
var obj = this.Objects[objectName];
var objectHeaders = GenerateObjectResponseHeaders(obj);
return TestHelper.CreateResponse(HttpStatusCode.OK, objectHeaders, obj.Content);
return this.GetObject(objectName);
}
var query = this.Uri.ParseQueryString();
if (query.HasKeys() && query["prefix"] != null && query["delimiter"] != null)
{
return this.GetObject(query["prefix"]);
}
if (!this.Containers.ContainsKey(containerName))
{
return TestHelper.CreateResponse(HttpStatusCode.NotFound);
@ -396,6 +394,19 @@ namespace Openstack.Test.Storage
return TestHelper.CreateResponse(HttpStatusCode.OK, headers, content);
}
public IHttpResponseAbstraction GetObject(string objectName)
{
if (!this.Objects.ContainsKey(objectName))
{
return TestHelper.CreateResponse(HttpStatusCode.NotFound);
}
var obj = this.Objects[objectName];
var objectHeaders = GenerateObjectResponseHeaders(obj);
return TestHelper.CreateResponse(HttpStatusCode.OK, objectHeaders, obj.Content);
}
public string GetContainerName(string[] uriSegments)
{
return uriSegments.Count() < 4 ? null : uriSegments[3].TrimEnd('/');

View File

@ -218,6 +218,88 @@ namespace Openstack.Test.Storage
await client.GetStorageObject(containerName, string.Empty);
}
[TestMethod]
public async Task CanGetStorageFolder()
{
var containerName = "TestContainer";
var folderName = "TestFolder/";
var obj = new StorageFolder(folderName, new List<StorageFolder>());
this.ServicePocoClient.GetStorageFolderDelegate = (s, s1) =>
{
Assert.AreEqual(s, containerName);
Assert.AreEqual(s1, folderName);
return Task.Factory.StartNew(() => obj);
};
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
var resp = await client.GetStorageFolder(containerName, folderName);
Assert.AreEqual(obj, resp);
}
[TestMethod]
public async Task CanGetStorageFolderWithoutTrailingSlash()
{
var containerName = "TestContainer";
var folderName = "TestFolder";
var obj = new StorageFolder(folderName, new List<StorageFolder>());
this.ServicePocoClient.GetStorageFolderDelegate = (s, s1) =>
{
Assert.AreEqual(s, containerName);
Assert.AreEqual(s1, folderName +"/");
return Task.Factory.StartNew(() => obj);
};
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
var resp = await client.GetStorageFolder(containerName, folderName);
Assert.AreEqual(obj, resp);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task GettingStorageFolderWithNullContainerNameThrows()
{
var folderName = "TestFolder";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.GetStorageFolder(null, folderName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task GettingStorageFolderWithEmptyContainerNameThrows()
{
var folderName = "TestFolder";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.GetStorageFolder(string.Empty, folderName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task GettingStorageFolderWithNullObjectNameThrows()
{
var containerName = "TestContainer";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.GetStorageFolder(containerName, null);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task GettingStorageFolderWithEmptyObjectNameThrows()
{
var containerName = "TestContainer";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.GetStorageFolder(containerName, string.Empty);
}
[TestMethod]
public async Task CanCreateStorageObjects()
{
@ -304,6 +386,99 @@ namespace Openstack.Test.Storage
await client.CreateStorageObject(containerName, objectName, null, new MemoryStream());
}
[TestMethod]
public async Task CanCreateStorageFolder()
{
var containerName = "TestContainer";
var folderName = "TestFolder/";
var obj = new StorageFolder(folderName, new List<StorageFolder>());
this.ServicePocoClient.CreateStorageFolderDelegate = async (s, s1) =>
{
await Task.Run(() =>
{
Assert.AreEqual(s1, folderName);
Assert.AreEqual(s, containerName);
});
};
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
public async Task CanCreateStorageFolderWithoutTrailingSlash()
{
var containerName = "TestContainer";
var folderName = "TestFolder";
var obj = new StorageFolder(folderName, new List<StorageFolder>());
this.ServicePocoClient.CreateStorageFolderDelegate = async (s, s1) =>
{
await Task.Run(() =>
{
Assert.AreEqual(s1, folderName +"/");
Assert.AreEqual(s, containerName);
});
};
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task CreatingStorageFolderWithInvalidFolderNameThrows()
{
var containerName = "someContainer";
var folderName = "Test//Folder";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task CreatingStorageFolderWithNullContainerNameThrows()
{
var folderName = "TestFolder";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.CreateStorageFolder(null, folderName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task CreatingStorageFolderWithEmptyContainerNameThrows()
{
var folderName = "TestFolder";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.CreateStorageFolder(string.Empty, folderName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task CreatingStorageFolderWithNullObjectNameThrows()
{
var containerName = "TestContainer";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.CreateStorageFolder(containerName, null);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task CreatingStorageFolderWithEmptyObjectNameThrows()
{
var containerName = "TestContainer";
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.CreateStorageFolder(containerName, string.Empty);
}
[TestMethod]
public async Task CanCreateStorageContainers()
{
@ -490,6 +665,76 @@ namespace Openstack.Test.Storage
await client.DeleteStorageObject("TestContainer", string.Empty);
}
[TestMethod]
public async Task CanDeleteStorageFolder()
{
var containerName = "TestContainer";
var folderName = "TestFolder/";
this.ServicePocoClient.DeleteStorageFolderDelegate = async (s, s1) =>
{
await Task.Run(() =>
{
Assert.AreEqual(s, containerName);
Assert.AreEqual(s1, folderName);
});
};
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.DeleteStorageFolder(containerName, folderName);
}
[TestMethod]
public async Task CanDeleteStorageFolderWithoutTrailingSlash()
{
var containerName = "TestContainer";
var folderName = "TestFolder";
this.ServicePocoClient.DeleteStorageFolderDelegate = async (s, s1) =>
{
await Task.Run(() =>
{
Assert.AreEqual(s, containerName);
Assert.AreEqual(s1, folderName +"/");
});
};
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.DeleteStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task DeletingStorageFolderWithNullContainerNameThrows()
{
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.DeleteStorageFolder(null, "TestFolder");
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task DeletingStorageFolderWithEmptyContainerNameThrows()
{
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.DeleteStorageFolder(string.Empty, "TestFolder");
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task DeletingStorageFolderWithNullObjectNameThrows()
{
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.DeleteStorageFolder("TestContainer", null);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task DeletingStorageFolderWithEmptyObjectNameThrows()
{
var client = new StorageServiceClient(GetValidCreds(), CancellationToken.None);
await client.DeleteStorageFolder("TestContainer", string.Empty);
}
[TestMethod]
public async Task CanUpdateStorageObject()
{

View File

@ -551,6 +551,233 @@ namespace Openstack.Test.Storage
await client.GetStorageObject("container", string.Empty);
}
#endregion
#region Get Storage Folder Tests
[TestMethod]
public async Task CanGetStorageFolderWithOkResponseAndNoSubFolders()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var headers = new HttpHeadersAbstraction()
{
{"X-Container-Bytes-Used", "1234"},
{"X-Container-Object-Count", "1"}
};
var payload = @"[
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/BLAH"",
""content_type"": ""application/octet-stream""
}]";
var content = TestHelper.CreateStream(payload);
var restResp = new HttpResponseAbstraction(content, headers, HttpStatusCode.OK);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
var result = await client.GetStorageFolder(containerName, folderName);
Assert.IsNotNull(result);
Assert.AreEqual("a/b/c", result.FullName);
Assert.AreEqual("c", result.Name);
Assert.IsNotNull(result.Objects);
Assert.AreEqual(1, result.Objects.Count());
Assert.IsNotNull(result.Folders);
Assert.AreEqual(0, result.Folders.Count());
}
[TestMethod]
public async Task CanGetStorageFolderWithNoContentResponse()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var headers = new HttpHeadersAbstraction()
{
{"X-Container-Bytes-Used", "1234"},
{"X-Container-Object-Count", "1"}
};
var restResp = new HttpResponseAbstraction(new MemoryStream(), headers, HttpStatusCode.NoContent);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
var result = await client.GetStorageFolder(containerName, folderName);
Assert.IsNotNull(result);
Assert.AreEqual("a/b/c", result.FullName);
Assert.AreEqual("c", result.Name);
Assert.IsNotNull(result.Objects);
Assert.AreEqual(0, result.Objects.Count());
Assert.IsNotNull(result.Folders);
Assert.AreEqual(0, result.Folders.Count());
}
[TestMethod]
public async Task CanGetStorageFolderWithOkResponseAndSubFolders()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var headers = new HttpHeadersAbstraction()
{
{"X-Container-Bytes-Used", "1234"},
{"X-Container-Object-Count", "1"}
};
var payload = @"[
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/"",
""content_type"": ""application/octet-stream""
},
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/BLAH"",
""content_type"": ""application/octet-stream""
},
{
""subdir"": ""a/b/c/d/""
},
{
""subdir"": ""a/b/c/x/""
}
]";
var content = TestHelper.CreateStream(payload);
var restResp = new HttpResponseAbstraction(content, headers, HttpStatusCode.OK);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
var resp = await client.GetStorageFolder(containerName, folderName);
Assert.AreEqual("c", resp.Name);
Assert.AreEqual("a/b/c", resp.FullName);
Assert.AreEqual(1, resp.Objects.Count);
Assert.AreEqual(2, resp.Folders.Count);
var obj = resp.Objects.First();
Assert.AreEqual("a/b/c/BLAH", obj.Name);
var dNode = resp.Folders.First(f => f.FullName == "a/b/c/d");
var xNode = resp.Folders.First(f => f.FullName == "a/b/c/x");
Assert.AreEqual("d", dNode.Name);
Assert.AreEqual(0, dNode.Folders.Count);
Assert.AreEqual(0, dNode.Objects.Count);
Assert.AreEqual("x", xNode.Name);
Assert.AreEqual(0, xNode.Folders.Count);
Assert.AreEqual(0, xNode.Objects.Count);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionthrownWhenGettingAStorageFolderThatDoesNotExistAndCannotBeInferred()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var headers = new HttpHeadersAbstraction()
{
{"X-Container-Bytes-Used", "1234"},
{"X-Container-Object-Count", "1"}
};
var payload = @"[]";
var content = TestHelper.CreateStream(payload);
var restResp = new HttpResponseAbstraction(content, headers, HttpStatusCode.OK);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
var resp = await client.GetStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionthrownWhenGettingAStorageFolderThatDoesNotExist()
{
var containerName = "TestContainer";
var fodlerName = "a/b/b/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.NotFound);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.GetStorageFolder(containerName, fodlerName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionthrownWhenGettingAStoragFolderAndNotAuthed()
{
var containerName = "TestContainer";
var fodlerName = "a/b/b/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.Unauthorized);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.GetStorageFolder(containerName, fodlerName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionthrownWhenGettingAStorageFolderAndServerError()
{
var containerName = "TestContainer";
var fodlerName = "a/b/b/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.InternalServerError);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.GetStorageFolder(containerName, fodlerName);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task CannotGetStorageFolderWithNullContainerName()
{
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
await client.GetStorageFolder(null, "a/b/c/");
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public async Task CannotGetStorageFolderWithNullFolderName()
{
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
await client.GetStorageFolder("container", null);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task CannotGetStorageFolderWithEmptyContainerName()
{
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
await client.GetStorageFolder(string.Empty, "a/b/c/");
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public async Task CannotGetStorageFolderWithEmptyFolderName()
{
var client = new StorageServicePocoClientFactory().Create(GetValidContext()) as StorageServicePocoClient;
await client.GetStorageFolder("container", string.Empty);
}
#endregion
#region Create Storage Object Tests
@ -674,6 +901,109 @@ namespace Openstack.Test.Storage
#endregion
#region Create Storage Folder Tests
[TestMethod]
public async Task CanCreateStorageFolderWithCreatedResponse()
{
var containerName = "TestContainer";
var folderName = "a/b/b/";
var headers = new HttpHeadersAbstraction()
{
{"Content-Length", "1234"},
{"Content-Type", "application/octet-stream"},
{"Last-Modified", "Wed, 12 Mar 2014 23:42:23 GMT"},
{"ETag", "d41d8cd98f00b204e9800998ecf8427e"}
};
var restResp = new HttpResponseAbstraction(new MemoryStream(), headers, HttpStatusCode.Created);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenCreatingaStorageFolderMissingLength()
{
var containerName = "TestContainer";
var folderName = "a/b/b/";
var headers = new HttpHeadersAbstraction()
{
{"Content-Length", "1234"},
{"Content-Type", "application/octet-stream"},
{"Last-Modified", "Wed, 12 Mar 2014 23:42:23 GMT"},
{"ETag", "d41d8cd98f00b204e9800998ecf8427e"}
};
var restResp = new HttpResponseAbstraction(new MemoryStream(), headers, HttpStatusCode.LengthRequired);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenCreatingaStorageFolderWithBadETag()
{
var containerName = "TestContainer";
var folderName = "a/b/b/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), (HttpStatusCode)422);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenCreatingaStorageFolderWithBadAuth()
{
var containerName = "TestContainer";
var folderName = "a/b/b/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.Unauthorized);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenCreatingaStorageFolderHasInternalServerError()
{
var containerName = "TestContainer";
var folderName = "a/b/b/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.InternalServerError);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.CreateStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenCreatingaStorageFolderTimesOut()
{
var containerName = "TestContainer";
var folderName = "a/b/b/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.RequestTimeout);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.CreateStorageFolder(containerName, folderName);
}
#endregion
#region Create Storage Container Tests
[TestMethod]
@ -881,6 +1211,94 @@ namespace Openstack.Test.Storage
#endregion
#region Delete Storage Folder Tests
[TestMethod]
public async Task CanDeleteStorageFolderWithNoContentResponse()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.NoContent);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.DeleteStorageFolder(containerName, folderName);
}
[TestMethod]
public async Task CanDeleteStorageFolderWithOkResponse()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.OK);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.DeleteStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenDeletingAStorageFolderThatHasChildren()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var headers = new HttpHeadersAbstraction()
{
{"X-Container-Bytes-Used", "1234"},
{"X-Container-Object-Count", "1"}
};
var payload = @"[
{
""hash"": ""d41d8cd98f00b204e9800998ecf8427e"",
""last_modified"": ""2014-03-07T21:31:31.588170"",
""bytes"": 0,
""name"": ""a/b/c/BLAH"",
""content_type"": ""application/octet-stream""
}]";
var content = TestHelper.CreateStream(payload);
var restResp = new HttpResponseAbstraction(content, headers, HttpStatusCode.OK);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.DeleteStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenDeletingAStorageFolderWithBadAuth()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.Unauthorized);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.DeleteStorageFolder(containerName, folderName);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionThrownWhenDeletingAStorageFolderWithInternalServerError()
{
var containerName = "TestContainer";
var folderName = "a/b/c/";
var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.InternalServerError);
this.StorageServiceRestClient.Response = restResp;
var client = new StorageServicePocoClient(GetValidContext());
await client.DeleteStorageFolder(containerName, folderName);
}
#endregion
#region Update Storage Container Tests
[TestMethod]

View File

@ -543,6 +543,7 @@ namespace Openstack.Test.Storage
#region Get Storage Object Tests
[TestMethod]
public async Task GetStorageObjectIncludesAuthHeader()
{
var containerName = "newContainer";
@ -1289,5 +1290,96 @@ namespace Openstack.Test.Storage
}
#endregion
#region Get Storage Folder Tests
[TestMethod]
public async Task GetStorageFolderIncludesAuthHeader()
{
var containerName = "newContainer";
var folderName = "newFolder";
var client =
new StorageServiceRestClient(GetValidContext());
await client.GetFolder(containerName, folderName);
Assert.IsTrue(this.simulator.Headers.ContainsKey("X-Auth-Token"));
Assert.AreEqual(this.authId, this.simulator.Headers["X-Auth-Token"]);
}
[TestMethod]
public async Task GetStorageFolderFormsCorrectUrlAndMethod()
{
var containerName = "newContainer";
var folderName = "a/b/b/";
var client =
new StorageServiceRestClient(GetValidContext());
await client.GetFolder(containerName, folderName);
Assert.AreEqual(string.Format("{0}/{1}?prefix={2}&delimiter=/", endpoint, containerName, folderName), this.simulator.Uri.ToString());
Assert.AreEqual(HttpMethod.Get, this.simulator.Method);
}
[TestMethod]
public async Task ErrorIsReturnedWhenFolderIsNotFound()
{
var containerName = "newContainer";
var folderName = "a/b/b/";
var client =
new StorageServiceRestClient(GetValidContext());
var resp = await client.GetFolder(containerName, folderName);
Assert.AreEqual(HttpStatusCode.NotFound, resp.StatusCode);
}
[TestMethod]
public async Task CanGetStorageFolder()
{
var containerName = "newContainer";
var folderName = "a/b/b/";
var content = TestHelper.CreateStream(string.Empty);
content.Position = 0;
this.simulator.Objects.Add(folderName, new StorageRestSimulator.StorageItem(folderName) { Content = content });
var client =
new StorageServiceRestClient(GetValidContext());
var resp = await client.GetFolder(containerName, folderName);
Assert.AreEqual(HttpStatusCode.OK, resp.StatusCode);
}
[TestMethod]
public async Task CanGetStorageFolderWithMetadata()
{
var containerName = "newContainer";
var folderName = "a/b/b/";
var content = TestHelper.CreateStream(string.Empty);
content.Position = 0;
var metaData = new Dictionary<string, string> { { "X-Object-Meta-Test1", "Test1" }, { "X-Object-Meta-Test2", "Test2" } };
this.simulator.Objects.Add(folderName, new StorageRestSimulator.StorageItem(folderName) { MetaData = metaData, Content = content });
var client =
new StorageServiceRestClient(GetValidContext());
var resp = await client.GetFolder(containerName, folderName);
Assert.AreEqual(HttpStatusCode.OK, resp.StatusCode);
Assert.IsTrue(resp.Headers.Any(kvp => kvp.Key == "X-Object-Meta-Test2"));
Assert.AreEqual("Test2", resp.Headers.First(kvp => kvp.Key == "X-Object-Meta-Test2").Value.First());
}
#endregion
}
}

View File

@ -27,16 +27,22 @@ namespace Openstack.Test.Storage
public Func<StorageContainer, Task<StorageContainer>> CreateStorageContainerDelegate { get; set; }
public Func<string, string, Task> CreateStorageFolderDelegate { get; set; }
public Func<string, Task<StorageContainer>> GetStorageContainerDelegate { get; set; }
public Func<Task<StorageAccount>> GetStorageAccountDelegate { get; set; }
public Func<string, string, Task<StorageObject>> GetStorageObjectDelegate { get; set; }
public Func<string, string, Task<StorageFolder>> GetStorageFolderDelegate { get; set; }
public Func<string, string, Stream, Task<StorageObject>> DownloadStorageObjectDelegate { get; set; }
public Func<string, string, Task> DeleteStorageObjectDelegate { get; set; }
public Func<string, string, Task> DeleteStorageFolderDelegate { get; set; }
public Func<StorageObject, Task> UpdateStorageObjectDelegate { get; set; }
public Func<string, Task> DeleteStorageConainerDelegate { get; set; }
@ -92,6 +98,21 @@ namespace Openstack.Test.Storage
{
await this.UpdateStorageObjectDelegate(obj);
}
public async Task<StorageFolder> GetStorageFolder(string containerName, string folderName)
{
return await this.GetStorageFolderDelegate(containerName, folderName);
}
public async Task CreateStorageFolder(string containerName, string folderName)
{
await this.CreateStorageFolderDelegate(containerName, folderName);
}
public async Task DeleteStorageFolder(string containerName, string folderName)
{
await this.DeleteStorageFolderDelegate(containerName, folderName);
}
}
public class TestStorageServicePocoClientFactory : IStorageServicePocoClientFactory

View File

@ -41,6 +41,11 @@ namespace Openstack.Test.Storage
return Task.Factory.StartNew(() => Response);
}
public Task<IHttpResponseAbstraction> GetFolder(string containerName, string folderName)
{
return Task.Factory.StartNew(() => Response);
}
public Task<IHttpResponseAbstraction> GetContainer(string containerName)
{
return Task.Factory.StartNew(() => Response);

View File

@ -88,6 +88,7 @@
<Compile Include="Identity\OpenstackServiceEndpointPayloadConverter.cs" />
<Compile Include="Identity\OpenstackServiceEndpointResolver.cs" />
<Compile Include="ServiceRegistrar.cs" />
<Compile Include="Storage\FolderNameValidator.cs" />
<Compile Include="Storage\ContainerNameValidator.cs" />
<Compile Include="Storage\IStorageAccountPayloadConverter.cs" />
<Compile Include="Storage\IStorageFolderPayloadConverter.cs" />

View File

@ -30,6 +30,7 @@ namespace Openstack
manager.RegisterServiceInstance(typeof(IStorageServicePocoClientFactory), new StorageServicePocoClientFactory());
manager.RegisterServiceInstance(typeof(IStorageServiceRestClientFactory), new StorageServiceRestClientFactory());
manager.RegisterServiceInstance(typeof(IStorageContainerNameValidator), new StorageContainerNameValidator());
manager.RegisterServiceInstance(typeof(IStorageFolderNameValidator), new StorageFolderNameValidator());
//Identity related clients/services
manager.RegisterServiceInstance(typeof(IIdentityServicePocoClientFactory), new IdentityServicePocoClientFactory());

View File

@ -0,0 +1,45 @@
// /* ============================================================================
// Copyright 2014 Hewlett Packard
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ============================================================================ */
using System.Text.RegularExpressions;
namespace Openstack.Storage
{
/// <summary>
/// Validates a container name.
/// </summary>
public interface IStorageFolderNameValidator
{
/// <summary>
/// Validates a folder name.
/// </summary>
/// <param name="folderName">The name to validate.</param>
/// <returns>A value indicating if the the name could be validated.</returns>
bool Validate(string folderName);
}
/// <inheritdoc/>
internal class StorageFolderNameValidator : IStorageFolderNameValidator
{
/// <inheritdoc/>
public bool Validate(string folderName)
{
//Folder names cannot have consecutive slashes in their names.
//This is not a swift limitation, but it's good practice, and helps simplify things in the rest of the client.
return !Regex.IsMatch(folderName, @"/{2,}");
}
}
}

View File

@ -20,6 +20,20 @@ namespace Openstack.Storage
public interface IStorageFolderPayloadConverter
{
/// <summary>
/// Converts a list of storage objects into a deep folder structure.
/// </summary>
/// <param name="objects">The list of objects to convert.</param>
/// <returns>A deep folder structure.</returns>
IEnumerable<StorageFolder> Convert(IEnumerable<StorageObject> objects);
/// <summary>
/// Converts a Json payload into a shallow storage folder object.
/// </summary>
/// <param name="containerName">The name of the parent container.</param>
/// <param name="folderName">The full name of the folder.</param>
/// <param name="payload">The Json payload.</param>
/// <returns>A shallow storage folder object.</returns>
StorageFolder Convert(string containerName, string folderName, string payload);
}
}

View File

@ -18,6 +18,7 @@ namespace Openstack.Storage
{
using System.Collections.Generic;
using Openstack.Common.Http;
using Newtonsoft.Json.Linq;
/// <summary>
/// Converts a Json payload into a storage object.
@ -40,5 +41,13 @@ namespace Openstack.Storage
/// <param name="headers">The collection of headers</param>
/// <returns>The storage object.</returns>
StorageObject Convert(string containerName, string objectName, IHttpHeadersAbstraction headers);
/// <summary>
/// Converts a Json token into a storage object.
/// </summary>
/// <param name="obj">The token.</param>
/// <param name="containerName">The name of the parent container.</param>
/// <returns>The storage object.</returns>
StorageObject ConvertSingle(JToken obj, string containerName);
}
}

View File

@ -14,13 +14,12 @@
// limitations under the License.
// ============================================================================ */
using System;
namespace Openstack.Storage
{
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
/// <summary>
/// Client that can interact with an Openstack storage service.
@ -122,5 +121,29 @@ namespace Openstack.Storage
/// <param name="obj">The object to update.</param>
/// <returns>An async task.</returns>
Task UpdateStorageObject(StorageObject obj);
/// <summary>
/// Gets a storage folder from the remote Openstack instance. The returned folder is a shallow object graph representation.
/// </summary>
/// <param name="containerName">The name of the parent container.</param>
/// <param name="folderName">The name of the folder to get.</param>
/// <returns>A shallow object representation of the folder and it's contained objects and sub folders.</returns>
Task<StorageFolder> GetStorageFolder(string containerName, string folderName);
/// <summary>
/// Creates a storage folder on the remote Openstack instance.
/// </summary>
/// <param name="containerName">The name of the parent container.</param>
/// <param name="folderName">The name of the folder to create.</param>
/// <returns>An async task.</returns>
Task CreateStorageFolder(string containerName, string folderName);
/// <summary>
/// Deletes a storage folder from the remote Openstack instance.
/// </summary>
/// <param name="containerName">The name of the parent container.</param>
/// <param name="folderName">The name of the folder to delete.</param>
/// <returns>An async task.</returns>
Task DeleteStorageFolder(string containerName, string folderName);
}
}

View File

@ -99,5 +99,28 @@ namespace Openstack.Storage
/// <returns>An async task.</returns>
Task UpdateStorageObject(StorageObject obj);
/// <summary>
/// Gets a storage folder from the remote Openstack instance. The returned folder is a shallow object graph representation.
/// </summary>
/// <param name="containerName">The name of the parent container.</param>
/// <param name="folderName">The name of the folder to get.</param>
/// <returns>A shallow object representation of the folder and it's contained objects and sub folders.</returns>
Task<StorageFolder> GetStorageFolder(string containerName, string folderName);
/// <summary>
/// Creates a storage folder on the remote Openstack instance.
/// </summary>
/// <param name="containerName">The name of the parent container.</param>
/// <param name="folderName">The name of the folder to create.</param>
/// <returns>An async task.</returns>
Task CreateStorageFolder(string containerName, string folderName);
/// <summary>
/// Deletes a storage folder from the remote Openstack instance.
/// </summary>
/// <param name="containerName">The name of the parent container.</param>
/// <param name="folderName">The name of the folder to delete.</param>
/// <returns>An async task.</returns>
Task DeleteStorageFolder(string containerName, string folderName);
}
}

View File

@ -52,6 +52,14 @@ namespace Openstack.Storage
/// <returns>The Http response from the remote service.</returns>
Task<IHttpResponseAbstraction> GetObject(string containerName, string objectName);
/// <summary>
/// Gets a storage folder from the remote Openstack instance.
/// </summary>
/// <param name="containerName">The name of the parent storage container.</param>
/// <param name="folderName">The name of the folder.</param>
/// <returns>The Http response from the remote service.</returns>
Task<IHttpResponseAbstraction> GetFolder(string containerName, string folderName);
/// <summary>
/// Gets a storage container from the remote Openstack instance.
/// </summary>

View File

@ -20,19 +20,45 @@ namespace Openstack.Storage
using Openstack.Common;
using System.Collections.Generic;
/// <summary>
/// Represents a storage folder on a remote Openstack instance.
/// </summary>
public class StorageFolder
{
/// <summary>
/// The "friendly" name of the folder
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The full name/path of the folder.
/// </summary>
public string FullName { get; private set; }
/// <summary>
/// A collection of sub-folders inside this folder.
/// </summary>
public ICollection<StorageFolder> Folders { get; private set; }
/// <summary>
/// A collection of objects inside this folder.
/// </summary>
public ICollection<StorageObject> Objects { get; private set; }
/// <summary>
/// Creates a new instance of the StorageFolder class.
/// </summary>
/// <param name="fullName">The full name/path of the folder.</param>
/// <param name="folders">A collection of sub-folders that are inside this folder.</param>
public StorageFolder(string fullName, IEnumerable<StorageFolder> folders) : this(fullName, folders, new List<StorageObject>())
{ }
/// <summary>
/// Creates a new instance of the StorageFolder class.
/// </summary>
/// <param name="fullName">The full name/path of the folder.</param>
/// <param name="folders">A collection of sub-folders that are inside this folder.</param>
/// <param name="objects">A collection of objects that are inside this folder.</param>
public StorageFolder(string fullName, IEnumerable<StorageFolder> folders, IEnumerable<StorageObject> objects )
{
fullName.AssertIsNotNullOrEmpty("fullName", "Cannot create a storage folder with a null or empty full name.");
@ -45,6 +71,11 @@ namespace Openstack.Storage
this.Objects = objects.ToList();
}
/// <summary>
/// Extracts the "friendly" name from the folders full name/path.
/// </summary>
/// <param name="fullFolderName">The full name/path of the folder.</param>
/// <returns>The "friendly" name of the folder. (e.g. "b" if the folder path is "a/b/")</returns>
internal static string ExtractFolderName(string fullFolderName)
{
var fullName = fullFolderName.Trim('/');

View File

@ -17,16 +17,22 @@
namespace Openstack.Storage
{
using System;
using System.IO;
using System.Web;
using System.Linq;
using Openstack.Common;
using Newtonsoft.Json.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Openstack.Common.ServiceLocation;
/// <inheritdoc/>
internal class StorageFolderPayloadConverter : IStorageFolderPayloadConverter
{
internal const string consecutiveSlashRegex = @"/{2,}";
/// <inheritdoc/>
public IEnumerable<StorageFolder> Convert(IEnumerable<StorageObject> objects)
{
objects.AssertIsNotNull("objects", "Cannot build folders with a null object collection.");
@ -84,5 +90,64 @@ namespace Openstack.Storage
}
return folders;
}
/// <inheritdoc/>
public StorageFolder Convert(string containerName, string folderName, string payload)
{
if (String.IsNullOrEmpty(payload))
{
return new StorageFolder(folderName, new List<StorageFolder>());
}
try
{
var array = JArray.Parse(payload);
if (array.Count == 0)
{
throw new InvalidDataException("Folder cannot be converted. The folder does not exist, has no children, and cannot be inferred.");
}
var subFolders = array.Where(t => t["subdir"] != null);
var rawObjects = array.Where(t => t["subdir"] == null);
var objectConverter = ServiceLocator.Instance.Locate<IStorageObjectPayloadConverter>();
var objects = rawObjects.Select(t => objectConverter.ConvertSingle(t,containerName)).ToList();
objects.RemoveAll(o => string.Compare(o.Name, folderName, StringComparison.InvariantCulture) == 0);
return new StorageFolder(folderName, subFolders.Select(ParseSubFolder), objects);
}
catch (HttpParseException)
{
throw;
}
catch (InvalidDataException)
{
throw;
}
catch (Exception ex)
{
throw new HttpParseException(string.Format("Storage Container payload could not be parsed. Payload: '{0}'", payload), ex);
}
}
/// <summary>
/// Converts a JToken object into a StorageFolder object.
/// </summary>
/// <param name="token">The JToken to convert.</param>
/// <returns>A StorageFolder object.</returns>
internal StorageFolder ParseSubFolder(JToken token)
{
try
{
var fullName = (string) token["subdir"];
return new StorageFolder(fullName, new List<StorageFolder>());
}
catch (Exception ex)
{
throw new HttpParseException(string.Format("Storage Folder payload could not be parsed. Payload: '{0}'", token), ex);
}
}
}
}

View File

@ -56,13 +56,8 @@ namespace Openstack.Storage
return objects;
}
/// <summary>
/// Converts a Json token into a storage object.
/// </summary>
/// <param name="obj">The token.</param>
/// <param name="containerName">The name of the parent container.</param>
/// <returns>The storage object.</returns>
internal StorageObject ConvertSingle(JToken obj, string containerName)
/// <inheritdoc/>
public StorageObject ConvertSingle(JToken obj, string containerName)
{
string name = string.Empty;

View File

@ -121,6 +121,47 @@ namespace Openstack.Storage
await client.UpdateStorageObject(obj);
}
/// <inheritdoc/>
public async Task<StorageFolder> GetStorageFolder(string containerName, string folderName)
{
containerName.AssertIsNotNullOrEmpty("containerName", "Cannot get a storage folder with a container name that is null or empty.");
folderName.AssertIsNotNullOrEmpty("folderName", "Cannot get a storage folder with a name that is null or empty.");
folderName = EnsureTrailingSlashOnFolderName(folderName);
var client = this.GetPocoClient();
return await client.GetStorageFolder(containerName, folderName);
}
/// <inheritdoc/>
public async Task CreateStorageFolder(string containerName, string folderName)
{
containerName.AssertIsNotNullOrEmpty("containerName", "Cannot create a storage folder with a container name that is null or empty.");
folderName.AssertIsNotNullOrEmpty("folderName", "Cannot create a storage folder with a name that is null or empty.");
folderName = EnsureTrailingSlashOnFolderName(folderName);
var validator = ServiceLocator.Instance.Locate<IStorageFolderNameValidator>();
if (!validator.Validate(folderName))
{
throw new ArgumentException(string.Format("Folder name '{0}' is invalid. Folder names cannot includes consecutive slashes.", folderName), "folderName");
}
var client = this.GetPocoClient();
await client.CreateStorageFolder(containerName, folderName);
}
/// <inheritdoc/>
public async Task DeleteStorageFolder(string containerName, string folderName)
{
containerName.AssertIsNotNullOrEmpty("containerName", "Cannot delete a storage folder with a container name that is null or empty.");
folderName.AssertIsNotNullOrEmpty("folderName", "Cannot delete a storage folder with a name that is null or empty.");
folderName = EnsureTrailingSlashOnFolderName(folderName);
var client = this.GetPocoClient();
await client.DeleteStorageFolder(containerName, folderName);
}
/// <inheritdoc/>
public async Task<StorageContainer> GetStorageContainer(string containerName)
{
@ -188,5 +229,19 @@ namespace Openstack.Storage
{
return ServiceLocator.Instance.Locate<IStorageServicePocoClientFactory>().Create(this.Context);
}
/// <summary>
/// Ensures that a folder name has a single trailing slash on the end.
/// </summary>
/// <param name="folderName">The folder name.</param>
/// <returns>The folder name with a slash on the end.</returns>
internal string EnsureTrailingSlashOnFolderName(string folderName)
{
if (!folderName.EndsWith("/"))
{
folderName += "/";
}
return folderName;
}
}
}

View File

@ -21,9 +21,11 @@ namespace Openstack.Storage
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Collections.Generic;
using Openstack.Common;
using Openstack.Common.ServiceLocation;
/// <inheritdoc/>
internal class StorageServicePocoClient : IStorageServicePocoClient
{
@ -215,6 +217,68 @@ namespace Openstack.Storage
}
}
/// <inheritdoc/>
public async Task<StorageFolder> GetStorageFolder(string containerName, string folderName)
{
containerName.AssertIsNotNullOrEmpty("containerName", "Cannot get a storage folder with a container name that is null or empty.");
folderName.AssertIsNotNullOrEmpty("folderName", "Cannot get a storage folder with a folder name that is null or empty.");
var client = this.GetRestClient();
var resp = await client.GetFolder(containerName, folderName);
if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.NoContent)
{
throw new InvalidOperationException(string.Format("Failed to get storage folder '{0}'. The remote server returned the following status code: '{1}'.", folderName, resp.StatusCode));
}
try
{
var converter = ServiceLocator.Instance.Locate<IStorageFolderPayloadConverter>();
var folder = converter.Convert(containerName, folderName, await resp.ReadContentAsStringAsync());
return folder;
}
catch (InvalidDataException)
{
throw new InvalidOperationException(string.Format("Failed to get storage folder '{0}'. The requested folder could not be found.", folderName));
}
}
/// <inheritdoc/>
public async Task CreateStorageFolder(string containerName, string folderName)
{
containerName.AssertIsNotNullOrEmpty("containerName", "Cannot create a storage folder with a container name that is null or empty.");
folderName.AssertIsNotNullOrEmpty("folderName", "Cannot create a storage folder with a folder name that is null or empty.");
var client = this.GetRestClient();
var resp = await client.CreateObject(containerName, folderName, new Dictionary<string, string>(), new MemoryStream());
if (resp.StatusCode != HttpStatusCode.Created)
{
throw new InvalidOperationException(string.Format("Failed to create storage folder '{0}'. The remote server returned the following status code: '{1}'.", folderName, resp.StatusCode));
}
}
/// <inheritdoc/>
public async Task DeleteStorageFolder(string containerName, string folderName)
{
containerName.AssertIsNotNullOrEmpty("containerName", "Cannot delete a storage object with a container name that is null or empty.");
folderName.AssertIsNotNullOrEmpty("objectName", "Cannot delete a storage object with a name that is null or empty.");
var folder = await this.GetStorageFolder(containerName, folderName);
if (folder.Folders.Count > 0 || folder.Objects.Count > 0)
{
throw new InvalidOperationException(string.Format("Failed to delete storage folder '{0}'. The folder is not empty and cannot be deleted.", folderName));
}
var client = this.GetRestClient();
var resp = await client.DeleteObject(containerName, folderName);
if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.NoContent)
{
throw new InvalidOperationException(string.Format("Failed to delete storage folder '{0}'. The remote server returned the following status code: '{1}'.", folderName, resp.StatusCode));
}
}
/// <summary>
/// Gets a client that can be used to connect to the REST endpoints of an Openstack storage service.
/// </summary>

View File

@ -22,6 +22,7 @@ namespace Openstack.Storage
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Openstack.Common;
using Openstack.Common.Http;
using Openstack.Common.ServiceLocation;
@ -87,6 +88,21 @@ namespace Openstack.Storage
return await client.SendAsync();
}
/// <inheritdoc/>
public async Task<IHttpResponseAbstraction> GetFolder(string containerName, string folderName)
{
AssertContainerNameIsValid(containerName);
folderName.AssertIsNotNullOrEmpty("folderName","Cannot get a folder with a null or empty folder name.");
var client = this.GetHttpClient(this.context);
var baseUri = CreateRequestUri(GetServiceEndpoint(this.context), containerName);
client.Uri = new Uri(string.Format("{0}?prefix={1}&delimiter=/", baseUri, folderName));
client.Method = HttpMethod.Get;
return await client.SendAsync();
}
/// <inheritdoc/>
public async Task<IHttpResponseAbstraction> GetContainer(string containerName)
{