diff --git a/integration-tests/NAME.IntegrationTests/Constants.cs b/integration-tests/NAME.IntegrationTests/Constants.cs index bc4ecce..009756c 100644 --- a/integration-tests/NAME.IntegrationTests/Constants.cs +++ b/integration-tests/NAME.IntegrationTests/Constants.cs @@ -1,4 +1,4 @@ -using System; +using System; public static class Constants { public static string ExpectedOperatingSystem; @@ -17,6 +17,11 @@ public static class Constants { public static string SpecificRabbitVersion; public static string SpecificServiceVersion; + // Elasticsearch variables + public static string LatestElasticsearchHostname; + public static string SpecificElasticsearchHostname; + public static string SpecificElasticsearchVersion; + static Constants() { ExpectedOperatingSystem = Environment.GetEnvironmentVariable("OPERATING_SYSTEM") ?? "windows"; @@ -31,5 +36,9 @@ static Constants() { SpecificMongoVersion = Environment.GetEnvironmentVariable("SPECIFIC_MONGO_VERSION") ?? "3.0.0"; SpecificRabbitVersion = Environment.GetEnvironmentVariable("SPECIFIC_RABBIT_VERSION") ?? "3.6.5"; SpecificServiceVersion = Environment.GetEnvironmentVariable("SPECIFIC_SERVICE_VERSION") ?? "1.0.0"; + + LatestElasticsearchHostname = Environment.GetEnvironmentVariable("LATEST_ELASTICSEARCH_HOSTNAME") ?? "localhost"; + SpecificElasticsearchHostname = Environment.GetEnvironmentVariable("SPECIFIC_ELASTICSEARCH_HOSTNAME") ?? "localhost"; + SpecificElasticsearchVersion = Environment.GetEnvironmentVariable("SPECIFIC_ELASTICSEARCH_VERSION") ?? "5.5.1"; } } \ No newline at end of file diff --git a/integration-tests/NAME.IntegrationTests/Elasticsearch/ElasticsearchVersionResolverTests.cs b/integration-tests/NAME.IntegrationTests/Elasticsearch/ElasticsearchVersionResolverTests.cs new file mode 100644 index 0000000..77210a9 --- /dev/null +++ b/integration-tests/NAME.IntegrationTests/Elasticsearch/ElasticsearchVersionResolverTests.cs @@ -0,0 +1,37 @@ +using NAME.ConnectionStrings; +using NAME.Core; +using NAME.Elasticsearch; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace NAME.IntegrationTests.Elasticsearch +{ + public class ElasticsearchVersionResolverTests + { + [Fact] + [Trait("TestCategory", "Integration")] + public async Task GetVersions_SpecificVersion() + { + ElasticsearchVersionResolver resolver = new ElasticsearchVersionResolver(new StaticConnectionStringProvider($"http://{ Constants.SpecificElasticsearchHostname }:9200"), 10000, 10000); + + var versions = await resolver.GetVersions().ConfigureAwait(false); + + Assert.Equal(1, versions.Count()); + Assert.Equal(versions.First(), DependencyVersion.Parse(Constants.SpecificElasticsearchVersion)); + } + + [Fact] + [Trait("TestCategory", "Integration")] + public async Task GetVersions_LatestVersion() + { + ElasticsearchVersionResolver resolver = new ElasticsearchVersionResolver(new StaticConnectionStringProvider($"http://{ Constants.LatestElasticsearchHostname }:9200"), 10000, 10000); + + var versions = await resolver.GetVersions().ConfigureAwait(false); + + Assert.Equal(1, versions.Count()); + // Latest GA RELEASE: https://www.elastic.co/downloads/elasticsearch#ga-release + Assert.Equal(versions.First(), DependencyVersion.Parse("5.6.3")); + } + } +} \ No newline at end of file diff --git a/integration-tests/docker-compose.yml b/integration-tests/docker-compose.yml index ce1b395..97317c7 100644 --- a/integration-tests/docker-compose.yml +++ b/integration-tests/docker-compose.yml @@ -9,6 +9,8 @@ services: - specific-mongo - specific-rabbitmq - dummy-service + - elasticsearch + - specific-elasticsearch environment: - LATEST_MONGO_HOSTNAME=mongo - LATEST_RABBIT_HOSTNAME=rabbitmq @@ -21,6 +23,9 @@ services: - SPECIFIC_SERVICE_VERSION=1.0.0 - OPERATING_SYSTEM=debian - RUNNING_ON_DOCKER=true + - LATEST_ELASTICSEARCH_HOSTNAME=elasticsearch + - SPECIFIC_ELASTICSEARCH_HOSTNAME=specific-elasticsearch + - SPECIFIC_ELASTICSEARCH_VERSION=5.5.1 volumes: - ../Output/Artifacts/NuGets/Release:/integration/nugets - ../Output/IntegrationTestsResults:/integration/TestResults @@ -31,6 +36,8 @@ services: - specific-mongo - specific-rabbitmq - dummy-service + - elasticsearch + - specific-elasticsearch dummy-service: build: ./NAME.DummyService environment: @@ -61,4 +68,17 @@ services: - "1433" environment: - SA_PASSWORD=W1#llnotbeused - - ACCEPT_EULA=Y \ No newline at end of file + - ACCEPT_EULA=Y + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:5.6.3 + expose: + - "9200" + environment: + - xpack.security.enabled=false + specific-elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:5.5.1 + expose: + - "9200" + environment: + - xpack.security.enabled=false + \ No newline at end of file diff --git a/name.dependencies.v1.jschema b/name.dependencies.v1.jschema index 4d37762..28dd7b7 100644 --- a/name.dependencies.v1.jschema +++ b/name.dependencies.v1.jschema @@ -72,7 +72,8 @@ "enum": [ "RabbitMq", "MongoDb", - "SqlServer" + "SqlServer", + "Elasticsearch" ] }, "min_version": { diff --git a/src/NAME.Core/ConnectedVersionResolver.cs b/src/NAME.Core/ConnectedVersionResolver.cs index ef4a077..7e8b8fe 100644 --- a/src/NAME.Core/ConnectedVersionResolver.cs +++ b/src/NAME.Core/ConnectedVersionResolver.cs @@ -1,5 +1,7 @@ -using NAME.Core.Exceptions; +using NAME.Core.Exceptions; +using System; using System.Collections.Generic; +using System.Net; using System.Net.Sockets; using System.Threading.Tasks; @@ -80,5 +82,27 @@ protected async Task OpenTcpClient(string host, int port, string depe throw new DependencyNotReachableException(dependencyName, ex); } } + + /// + /// Gets a HttpWebRequest + /// + /// Request Uri String + /// Dependency Name + /// Returns a HttpWebRequest + protected HttpWebRequest GetHttpWebRequest(string requestUriString, string dependencyName) + { + HttpWebRequest request = null; + try + { + request = WebRequest.CreateHttp(requestUriString); + request.ContinueTimeout = this.ConnectTimeout; + request.ContentType = "application/json; charset=utf-8"; + return request; + } + catch (Exception e) + { + throw new DependencyNotReachableException(dependencyName, e); + } + } } -} +} \ No newline at end of file diff --git a/src/NAME.Core/SupportedDependencies.cs b/src/NAME.Core/SupportedDependencies.cs index 2bd7e3f..d54c5a4 100644 --- a/src/NAME.Core/SupportedDependencies.cs +++ b/src/NAME.Core/SupportedDependencies.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - namespace NAME.Core { /// @@ -14,25 +9,30 @@ public enum SupportedDependencies /// MongoDb /// MongoDb = 10, - + /// /// Operating System /// OperatingSystem = 20, - + /// /// RabbitMq /// RabbitMq = 30, - + /// /// SQL Server /// SqlServer = 40, - + /// /// A service with NAME /// - Service = 50 + Service = 50, + + /// + /// Elasticsearch + /// + Elasticsearch = 60 } -} +} \ No newline at end of file diff --git a/src/NAME/DependenciesReader.cs b/src/NAME/DependenciesReader.cs index 5172c60..c6b709f 100644 --- a/src/NAME/DependenciesReader.cs +++ b/src/NAME/DependenciesReader.cs @@ -1,4 +1,4 @@ -using NAME.Core.Exceptions; +using NAME.Core.Exceptions; using System; using System.Collections.Generic; using System.IO; @@ -265,6 +265,8 @@ private static IVersionResolver GetConnectedDependencyVersionResolver(SupportedD return new SqlServer.SqlServerVersionResolver(connectionStringProvider, configuration.DependencyConnectTimeout, configuration.DependencyReadWriteTimeout); case SupportedDependencies.Service: return new Service.ServiceVersionResolver(connectionStringProvider, context.ServiceDependencyCurrentNumberOfHops, configuration.ServiceDependencyMaxHops, configuration.DependencyConnectTimeout, configuration.DependencyReadWriteTimeout); + case SupportedDependencies.Elasticsearch: + return new Elasticsearch.ElasticsearchVersionResolver(connectionStringProvider, configuration.DependencyConnectTimeout, configuration.DependencyReadWriteTimeout); default: throw new NAMEException($"The dependency of type {dependencyType} is not supported as a connected dependency."); } diff --git a/src/NAME/Elasticsearch/ElasticsearchVersionResolver.cs b/src/NAME/Elasticsearch/ElasticsearchVersionResolver.cs new file mode 100644 index 0000000..f4c969c --- /dev/null +++ b/src/NAME/Elasticsearch/ElasticsearchVersionResolver.cs @@ -0,0 +1,84 @@ +using NAME.Core; +using NAME.Core.Exceptions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; + +namespace NAME.Elasticsearch +{ + internal class ElasticsearchVersionResolver : ConnectedVersionResolver + { + private IConnectionStringProvider connectionStringProvider; + + public ElasticsearchVersionResolver(IConnectionStringProvider connectionStringProvider, int connectTimeout, int readWriteTimeout) + : base(connectTimeout, readWriteTimeout) + { + this.connectionStringProvider = connectionStringProvider; + } + + public override async Task> GetVersions() + { + var versions = new List(); + var connectionString = string.Empty; + + if (!this.connectionStringProvider.TryGetConnectionString(out connectionString)) + { + throw new ConnectionStringNotFoundException(this.connectionStringProvider.ToString()); + } + + HttpWebResponse response; + try + { + var request = this.GetHttpWebRequest(connectionString, SupportedDependencies.Elasticsearch.ToString()); + + var getResponseTask = request.GetResponseAsync(); + var readWriteTimeout = this.ConnectTimeout + this.ReadWriteTimeout; + + readWriteTimeout = readWriteTimeout < 1000 ? 1000 : readWriteTimeout; + + //If Elasticsearch takes to long to return a response, when readWriteTimeout passes, the request will be aborted + var completedTask = await Task.WhenAny(getResponseTask, Task.Delay(readWriteTimeout)).ConfigureAwait(false); + if (completedTask == getResponseTask) + { + using (response = await getResponseTask.ConfigureAwait(false) as HttpWebResponse) + { + using (var reader = new StreamReader(response.GetResponseStream())) + { + var body = await reader.ReadToEndAsync(); + var version = string.Empty; + + version = this.DeserializeJsonResponse(body); + versions.Add(DependencyVersion.Parse(version)); + } + } + } + else + { + request.Abort(); + throw new NAMEException($"{SupportedDependencies.Elasticsearch}: Timed out, the server accepted the connection but did not send a response."); + } + } + catch (WebException e) + { + throw new DependencyNotReachableException($"{SupportedDependencies.Elasticsearch}: {e.Message}"); + } + + return versions; + } + + private string DeserializeJsonResponse(string result) + { + try + { + var jsonResult = Json.Json.Parse(result); + return jsonResult["version"]["number"]; + } + catch (Exception e) + { + throw new VersionParsingException(result, e.Message); + } + } + } +} \ No newline at end of file diff --git a/src/NAME/MongoDb/MongoDBVersionResolver.cs b/src/NAME/MongoDb/MongoDBVersionResolver.cs index 65010f4..9c2fd7a 100644 --- a/src/NAME/MongoDb/MongoDBVersionResolver.cs +++ b/src/NAME/MongoDb/MongoDBVersionResolver.cs @@ -1,17 +1,14 @@ -using NAME.Core.Exceptions; +using NAME.Core; +using NAME.Core.Exceptions; using NAME.MongoDb.Bson; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Sockets; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using NAME.Core.Utils; -using NAME.Core; namespace NAME.MongoDb { @@ -148,4 +145,4 @@ private byte[] CreateServerStatusMessagePayload(MongoConnectionStringBuilder con return message; } } -} +} \ No newline at end of file diff --git a/src/NAME/Nuget/Content/dependencies.json b/src/NAME/Nuget/Content/dependencies.json index a978fd2..df4b088 100644 --- a/src/NAME/Nuget/Content/dependencies.json +++ b/src/NAME/Nuget/Content/dependencies.json @@ -26,6 +26,15 @@ // "locator": "ConnectionStrings", // "key": "EventaConnectionString" // } + //}, + //{ + // "type": "Elasticsearch", + // "min_version": "5.5.1", + // "max_version": "5.6.3", + // "connection_string": { + // "locator": "ConnectionStrings", + // "key": "ElasticConnectionString" + // } //} ], "service_dependencies": [ diff --git a/src/NAME/Service/ServiceVersionResolver.cs b/src/NAME/Service/ServiceVersionResolver.cs index d163681..ab210a9 100644 --- a/src/NAME/Service/ServiceVersionResolver.cs +++ b/src/NAME/Service/ServiceVersionResolver.cs @@ -1,12 +1,10 @@ -using NAME.Core; +using NAME.Core; using NAME.Core.Exceptions; using NAME.Json; using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net; -using System.Threading; using System.Threading.Tasks; namespace NAME.Service @@ -18,7 +16,7 @@ namespace NAME.Service public class ServiceVersionResolver : ConnectedVersionResolver { private readonly IConnectionStringProvider _connectionStringProvider; - private readonly Func _webRequestFactory; + private const string InfrastructureDependenciesKey = "infrastructure_dependencies"; private const string ServiceDependenciesKey = "service_dependencies"; @@ -46,15 +44,12 @@ public class ServiceVersionResolver : ConnectedVersionResolver /// The maximum hop count. /// The connect timeout. /// The read write timeout. - /// The web request factory. - public ServiceVersionResolver(IConnectionStringProvider connectionStringProvider, int currentHopNumber, int maxHopCount, int connectTimeout, int readWriteTimeout, Func webRequestFactory = null) + public ServiceVersionResolver(IConnectionStringProvider connectionStringProvider, int currentHopNumber, int maxHopCount, int connectTimeout, int readWriteTimeout) : base(connectTimeout, readWriteTimeout) { this._connectionStringProvider = connectionStringProvider; this.HopNumber = currentHopNumber; this.MaxHopCount = maxHopCount; - - this._webRequestFactory = webRequestFactory ?? WebRequest.CreateHttp; } /// @@ -150,12 +145,11 @@ public async Task GetDependantManifests(DependencyVersion rootDependency) private async Task GetManifest(Uri endpointUri, bool retry, int hop) { - HttpWebRequest request = this._webRequestFactory.Invoke(endpointUri.AbsoluteUri); - request.ContentType = "application/json; charset=utf-8"; + HttpWebRequest request = this.GetHttpWebRequest(endpointUri.AbsoluteUri, SupportedDependencies.Service.ToString()); request.Headers[Constants.HOP_COUNT_HEADER_NAME] = hop.ToString(); // This timeout defines the time it should take to connect to the instance. - request.ContinueTimeout = this.ConnectTimeout; + try { var getResponseTask = request.GetResponseAsync(); @@ -225,4 +219,4 @@ private async Task GetManifest(Uri endpointUri, bool retry, int hop) } } } -} +} \ No newline at end of file diff --git a/unit-tests/NAME.Tests/Elasticsearch/ElasticsearchVersionResolverTests.cs b/unit-tests/NAME.Tests/Elasticsearch/ElasticsearchVersionResolverTests.cs new file mode 100644 index 0000000..9488319 --- /dev/null +++ b/unit-tests/NAME.Tests/Elasticsearch/ElasticsearchVersionResolverTests.cs @@ -0,0 +1,36 @@ +using NAME.ConnectionStrings; +using NAME.Core; +using NAME.Core.Exceptions; +using NAME.Elasticsearch; +using System.Threading.Tasks; +using Xunit; + +namespace NAME.Tests.Elasticsearch +{ + public class ElasticsearchVersionResolverTests + { + [Fact] + [Trait("TestCategory", "Unit")] + public async Task GetVersions_ConnectionStringNotFound() + { + IVersionResolver resolver = new ElasticsearchVersionResolver(new DummyNotFoundConnectionStringProvider(), 10, 10); + + await Assert.ThrowsAsync(async () => + { + var value = await resolver.GetVersions().ConfigureAwait(false); + }); + } + + [Fact] + [Trait("TestCategory", "Unit")] + public async Task GetVersions_DependencyNotReachable() + { + IVersionResolver resolver = new ElasticsearchVersionResolver(new StaticConnectionStringProvider(""), 10, 10); + + await Assert.ThrowsAsync(async () => + { + var value = await resolver.GetVersions().ConfigureAwait(false); + }); + } + } +} \ No newline at end of file