// /* ============================================================================ // 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; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Openstack.Common.Http; namespace Openstack.Test.Storage { public class StorageRestSimulator : DisposableClass, IHttpAbstractionClient { internal class StorageItem { public string Name { get; set; } public IDictionary MetaData { get; set; } public Stream Content { get; set; } public StorageItem() { this.MetaData = new Dictionary(); } public StorageItem(string name) : this() { this.Name = name; } public void ProcessMetaDataFromHeaders(IDictionary headers) { headers.Keys.Where(k => k.ToLowerInvariant().StartsWith("x-object-meta-")).ToList().ForEach(i => this.MetaData.Add(i, headers[i])); } public void LoadContent(Stream content) { var memStream = new MemoryStream(); if (content != null) { content.CopyTo(memStream); memStream.Position = 0; } this.Content = memStream; } } public StorageRestSimulator() { this.Headers = new Dictionary(); this.Containers = new Dictionary(); this.Objects = new Dictionary(); this.Delay = TimeSpan.FromMilliseconds(0); this.IsContainerEmpty = true; } public StorageRestSimulator(CancellationToken token) : this() { } internal Dictionary Containers { get; set; } internal Dictionary Objects { get; set; } public HttpMethod Method { get; set; } public Uri Uri { get; set; } public Stream Content { get; set; } public IDictionary Headers { get; private set; } public string ContentType { get; set; } public TimeSpan Timeout { get; set; } public TimeSpan Delay { get; set; } public bool IsContainerEmpty { get; set; } //public event EventHandler HttpReceiveProgress; //public event EventHandler HttpSendProgress; public Task SendAsync() { if (!this.Headers.ContainsKey("X-Auth-Token") || this.Headers["X-Auth-Token"] != "12345") { return Task.Factory.StartNew(() => TestHelper.CreateResponse(HttpStatusCode.Unauthorized)); } IHttpResponseAbstraction retVal; switch (this.Method.ToString().ToLowerInvariant()) { case "get": retVal = HandleGet(); break; case "post": retVal = HandlePost(); break; case "put": retVal = HandlePut(); break; case "delete": retVal = HandleDelete(); break; case "head": retVal = HandleHead(); break; case "copy": retVal = HandleCopy(); break; default: retVal = TestHelper.CreateErrorResponse(); break; } Thread.Sleep(Delay); return Task.Factory.StartNew(() => retVal); } private IHttpResponseAbstraction HandleCopy() { var containerName = GetContainerName(this.Uri.Segments); var objectName = GetObjectName(this.Uri.Segments); if (containerName == null && objectName == null) { return TestHelper.CreateErrorResponse(); } //cannot copy a container if (objectName == null) { return TestHelper.CreateResponse(HttpStatusCode.MethodNotAllowed); } if (!this.Containers.ContainsKey(containerName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } if (!this.Headers.ContainsKey("Destination")) { return TestHelper.CreateResponse(HttpStatusCode.PreconditionFailed); } if (!this.Objects.ContainsKey(objectName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } var destination = this.Headers["Destination"]; var destinationSegs = destination.Split('/'); if (destinationSegs.Count() < 2) { return TestHelper.CreateResponse(HttpStatusCode.PreconditionFailed); } if (destinationSegs[1] == string.Empty) { return TestHelper.CreateResponse(HttpStatusCode.MethodNotAllowed); } if (!this.Containers.ContainsKey(destinationSegs[0])) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } var destObjectName = string.Join("", destinationSegs.Skip(1)); var srcObj = this.Objects[objectName]; var obj = new StorageItem(destObjectName); obj.MetaData = srcObj.MetaData; obj.ProcessMetaDataFromHeaders(this.Headers); var content = new MemoryStream(); srcObj.Content.CopyTo(content); srcObj.Content.Position = 0; content.Position = 0; obj.Content = content; this.Objects[obj.Name] = obj; var headers = GenerateObjectResponseHeaders(obj); return TestHelper.CreateResponse(HttpStatusCode.Created, headers); } private IHttpResponseAbstraction HandleHead() { var containerName = GetContainerName(this.Uri.Segments); var objectName = GetObjectName(this.Uri.Segments); if (containerName == null && objectName == null) { return TestHelper.CreateErrorResponse(); } var headers = new Dictionary(); if (objectName != null) { if (!this.Objects.ContainsKey(objectName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } headers = GenerateObjectResponseHeaders(this.Objects[objectName]); } else { if (!this.Containers.ContainsKey(containerName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } headers = GenerateContainerResponseHeaders(this.Containers[containerName]); } return TestHelper.CreateResponse(HttpStatusCode.NoContent, headers); } private IHttpResponseAbstraction HandleDelete() { var containerName = GetContainerName(this.Uri.Segments); var objectName = GetObjectName(this.Uri.Segments); if (containerName == null && objectName == null) { return TestHelper.CreateErrorResponse(); } if (objectName != null) { if (!this.Objects.ContainsKey(objectName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } this.Objects.Remove(objectName); return TestHelper.CreateResponse(HttpStatusCode.NoContent, GenerateDeleteHeaders()); } if (!this.Containers.ContainsKey(containerName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } var statusCode = HttpStatusCode.NoContent; if (!this.IsContainerEmpty) { statusCode = HttpStatusCode.Conflict; } else { this.Containers.Remove(containerName); } return TestHelper.CreateResponse(statusCode, GenerateDeleteHeaders()); } private IHttpResponseAbstraction HandlePut() { var containerName = GetContainerName(this.Uri.Segments); var objectName = GetObjectName(this.Uri.Segments); if (containerName == null && objectName == null) { return TestHelper.CreateErrorResponse(); } if (objectName != null) { var obj = new StorageItem(objectName); obj.ProcessMetaDataFromHeaders(this.Headers); obj.LoadContent(this.Content); this.Objects[obj.Name] = obj; var headers = GenerateObjectResponseHeaders(obj); return TestHelper.CreateResponse(HttpStatusCode.Created, headers); } else { var container = new StorageItem(containerName); container.ProcessMetaDataFromHeaders(this.Headers); var containerUpdated = this.Containers.Keys.Any(k => String.Equals(k, container.Name, StringComparison.InvariantCultureIgnoreCase)); this.Containers[container.Name] = container; return TestHelper.CreateResponse(containerUpdated ? HttpStatusCode.Accepted : HttpStatusCode.Created); } } private IHttpResponseAbstraction HandlePost() { var containerName = GetContainerName(this.Uri.Segments); var objectName = GetObjectName(this.Uri.Segments); if (containerName == null && objectName == null) { return TestHelper.CreateErrorResponse(); } StorageItem storageItem; if (objectName != null) { if (!this.Objects.ContainsKey(objectName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } storageItem = this.Objects[objectName]; } else { if (!this.Containers.ContainsKey(containerName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } storageItem = this.Containers[containerName]; } storageItem.MetaData.Clear(); storageItem.ProcessMetaDataFromHeaders(this.Headers); return TestHelper.CreateResponse(HttpStatusCode.Accepted); } private IHttpResponseAbstraction HandleGet() { var containerName = GetContainerName(this.Uri.Segments); var objectName = GetObjectName(this.Uri.Segments); var accountName = GetAccountName(this.Uri.Segments); if (containerName == null && objectName == null) { if (accountName == null) { return TestHelper.CreateErrorResponse(); } var accountHeaders = GenerateAccountResponseHeaders(); var accountContent = TestHelper.CreateStream(GenerateAccountPayload()); return TestHelper.CreateResponse(HttpStatusCode.OK, accountHeaders, accountContent); } 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); } if (!this.Containers.ContainsKey(containerName)) { return TestHelper.CreateResponse(HttpStatusCode.NotFound); } var container = this.Containers[containerName]; var headers = GenerateContainerResponseHeaders(container); var content = TestHelper.CreateStream("[]"); return TestHelper.CreateResponse(HttpStatusCode.OK, headers, content); } public string GetContainerName(string[] uriSegments) { return uriSegments.Count() < 4 ? null : uriSegments[3].TrimEnd('/'); } public string GetAccountName(string[] uriSegments) { return uriSegments.Count() < 3 ? null : uriSegments[2].TrimEnd('/'); } public string GetObjectName(string[] uriSegments) { if (uriSegments.Count() < 5) { return null; } return string.Join("", uriSegments.Skip(4)); } private string GenerateAccountPayload() { var payload = new StringBuilder(); payload.Append("["); var first = true; foreach (var container in this.Containers) { if (!first) { payload.Append(","); first = false; } payload.Append("{"); payload.Append("\"count\": 42,"); payload.Append("\"bytes\": 12345,"); payload.Append(string.Format("\"name\": \"{0}\"",container.Value.Name)); payload.Append("}"); } payload.Append("]"); return payload.ToString(); } private IEnumerable> GenerateAccountResponseHeaders() { return new Dictionary() { {"X-Account-Bytes-Used", "12345"}, {"X-Account-Container-Count", this.Containers.Count.ToString()}, {"X-Account-Object-Count", this.Objects.Count.ToString()}, {"Content-Type", this.ContentType}, {"X-Trans-Id", "12345"}, {"Date", DateTime.UtcNow.ToShortTimeString()}, {"X-Timestamp", "1234567890.98765"} }; } internal Dictionary GenerateObjectResponseHeaders(StorageItem obj) { var etag = Convert.ToBase64String(MD5.Create().ComputeHash(obj.Content)); obj.Content.Position = 0; return new Dictionary(obj.MetaData) { {"ETag", etag}, {"Content-Type", this.ContentType}, {"X-Trans-Id", "12345"}, {"Date", DateTime.UtcNow.ToShortTimeString()}, {"X-Timestamp", "1234567890.98765"} }; } internal Dictionary GenerateContainerResponseHeaders(StorageItem obj) { return new Dictionary(obj.MetaData) { {"X-Container-Bytes-Used", "0"}, {"Content-Type", this.ContentType}, {"X-Container-Object-Count", "0"}, {"Date", DateTime.UtcNow.ToShortTimeString()}, {"X-Container-Read", ".r.*,.rlistings"}, {"X-Trans-Id", "12345"}, {"X-Timestamp", "1234567890.98765"} }; } internal Dictionary GenerateDeleteHeaders() { return new Dictionary() { {"X-Trans-Id", "12345"}, {"Date", DateTime.UtcNow.ToShortTimeString()} }; } } public class StorageRestSimulatorFactory : IHttpAbstractionClientFactory { internal StorageRestSimulator Simulator = null; public StorageRestSimulatorFactory(StorageRestSimulator simulator) { this.Simulator = simulator; } public IHttpAbstractionClient Create() { throw new NotImplementedException(); } public IHttpAbstractionClient Create(CancellationToken token) { if (this.Simulator != null) { this.Simulator.Headers.Clear(); } return this.Simulator ?? new StorageRestSimulator(token); } public IHttpAbstractionClient Create(TimeSpan timeout) { throw new NotImplementedException(); } public IHttpAbstractionClient Create(TimeSpan timeout, CancellationToken token) { throw new NotImplementedException(); } public IHttpAbstractionClient Create(IOpenStackAccessToken credentials, CancellationToken token) { throw new NotImplementedException(); } public IHttpAbstractionClient Create(IOpenStackAccessToken credentials, TimeSpan timeout, CancellationToken token) { throw new NotImplementedException(); } } }