diff --git a/OpenStack/OpenStack.Test/Storage/StorageServiceClientTests.cs b/OpenStack/OpenStack.Test/Storage/StorageServiceClientTests.cs index 5858164..271e946 100644 --- a/OpenStack/OpenStack.Test/Storage/StorageServiceClientTests.cs +++ b/OpenStack/OpenStack.Test/Storage/StorageServiceClientTests.cs @@ -411,6 +411,55 @@ namespace OpenStack.Test.Storage Assert.AreEqual(obj, resp); } + [TestMethod] + public async Task CanCopyStorageObjects() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var obj = new StorageObject(objectName, targetContainerName, DateTime.UtcNow, "12345", 12345, + "application/octet-stream", new Dictionary()); + + this.ServicePocoClient.CopyStorageObjectDelegate = async (s, container, destinationObjectName) => + { + Assert.AreEqual(container, obj.ContainerName); + Assert.AreEqual(s.Name, obj.Name); + + return await Task.Run(() => obj); + }; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + var resp = await client.CopyStorageObject(containerName, objectName, targetContainerName); + + Assert.AreEqual(obj, resp); + } + + [TestMethod] + public async Task CanCopyStorageObjectsAndChangeName() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetObjectName = "TargetTestObject"; + var targetContainerName = "TargetTestContainer"; + + var obj = new StorageObject(targetObjectName, targetContainerName, DateTime.UtcNow, "12345", 12345, + "application/octet-stream", new Dictionary()); + + this.ServicePocoClient.CopyStorageObjectDelegate = async (s, container, destinationObjectName) => + { + Assert.AreEqual(container, obj.ContainerName); + Assert.AreEqual(destinationObjectName, obj.Name); + + return await Task.Run(() => obj); + }; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + var resp = await client.CopyStorageObject(containerName, objectName, targetContainerName, targetObjectName); + + Assert.AreEqual(obj, resp); + } + [TestMethod] public async Task CreatingAnObjectLargerThanTheThresholdCreatesObjectWithSegments() { @@ -639,6 +688,77 @@ namespace OpenStack.Test.Storage await client.CreateStorageObject(containerName, objectName, null, new MemoryStream()); } + #region Copy Storage Objects Tests + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public async Task CopyStorageObjectsWithNullContainerNameThrows() + { + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + await client.CopyStorageObject(null, objectName, targetContainerName); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task CopyStorageObjectsWithEmptyContainerNameThrows() + { + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + await client.CopyStorageObject(string.Empty, objectName, targetContainerName); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public async Task CopyStorageObjectsWithNullObjectNameThrows() + { + var containerName = "TestContainer"; + var targetContainerName = "TargetTestContainer"; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + await client.CopyStorageObject(containerName, null, targetContainerName); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task CopyStorageObjectsWithEmptyObjectNameThrows() + { + var containerName = "TestContainer"; + var targetContainerName = "TargetTestContainer"; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + await client.CopyStorageObject(containerName, string.Empty, targetContainerName); + + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public async Task CopyStorageObjectsWithNullTargetContainerThrows() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + await client.CopyStorageObject(containerName, objectName, null); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task CopyStorageObjectsWithEmptyTargetContainerThrows() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + + var client = new StorageServiceClient(GetValidCreds(), "Swift", CancellationToken.None, this.ServiceLocator); + await client.CopyStorageObject(containerName, objectName, string.Empty); + } + + #endregion + [TestMethod] public async Task CanCreateStaticStorageManifest() { diff --git a/OpenStack/OpenStack.Test/Storage/StorageServicePocoClientTests.cs b/OpenStack/OpenStack.Test/Storage/StorageServicePocoClientTests.cs index aed2414..13f77c0 100644 --- a/OpenStack/OpenStack.Test/Storage/StorageServicePocoClientTests.cs +++ b/OpenStack/OpenStack.Test/Storage/StorageServicePocoClientTests.cs @@ -1375,6 +1375,162 @@ namespace OpenStack.Test.Storage #endregion + #region Copy Storage Object Tests + + [TestMethod] + public async Task CanCopyStorageObjectWithCreatedResponse() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var headers = new HttpHeadersAbstraction() + { + {"Content-Length", "0"}, + {"Content-Type", "application/octet-stream"}, + {"X-Copied-From-Last-Modified","Wed, 12 Mar 2014 22:42:23 GMT"}, + {"X-Copied-From" , "TestContainer/TestObject"}, + {"Last-Modified", "Wed, 12 Mar 2014 23:42:23 GMT"}, + {"ETag", "d41d8cd98f00b204e9800998ecf8427e"} + }; + + var objRequest = new StorageObject(objectName, containerName); + + var restResp = new HttpResponseAbstraction(new MemoryStream(), headers, HttpStatusCode.Created); + this.StorageServiceRestClient.Responses.Enqueue(restResp); + + var client = new StorageServicePocoClient(GetValidContext(), this.ServiceLocator); + var result = await client.CopyStorageObject(objRequest, targetContainerName); + + Assert.IsNotNull(result); + Assert.AreEqual(objectName, result.Name); + Assert.AreEqual(containerName, result.ContainerName); + Assert.AreEqual(0, result.Length); //length of content stream + Assert.AreEqual("application/octet-stream", result.ContentType); + Assert.AreEqual("d41d8cd98f00b204e9800998ecf8427e", result.ETag); + Assert.AreEqual(DateTime.Parse("Wed, 12 Mar 2014 23:42:23 GMT"), result.LastModified); + } + + [TestMethod] + public async Task CanCopyStorageObjectWithFoldersAndCreatedResponse() + { + var containerName = "TestContainer"; + var objectName = "a/b/TestObject"; + var targetContainerName = "TargetTestContainer"; + + var headers = new HttpHeadersAbstraction() + { + {"Content-Length", "0"}, + {"Content-Type", "application/octet-stream"}, + {"X-Copied-From-Last-Modified","Wed, 12 Mar 2014 22:42:23 GMT"}, + {"X-Copied-From" , "TestContainer/a/b/TestObject"}, + {"Last-Modified", "Wed, 12 Mar 2014 23:42:23 GMT"}, + {"ETag", "d41d8cd98f00b204e9800998ecf8427e"} + }; + + var objRequest = new StorageObject(objectName, containerName); + + var restResp = new HttpResponseAbstraction(new MemoryStream(), headers, HttpStatusCode.Created); + this.StorageServiceRestClient.Responses.Enqueue(restResp); + + var client = new StorageServicePocoClient(GetValidContext(), this.ServiceLocator); + var result = await client.CopyStorageObject(objRequest, targetContainerName); + + Assert.IsNotNull(result); + Assert.AreEqual(objectName, result.FullName); + Assert.AreEqual(containerName, result.ContainerName); + Assert.AreEqual(0, result.Length); + Assert.AreEqual("application/octet-stream", result.ContentType); + Assert.AreEqual("d41d8cd98f00b204e9800998ecf8427e", result.ETag); + Assert.AreEqual(DateTime.Parse("Wed, 12 Mar 2014 23:42:23 GMT"), result.LastModified); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task ExceptionThrownWhenCopyingaStorageObjectWithMissingLength() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var objRequest = new StorageObject(objectName, containerName); + + var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.LengthRequired); + this.StorageServiceRestClient.Responses.Enqueue(restResp); + + var client = new StorageServicePocoClient(GetValidContext(), this.ServiceLocator); + await client.CopyStorageObject(objRequest, targetContainerName); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task ExceptionThrownWhenCopyingaStorageObjectWithBadETag() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var objRequest = new StorageObject(objectName, containerName); + + var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), (HttpStatusCode)422); + this.StorageServiceRestClient.Responses.Enqueue(restResp); + + var client = new StorageServicePocoClient(GetValidContext(), this.ServiceLocator); + await client.CopyStorageObject(objRequest, targetContainerName); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task ExceptionThrownWhenCopyingaStorageObjectWithBadAuth() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var objRequest = new StorageObject(objectName, containerName); + + var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.Unauthorized); + this.StorageServiceRestClient.Responses.Enqueue(restResp); + + var client = new StorageServicePocoClient(GetValidContext(), this.ServiceLocator); + await client.CopyStorageObject(objRequest, targetContainerName); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task ExceptionThrownWhenCopyingaStorageObjectHasInternalServerError() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var objRequest = new StorageObject(objectName, containerName); + + var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.InternalServerError); + this.StorageServiceRestClient.Responses.Enqueue(restResp); + + var client = new StorageServicePocoClient(GetValidContext(), this.ServiceLocator); + await client.CopyStorageObject(objRequest, targetContainerName); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task ExceptionThrownWhenCopyingaStorageObjectTimesOut() + { + var containerName = "TestContainer"; + var objectName = "TestObject"; + var targetContainerName = "TargetTestContainer"; + + var objRequest = new StorageObject(objectName, containerName); + + var restResp = new HttpResponseAbstraction(new MemoryStream(), new HttpHeadersAbstraction(), HttpStatusCode.RequestTimeout); + this.StorageServiceRestClient.Responses.Enqueue(restResp); + + var client = new StorageServicePocoClient(GetValidContext(), this.ServiceLocator); + await client.CopyStorageObject(objRequest, targetContainerName); + } + #endregion + #region Create Storage Folder Tests [TestMethod] diff --git a/OpenStack/OpenStack.Test/Storage/TestStorageServiceClient.cs b/OpenStack/OpenStack.Test/Storage/TestStorageServiceClient.cs index a8ec20c..56b6891 100644 --- a/OpenStack/OpenStack.Test/Storage/TestStorageServiceClient.cs +++ b/OpenStack/OpenStack.Test/Storage/TestStorageServiceClient.cs @@ -44,6 +44,8 @@ namespace OpenStack.Test.Storage public Func, Stream, Task> CreateStorageObjectDelegate { get; set; } + public Func> CopyStorageObjectDelegate { get; set; } + public Func, Stream, int, Task> CreateLargeStorageObjectDelegate { get; set; } public Func> GetStorageObjectDelegate { get; set; } @@ -64,7 +66,6 @@ namespace OpenStack.Test.Storage public Func CreateStorageFolderDelegate { get; set; } - public Func DeleteStorageFolderDelegate { get; set; } public Func GetPublicEndpointDelegate { get; set; } @@ -114,6 +115,11 @@ namespace OpenStack.Test.Storage return await CreateStorageObjectDelegate(containerName, objectName, metadata, content); } + public async Task CopyStorageObject(string containerName, string objectName, string destinationContainerName, string destinationObjectName = null) + { + return await CopyStorageObjectDelegate(containerName, objectName, destinationContainerName, destinationObjectName); + } + public async Task CreateLargeStorageObject(string containerName, string objectName, IDictionary metadata, Stream content, int numberOfsegments) { diff --git a/OpenStack/OpenStack.Test/Storage/TestStorageServicePocoClient.cs b/OpenStack/OpenStack.Test/Storage/TestStorageServicePocoClient.cs index ed7bb5f..66d1abd 100644 --- a/OpenStack/OpenStack.Test/Storage/TestStorageServicePocoClient.cs +++ b/OpenStack/OpenStack.Test/Storage/TestStorageServicePocoClient.cs @@ -26,6 +26,8 @@ namespace OpenStack.Test.Storage { public Func> CreateStorageObjectDelegate { get; set; } + public Func> CopyStorageObjectDelegate { get; set; } + public Func> CreateStorageManifestDelegate { get; set; } public Func> CreateStorageContainerDelegate { get; set; } @@ -59,6 +61,11 @@ namespace OpenStack.Test.Storage return await this.CreateStorageObjectDelegate(obj, content); } + public async Task CopyStorageObject(StorageObject obj, string destinationContainerName, string destinationObjectName = null) + { + return await this.CopyStorageObjectDelegate(obj, destinationContainerName, destinationObjectName); + } + public async Task CreateStorageManifest(StorageManifest manifest) { return await this.CreateStorageManifestDelegate(manifest); diff --git a/OpenStack/OpenStack/Storage/IStorageServiceClient.cs b/OpenStack/OpenStack/Storage/IStorageServiceClient.cs index 031dd54..b8e0cee 100644 --- a/OpenStack/OpenStack/Storage/IStorageServiceClient.cs +++ b/OpenStack/OpenStack/Storage/IStorageServiceClient.cs @@ -100,6 +100,16 @@ namespace OpenStack.Storage /// A storage object. Task CreateStorageObject(string containerName, string objectName, IDictionary metadata, Stream content); + /// + /// Copy a storage object on the remote OpenStack instance. + /// + /// The name of the parent container. + /// The name of the object. + /// The container destination name. + /// The object destination name. + /// A storage object. + Task CopyStorageObject(string containerName, string objectName, string destinationContainerName, string destinationObjectName = null); + /// /// Creates a storage object on the remote OpenStack instance. /// diff --git a/OpenStack/OpenStack/Storage/IStorageServicePocoClient.cs b/OpenStack/OpenStack/Storage/IStorageServicePocoClient.cs index 44b5222..c24249c 100644 --- a/OpenStack/OpenStack/Storage/IStorageServicePocoClient.cs +++ b/OpenStack/OpenStack/Storage/IStorageServicePocoClient.cs @@ -32,6 +32,15 @@ namespace OpenStack.Storage /// A storage object. Task CreateStorageObject(StorageObject obj, Stream content); + /// + /// Copy a storage object on the remote OpenStack instance. + /// + /// The storage object to copy. + /// The container destination name. + /// The destination object name. + /// A storage object. + Task CopyStorageObject(StorageObject obj, string destinationContainerName, string destinationObjectName = null); + /// /// Creates a storage manifest on the remote OpenStack instance. /// diff --git a/OpenStack/OpenStack/Storage/StorageServiceClient.cs b/OpenStack/OpenStack/Storage/StorageServiceClient.cs index 543f50e..2b2a38d 100644 --- a/OpenStack/OpenStack/Storage/StorageServiceClient.cs +++ b/OpenStack/OpenStack/Storage/StorageServiceClient.cs @@ -86,6 +86,18 @@ namespace OpenStack.Storage return await client.CreateStorageObject(requestObject, content); } + /// + public async Task CopyStorageObject(string containerName, string objectName, string destinationContainerName, string destinationObjectName = null) + { + containerName.AssertIsNotNullOrEmpty("containerName", "Cannot copy a storage object with a container name that is null or empty."); + objectName.AssertIsNotNullOrEmpty("objectName", "Cannot copy a storage object with a name that is null or empty."); + destinationContainerName.AssertIsNotNullOrEmpty("destinationContainerName", "Cannot copy a storage object with null or empty destination container."); + + var requestObject = new StorageObject(objectName, containerName); + var client = this.GetPocoClient(); + return await client.CopyStorageObject(requestObject, destinationContainerName, destinationObjectName); + } + /// public async Task CreateStorageContainer(string containerName, IDictionary metadata) { diff --git a/OpenStack/OpenStack/Storage/StorageServicePocoClient.cs b/OpenStack/OpenStack/Storage/StorageServicePocoClient.cs index 41f8a9a..0ba7ae2 100644 --- a/OpenStack/OpenStack/Storage/StorageServicePocoClient.cs +++ b/OpenStack/OpenStack/Storage/StorageServicePocoClient.cs @@ -66,6 +66,39 @@ namespace OpenStack.Storage return respObj; } + /// + public async Task CopyStorageObject(StorageObject obj, string destinationContainerName, string destinationObjectName = null) + { + obj.AssertIsNotNull("obj", "Cannot create a null storage object."); + obj.ContainerName.AssertIsNotNullOrEmpty("obj.ContainerName", "Cannot copy a storage object with a null or empty container name."); + obj.Name.AssertIsNotNullOrEmpty("obj.Name", "Cannot copy a storage object without a name."); + destinationContainerName.AssertIsNotNullOrEmpty("destinationContainerName", "Cannot copy a storage object to a null or empty destination container name."); + + string localDestinationObjectName = null; + + if(!string.IsNullOrEmpty(destinationObjectName)) + { + localDestinationObjectName = destinationObjectName; + } + else + { + localDestinationObjectName = obj.FullName; + } + + var client = this.GetRestClient(); + var resp = await client.CopyObject(obj.ContainerName, obj.FullName, destinationContainerName, localDestinationObjectName); + + if (resp.StatusCode != HttpStatusCode.Created) + { + throw new InvalidOperationException(string.Format("Failed to copy storage object '{0}'. The remote server returned the following status code: '{1}'.", obj.Name, resp.StatusCode)); + } + + var converter = this.ServiceLocator.Locate(); + var respObj = converter.Convert(obj.ContainerName, obj.FullName, resp.Headers); + + return respObj; + } + public async Task CreateStorageManifest(StorageManifest manifest) { manifest.AssertIsNotNull("manifest", "Cannot create a null storage manifest.");