From 9b06464d96830e72ee242a8c6d84fca9ef390fa3 Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Wed, 10 Feb 2021 09:05:02 +0100 Subject: [PATCH 1/5] fixed default keep alive --- .../EventStoreClientConnectivitySettings.cs | 5 +++-- .../EventStoreClientSettings.ConnectionString.cs | 4 +++- test/EventStore.Client.Tests/ConnectionStringTests.cs | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/EventStore.Client/EventStoreClientConnectivitySettings.cs b/src/EventStore.Client/EventStoreClientConnectivitySettings.cs index 3af4ee9ae..7aeb66c2a 100644 --- a/src/EventStore.Client/EventStoreClientConnectivitySettings.cs +++ b/src/EventStore.Client/EventStoreClientConnectivitySettings.cs @@ -67,7 +67,7 @@ public class EventStoreClientConnectivitySettings { /// /// The optional amount of time to wait after which a keepalive ping is sent on the transport. /// - public TimeSpan? KeepAlive { get; set; } + public TimeSpan? KeepAlive { get; set; } = TimeSpan.FromSeconds(10); /// /// True if pointing to a single EventStoreDB node. @@ -86,7 +86,8 @@ public class EventStoreClientConnectivitySettings { MaxDiscoverAttempts = 10, GossipTimeout = TimeSpan.FromSeconds(5), DiscoveryInterval = TimeSpan.FromMilliseconds(100), - NodePreference = NodePreference.Leader + NodePreference = NodePreference.Leader, + KeepAlive = TimeSpan.FromSeconds(10), }; } } diff --git a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs index 01ab7cdab..340f7bb3e 100644 --- a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs +++ b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs @@ -167,7 +167,9 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us settings.OperationOptions.ThrowOnAppendFailure = (bool)throwOnAppendFailure; if (typedOptions.TryGetValue(KeepAlive, out var keepAliveMs)) { - settings.ConnectivitySettings.KeepAlive = TimeSpan.FromMilliseconds((int)keepAliveMs); + settings.ConnectivitySettings.KeepAlive = (int)keepAliveMs == -1 + ? new TimeSpan?() + : TimeSpan.FromMilliseconds((int)keepAliveMs); } connSettings.Insecure = !useTls; diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs index d84d7ae80..ad0b00519 100644 --- a/test/EventStore.Client.Tests/ConnectionStringTests.cs +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -144,7 +144,7 @@ public void with_valid_single_node_connection_string() { Assert.Equal(330, settings.OperationOptions.TimeoutAfter.Value.TotalMilliseconds); Assert.False(settings.OperationOptions.ThrowOnAppendFailure); - settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false"); + settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false&keepAlive=-1"); Assert.Null(settings.DefaultCredentials); Assert.Equal("http://hostname:4321/", settings.ConnectivitySettings.Address.ToString()); Assert.Empty(settings.ConnectivitySettings.GossipSeeds); From 845a7c766de71c09b13aa677952e192e9dc1516a Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Wed, 10 Feb 2021 10:54:30 +0100 Subject: [PATCH 2/5] renamed keepAlive -> keepAliveInterval --- src/EventStore.Client/ChannelFactory.cs | 8 +++--- .../EventStoreClientConnectivitySettings.cs | 4 +-- ...entStoreClientSettings.ConnectionString.cs | 16 +++++++----- .../ConnectionStringTests.cs | 26 +++++++++---------- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index dfbbbf8a3..88b93fe85 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -41,8 +41,8 @@ HttpMessageHandler CreateHandler() { } var handler = new SocketsHttpHandler(); - if (settings.ConnectivitySettings.KeepAlive.HasValue) { - handler.KeepAlivePingDelay = settings.ConnectivitySettings.KeepAlive.Value; + if (settings.ConnectivitySettings.KeepAliveInterval.HasValue) { + handler.KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval.Value; } return handler; @@ -52,9 +52,9 @@ HttpMessageHandler CreateHandler() { GetChannelOptions()); IEnumerable GetChannelOptions() { - if (settings.ConnectivitySettings.KeepAlive.HasValue) { + if (settings.ConnectivitySettings.KeepAliveInterval.HasValue) { yield return new ChannelOption("grpc.keepalive_time_ms", - (int)settings.ConnectivitySettings.KeepAlive.Value.TotalMilliseconds); + (int)settings.ConnectivitySettings.KeepAliveInterval.Value.TotalMilliseconds); } } #endif diff --git a/src/EventStore.Client/EventStoreClientConnectivitySettings.cs b/src/EventStore.Client/EventStoreClientConnectivitySettings.cs index 7aeb66c2a..c8f22aaaf 100644 --- a/src/EventStore.Client/EventStoreClientConnectivitySettings.cs +++ b/src/EventStore.Client/EventStoreClientConnectivitySettings.cs @@ -67,7 +67,7 @@ public class EventStoreClientConnectivitySettings { /// /// The optional amount of time to wait after which a keepalive ping is sent on the transport. /// - public TimeSpan? KeepAlive { get; set; } = TimeSpan.FromSeconds(10); + public TimeSpan? KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(10); /// /// True if pointing to a single EventStoreDB node. @@ -87,7 +87,7 @@ public class EventStoreClientConnectivitySettings { GossipTimeout = TimeSpan.FromSeconds(5), DiscoveryInterval = TimeSpan.FromMilliseconds(100), NodePreference = NodePreference.Leader, - KeepAlive = TimeSpan.FromSeconds(10), + KeepAliveInterval = TimeSpan.FromSeconds(10), }; } } diff --git a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs index 340f7bb3e..0b30709c6 100644 --- a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs +++ b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs @@ -35,7 +35,8 @@ private static class ConnectionStringParser { private const string TlsVerifyCert = nameof(TlsVerifyCert); private const string OperationTimeout = nameof(OperationTimeout); private const string ThrowOnAppendFailure = nameof(ThrowOnAppendFailure); - private const string KeepAlive = nameof(KeepAlive); + private const string KeepAliveInterval = nameof(KeepAliveInterval); + private const string KeepAliveTimeout = nameof(KeepAliveTimeout); private const string UriSchemeDiscover = "esdb+discover"; @@ -54,7 +55,8 @@ private static class ConnectionStringParser { {TlsVerifyCert, typeof(bool)}, {OperationTimeout, typeof(int)}, {ThrowOnAppendFailure, typeof(bool)}, - {KeepAlive, typeof(int)} + {KeepAliveInterval, typeof(int)}, + {KeepAliveTimeout, typeof(int)}, }; public static EventStoreClientSettings Parse(string connectionString) { @@ -166,10 +168,10 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us if (typedOptions.TryGetValue(ThrowOnAppendFailure, out object throwOnAppendFailure)) settings.OperationOptions.ThrowOnAppendFailure = (bool)throwOnAppendFailure; - if (typedOptions.TryGetValue(KeepAlive, out var keepAliveMs)) { - settings.ConnectivitySettings.KeepAlive = (int)keepAliveMs == -1 + if (typedOptions.TryGetValue(KeepAliveInterval, out var keepAliveIntervalMs)) { + settings.ConnectivitySettings.KeepAliveInterval = (int)keepAliveIntervalMs == -1 ? new TimeSpan?() - : TimeSpan.FromMilliseconds((int)keepAliveMs); + : TimeSpan.FromMilliseconds((int)keepAliveIntervalMs); } connSettings.Insecure = !useTls; @@ -192,8 +194,8 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; } - if (settings.ConnectivitySettings.KeepAlive.HasValue) { - handler.KeepAlivePingDelay = settings.ConnectivitySettings.KeepAlive.Value; + if (settings.ConnectivitySettings.KeepAliveInterval.HasValue) { + handler.KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval.Value; } return handler; diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs index ad0b00519..f388ac122 100644 --- a/test/EventStore.Client.Tests/ConnectionStringTests.cs +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -90,7 +90,7 @@ public void connection_string_with_duplicate_key_should_throw(string connectionS InlineData("esdb://user:pass@127.0.0.1/?gossipTimeout=defg"), InlineData("esdb://user:pass@127.0.0.1/?tlsVerifyCert=truee"), InlineData("esdb://user:pass@127.0.0.1/?nodePreference=blabla"), - InlineData("esdb://user:pass@127.0.0.1/?keepAlive=blabla")] + InlineData("esdb://user:pass@127.0.0.1/?keepAliveInterval=blabla")] public void connection_string_with_invalid_settings_should_throw(string connectionString) { Assert.Throws(() => EventStoreClientSettings.Create(connectionString)); } @@ -109,7 +109,7 @@ public void with_different_node_preferences(string nodePreference, NodePreferenc [Fact] public void with_valid_single_node_connection_string() { var settings = EventStoreClientSettings.Create( - "esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=13&DiscoveryInterval=37&gossipTimeout=33&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&keepAlive=10"); + "esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=13&DiscoveryInterval=37&gossipTimeout=33&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&keepAliveInterval=10"); Assert.Equal("user", settings.DefaultCredentials.Username); Assert.Equal("pass", settings.DefaultCredentials.Password); Assert.Equal("https://127.0.0.1:2113/", settings.ConnectivitySettings.Address.ToString()); @@ -120,12 +120,12 @@ public void with_valid_single_node_connection_string() { Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); Assert.Equal(33, settings.ConnectivitySettings.GossipTimeout.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAlive); + Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif settings = EventStoreClientSettings.Create( - "esdb://127.0.0.1?connectionName=test&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tls=true&tlsVerifyCert=true&operationTimeout=330&throwOnAppendFailure=faLse&KEepAlive=10"); + "esdb://127.0.0.1?connectionName=test&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tls=true&tlsVerifyCert=true&operationTimeout=330&throwOnAppendFailure=faLse&KEepAliveInTeRvAl=10"); Assert.Null(settings.DefaultCredentials); Assert.Equal("test", settings.ConnectionName); Assert.Equal("https://127.0.0.1:2113/", settings.ConnectivitySettings.Address.ToString()); @@ -136,7 +136,7 @@ public void with_valid_single_node_connection_string() { Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAlive); + Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif @@ -144,14 +144,14 @@ public void with_valid_single_node_connection_string() { Assert.Equal(330, settings.OperationOptions.TimeoutAfter.Value.TotalMilliseconds); Assert.False(settings.OperationOptions.ThrowOnAppendFailure); - settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false&keepAlive=-1"); + settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false&keepAliveInterval=-1"); Assert.Null(settings.DefaultCredentials); Assert.Equal("http://hostname:4321/", settings.ConnectivitySettings.Address.ToString()); Assert.Empty(settings.ConnectivitySettings.GossipSeeds); Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); Assert.True(settings.ConnectivitySettings.Insecure); - Assert.Null(settings.ConnectivitySettings.KeepAlive); + Assert.Null(settings.ConnectivitySettings.KeepAliveInterval); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif @@ -181,14 +181,14 @@ public void with_default_settings() { settings.OperationOptions.TimeoutAfter!.Value.TotalMilliseconds); Assert.Equal(EventStoreClientOperationOptions.Default.ThrowOnAppendFailure, settings.OperationOptions.ThrowOnAppendFailure); - Assert.Equal(EventStoreClientConnectivitySettings.Default.KeepAlive, - settings.ConnectivitySettings.KeepAlive); + Assert.Equal(EventStoreClientConnectivitySettings.Default.KeepAliveInterval, + settings.ConnectivitySettings.KeepAliveInterval); } [Fact] public void with_valid_cluster_connection_string() { var settings = EventStoreClientSettings.Create( - "esdb://user:pass@127.0.0.1,127.0.0.2:3321,127.0.0.3/?maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAlive=10"); + "esdb://user:pass@127.0.0.1,127.0.0.2:3321,127.0.0.3/?maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRVAl=10"); Assert.Equal("user", settings.DefaultCredentials.Username); Assert.Equal("pass", settings.DefaultCredentials.Password); Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); @@ -205,14 +205,14 @@ public void with_valid_cluster_connection_string() { Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAlive); + Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif settings = EventStoreClientSettings.Create( - "esdb://user:pass@host1,host2:3321,127.0.0.3/?tls=false&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAlive=10"); + "esdb://user:pass@host1,host2:3321,127.0.0.3/?tls=false&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRvAl=10"); Assert.Equal("user", settings.DefaultCredentials.Username); Assert.Equal("pass", settings.DefaultCredentials.Password); Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); @@ -229,7 +229,7 @@ public void with_valid_cluster_connection_string() { Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAlive); + Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif From fd122729d5fda11cc473156cd16a27055087204d Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Thu, 11 Feb 2021 11:52:44 +0100 Subject: [PATCH 3/5] add keepalivetimeout to connection string --- src/EventStore.Client/ChannelFactory.cs | 24 ++++++++++------- .../EventStoreClientConnectivitySettings.cs | 8 +++++- ...entStoreClientSettings.ConnectionString.cs | 26 +++++++++++++------ .../ConnectionStringTests.cs | 23 +++++++++++----- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/EventStore.Client/ChannelFactory.cs b/src/EventStore.Client/ChannelFactory.cs index 88b93fe85..812ce28bd 100644 --- a/src/EventStore.Client/ChannelFactory.cs +++ b/src/EventStore.Client/ChannelFactory.cs @@ -40,23 +40,27 @@ HttpMessageHandler CreateHandler() { return settings.CreateHttpMessageHandler.Invoke(); } - var handler = new SocketsHttpHandler(); - if (settings.ConnectivitySettings.KeepAliveInterval.HasValue) { - handler.KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval.Value; - } - - return handler; + return new SocketsHttpHandler { + KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, + KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout + }; } #else return new Channel(address.Host, address.Port, settings.ChannelCredentials ?? ChannelCredentials.Insecure, GetChannelOptions()); IEnumerable GetChannelOptions() { - if (settings.ConnectivitySettings.KeepAliveInterval.HasValue) { - yield return new ChannelOption("grpc.keepalive_time_ms", - (int)settings.ConnectivitySettings.KeepAliveInterval.Value.TotalMilliseconds); - } + yield return new ChannelOption("grpc.keepalive_time_ms", + GetValue((int)settings.ConnectivitySettings.KeepAliveInterval.TotalMilliseconds)); + + yield return new ChannelOption("grpc.keepalive_timeout_ms", + GetValue((int)settings.ConnectivitySettings.KeepAliveTimeout.TotalMilliseconds)); } + + static int GetValue(int value) => value switch { + { } v when v < 0 => int.MaxValue, + _ => value + }; #endif } } diff --git a/src/EventStore.Client/EventStoreClientConnectivitySettings.cs b/src/EventStore.Client/EventStoreClientConnectivitySettings.cs index c8f22aaaf..c520935c9 100644 --- a/src/EventStore.Client/EventStoreClientConnectivitySettings.cs +++ b/src/EventStore.Client/EventStoreClientConnectivitySettings.cs @@ -67,7 +67,12 @@ public class EventStoreClientConnectivitySettings { /// /// The optional amount of time to wait after which a keepalive ping is sent on the transport. /// - public TimeSpan? KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(10); + public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(10); + + /// + /// The optional amount of time to wait after which a sent keepalive ping is considered timed out. + /// + public TimeSpan KeepAliveTimeout { get; set; } = TimeSpan.FromSeconds(10); /// /// True if pointing to a single EventStoreDB node. @@ -88,6 +93,7 @@ public class EventStoreClientConnectivitySettings { DiscoveryInterval = TimeSpan.FromMilliseconds(100), NodePreference = NodePreference.Leader, KeepAliveInterval = TimeSpan.FromSeconds(10), + KeepAliveTimeout = TimeSpan.FromSeconds(10), }; } } diff --git a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs index 0b30709c6..937e3fe28 100644 --- a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs +++ b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading; #if !GRPC_CORE using System.Net.Http; #endif @@ -169,9 +170,19 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us settings.OperationOptions.ThrowOnAppendFailure = (bool)throwOnAppendFailure; if (typedOptions.TryGetValue(KeepAliveInterval, out var keepAliveIntervalMs)) { - settings.ConnectivitySettings.KeepAliveInterval = (int)keepAliveIntervalMs == -1 - ? new TimeSpan?() - : TimeSpan.FromMilliseconds((int)keepAliveIntervalMs); + settings.ConnectivitySettings.KeepAliveInterval = keepAliveIntervalMs switch { + int value when value == -1 => Timeout.InfiniteTimeSpan, + int value when value >= 0 => TimeSpan.FromMilliseconds(value), + _ => throw new InvalidSettingException($"Invalid KeepAliveInterval: {keepAliveIntervalMs}") + }; + } + + if (typedOptions.TryGetValue(KeepAliveTimeout, out var keepAliveTimeoutMs)) { + settings.ConnectivitySettings.KeepAliveTimeout = keepAliveTimeoutMs switch { + int value when value == -1 => Timeout.InfiniteTimeSpan, + int value when value >= 0 => TimeSpan.FromMilliseconds(value), + _ => throw new InvalidSettingException($"Invalid KeepAliveTimeout: {keepAliveTimeoutMs}") + }; } connSettings.Insecure = !useTls; @@ -188,16 +199,15 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us #if !GRPC_CORE settings.CreateHttpMessageHandler = () => { - var handler = new SocketsHttpHandler(); + var handler = new SocketsHttpHandler { + KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval, + KeepAlivePingTimeout = settings.ConnectivitySettings.KeepAliveTimeout + }; if (typedOptions.TryGetValue(TlsVerifyCert, out var tlsVerifyCert) && !(bool)tlsVerifyCert) { handler.SslOptions.RemoteCertificateValidationCallback = delegate { return true; }; } - if (settings.ConnectivitySettings.KeepAliveInterval.HasValue) { - handler.KeepAlivePingDelay = settings.ConnectivitySettings.KeepAliveInterval.Value; - } - return handler; }; #endif diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs index f388ac122..b740bafd6 100644 --- a/test/EventStore.Client.Tests/ConnectionStringTests.cs +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Threading; using Xunit; namespace EventStore.Client { @@ -90,7 +91,8 @@ public void connection_string_with_duplicate_key_should_throw(string connectionS InlineData("esdb://user:pass@127.0.0.1/?gossipTimeout=defg"), InlineData("esdb://user:pass@127.0.0.1/?tlsVerifyCert=truee"), InlineData("esdb://user:pass@127.0.0.1/?nodePreference=blabla"), - InlineData("esdb://user:pass@127.0.0.1/?keepAliveInterval=blabla")] + InlineData("esdb://user:pass@127.0.0.1/?keepAliveInterval=blabla"), + InlineData("esdb://user:pass@127.0.0.1/?keepAliveTimeout=blabla")] public void connection_string_with_invalid_settings_should_throw(string connectionString) { Assert.Throws(() => EventStoreClientSettings.Create(connectionString)); } @@ -109,7 +111,7 @@ public void with_different_node_preferences(string nodePreference, NodePreferenc [Fact] public void with_valid_single_node_connection_string() { var settings = EventStoreClientSettings.Create( - "esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=13&DiscoveryInterval=37&gossipTimeout=33&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&keepAliveInterval=10"); + "esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=13&DiscoveryInterval=37&gossipTimeout=33&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&keepAliveInterval=10&keepAliveTimeout=2000"); Assert.Equal("user", settings.DefaultCredentials.Username); Assert.Equal("pass", settings.DefaultCredentials.Password); Assert.Equal("https://127.0.0.1:2113/", settings.ConnectivitySettings.Address.ToString()); @@ -121,11 +123,12 @@ public void with_valid_single_node_connection_string() { Assert.Equal(33, settings.ConnectivitySettings.GossipTimeout.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif settings = EventStoreClientSettings.Create( - "esdb://127.0.0.1?connectionName=test&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tls=true&tlsVerifyCert=true&operationTimeout=330&throwOnAppendFailure=faLse&KEepAliveInTeRvAl=10"); + "esdb://127.0.0.1?connectionName=test&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tls=true&tlsVerifyCert=true&operationTimeout=330&throwOnAppendFailure=faLse&KEepAliveInTeRvAl=10&KeEpAlIvEtImEoUt=2000"); Assert.Null(settings.DefaultCredentials); Assert.Equal("test", settings.ConnectionName); Assert.Equal("https://127.0.0.1:2113/", settings.ConnectivitySettings.Address.ToString()); @@ -137,6 +140,7 @@ public void with_valid_single_node_connection_string() { Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif @@ -144,14 +148,15 @@ public void with_valid_single_node_connection_string() { Assert.Equal(330, settings.OperationOptions.TimeoutAfter.Value.TotalMilliseconds); Assert.False(settings.OperationOptions.ThrowOnAppendFailure); - settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false&keepAliveInterval=-1"); + settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false&keepAliveInterval=-1&keepAliveTimeout=-1"); Assert.Null(settings.DefaultCredentials); Assert.Equal("http://hostname:4321/", settings.ConnectivitySettings.Address.ToString()); Assert.Empty(settings.ConnectivitySettings.GossipSeeds); Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); Assert.True(settings.ConnectivitySettings.Insecure); - Assert.Null(settings.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(Timeout.InfiniteTimeSpan, settings.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(Timeout.InfiniteTimeSpan, settings.ConnectivitySettings.KeepAliveTimeout); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif @@ -183,12 +188,14 @@ public void with_default_settings() { settings.OperationOptions.ThrowOnAppendFailure); Assert.Equal(EventStoreClientConnectivitySettings.Default.KeepAliveInterval, settings.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(EventStoreClientConnectivitySettings.Default.KeepAliveTimeout, + settings.ConnectivitySettings.KeepAliveTimeout); } [Fact] public void with_valid_cluster_connection_string() { var settings = EventStoreClientSettings.Create( - "esdb://user:pass@127.0.0.1,127.0.0.2:3321,127.0.0.3/?maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRVAl=10"); + "esdb://user:pass@127.0.0.1,127.0.0.2:3321,127.0.0.3/?maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRVAl=10&KeEpAlIvEtImEoUt=2000"); Assert.Equal("user", settings.DefaultCredentials.Username); Assert.Equal("pass", settings.DefaultCredentials.Password); Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); @@ -206,13 +213,14 @@ public void with_valid_cluster_connection_string() { Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif settings = EventStoreClientSettings.Create( - "esdb://user:pass@host1,host2:3321,127.0.0.3/?tls=false&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRvAl=10"); + "esdb://user:pass@host1,host2:3321,127.0.0.3/?tls=false&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRvAl=10&KeEpAlIvEtImEoUt=2000"); Assert.Equal("user", settings.DefaultCredentials.Username); Assert.Equal("pass", settings.DefaultCredentials.Password); Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); @@ -230,6 +238,7 @@ public void with_valid_cluster_connection_string() { Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); #if !GRPC_CORE Assert.NotNull(settings.CreateHttpMessageHandler); #endif From 8f8cffd70a17686d294e748faeadba8cc8b37e61 Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Mon, 15 Feb 2021 13:05:57 +0100 Subject: [PATCH 4/5] add warning when keep alive interval exceeds minimum --- src/EventStore.Client/MultiChannel.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/EventStore.Client/MultiChannel.cs b/src/EventStore.Client/MultiChannel.cs index c3881c0a4..5e036b1d5 100644 --- a/src/EventStore.Client/MultiChannel.cs +++ b/src/EventStore.Client/MultiChannel.cs @@ -4,6 +4,8 @@ using System.Threading; using System.Threading.Tasks; using Grpc.Core; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; #nullable enable namespace EventStore.Client { @@ -11,9 +13,9 @@ internal class MultiChannel : IDisposable, IAsyncDisposable { private readonly EventStoreClientSettings _settings; private readonly IEndpointDiscoverer _endpointDiscoverer; private readonly ConcurrentDictionary _channels; + private readonly ILogger _log; private EndPoint? _current; - private int _disposed; public MultiChannel(EventStoreClientSettings settings) { @@ -22,6 +24,12 @@ public MultiChannel(EventStoreClientSettings settings) { ? (IEndpointDiscoverer)new SingleNodeEndpointDiscoverer(settings.ConnectivitySettings.Address) : new GossipBasedEndpointDiscoverer(settings.ConnectivitySettings, new GrpcGossipClient(settings)); _channels = new ConcurrentDictionary(); + _log = settings.LoggerFactory?.CreateLogger() ?? new NullLogger(); + + if (settings.ConnectivitySettings.KeepAliveInterval < TimeSpan.FromSeconds(10)) { + _log.LogWarning("Specified KeepAliveInterval of {interval} is less than recommended 10_000 ms.", + settings.ConnectivitySettings.KeepAliveInterval); + } } public void SetEndPoint(EndPoint value) => _current = value; From a6e403a6fa3cf91767a9c846006830133e7b281a Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Wed, 17 Feb 2021 11:10:04 +0100 Subject: [PATCH 5/5] refactored tests --- test/Directory.Build.props | 1 + .../ConnectionStringTests.cs | 432 ++++++++++-------- 2 files changed, 244 insertions(+), 189 deletions(-) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 467d205d3..1d09bfb7c 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,6 +1,7 @@ + diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs index b740bafd6..d36357507 100644 --- a/test/EventStore.Client.Tests/ConnectionStringTests.cs +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -1,11 +1,145 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Threading; +#if !GRPC_CORE +using System.Net.Http; +#endif +using AutoFixture; using Xunit; namespace EventStore.Client { public class ConnectionStringTests { + public static IEnumerable ValidCases() { + var fixture = new Fixture(); + fixture.Customize(composer => composer.FromFactory(s => TimeSpan.FromSeconds(s % 60))); + fixture.Customize(composer => composer.FromFactory(e => new UriBuilder { + Host = e.Host, + Port = e.Port == 80 ? 81 : e.Port + }.Uri)); + + return Enumerable.Range(0, 3).SelectMany(GetTestCases); + + IEnumerable GetTestCases(int _) { + var settings = new EventStoreClientSettings { + ConnectionName = fixture.Create(), + ConnectivitySettings = fixture.Create(), + OperationOptions = fixture.Create() + }; + + settings.ConnectivitySettings.Address = + new UriBuilder(EventStoreClientConnectivitySettings.Default.Address) { + Scheme = settings.ConnectivitySettings.Insecure ? Uri.UriSchemeHttp : Uri.UriSchemeHttps + }.Uri; + + yield return new object[] { + GetConnectionString(settings), + settings + }; + + yield return new object[] { + GetConnectionString(settings, MockingTone), + settings + }; + + var ipGossipSettings = new EventStoreClientSettings { + ConnectionName = fixture.Create(), + ConnectivitySettings = fixture.Create(), + OperationOptions = fixture.Create() + }; + + ipGossipSettings.ConnectivitySettings.Address = + new UriBuilder(EventStoreClientConnectivitySettings.Default.Address) { + Scheme = ipGossipSettings.ConnectivitySettings.Insecure ? Uri.UriSchemeHttp : Uri.UriSchemeHttps + }.Uri; + + ipGossipSettings.ConnectivitySettings.DnsGossipSeeds = null; + + yield return new object[] { + GetConnectionString(ipGossipSettings), + ipGossipSettings + }; + + yield return new object[] { + GetConnectionString(ipGossipSettings, MockingTone), + ipGossipSettings + }; + + var singleNodeSettings = new EventStoreClientSettings { + ConnectionName = fixture.Create(), + ConnectivitySettings = fixture.Create(), + OperationOptions = fixture.Create() + }; + singleNodeSettings.ConnectivitySettings.DnsGossipSeeds = null; + singleNodeSettings.ConnectivitySettings.IpGossipSeeds = null; + singleNodeSettings.ConnectivitySettings.Address = new UriBuilder(fixture.Create()) { + Scheme = singleNodeSettings.ConnectivitySettings.Insecure ? Uri.UriSchemeHttp : Uri.UriSchemeHttps + }.Uri; + + yield return new object[] { + GetConnectionString(singleNodeSettings), + singleNodeSettings + }; + + yield return new object[] { + GetConnectionString(singleNodeSettings, MockingTone), + singleNodeSettings + }; + } + + static string MockingTone(string key) => + new string(key.Select((c, i) => i % 2 == 0 ? char.ToUpper(c) : char.ToLower(c)).ToArray()); + } + + [Theory, MemberData(nameof(ValidCases))] + public void valid_connection_string(string connectionString, EventStoreClientSettings expected) { + var result = EventStoreClientSettings.Create(connectionString); + + Assert.Equal(expected, result, EventStoreClientSettingsEqualityComparer.Instance); + } + + [Theory, MemberData(nameof(ValidCases))] + public void valid_connection_string_with_empty_path(string connectionString, EventStoreClientSettings expected) { + var result = EventStoreClientSettings.Create(connectionString.Replace("?", "/?")); + + Assert.Equal(expected, result, EventStoreClientSettingsEqualityComparer.Instance); + } + +#if !GRPC_CORE + [Theory, InlineData(false), InlineData(true)] + public void tls_verify_cert(bool tlsVerifyCert) { + var connectionString = $"esdb://localhost:2113/?tlsVerifyCert={tlsVerifyCert}"; + var result = EventStoreClientSettings.Create(connectionString); + using var handler = result.CreateHttpMessageHandler?.Invoke(); + var socketsHandler = Assert.IsType(handler); + if (!tlsVerifyCert) { + Assert.NotNull(socketsHandler.SslOptions.RemoteCertificateValidationCallback); + Assert.True(socketsHandler.SslOptions.RemoteCertificateValidationCallback.Invoke(null!, default, + default, default)); + } else { + Assert.Null(socketsHandler.SslOptions.RemoteCertificateValidationCallback); + } + } + +#endif + + [Fact] + public void infinite_grpc_timeouts() { + var result = + EventStoreClientSettings.Create("esdb://localhost:2113?keepAliveInterval=-1&keepAliveTimeout=-1"); + + Assert.Equal(Timeout.InfiniteTimeSpan, result.ConnectivitySettings.KeepAliveInterval); + Assert.Equal(Timeout.InfiniteTimeSpan, result.ConnectivitySettings.KeepAliveTimeout); + +#if !GRPC_CORE + using var handler = result.CreateHttpMessageHandler?.Invoke(); + var socketsHandler = Assert.IsType(handler); + Assert.Equal(Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingTimeout); + Assert.Equal(Timeout.InfiniteTimeSpan, socketsHandler.KeepAlivePingDelay); +#endif + } + [Fact] public void connection_string_with_no_schema() { Assert.Throws(() => EventStoreClientSettings.Create(":so/mething/random")); @@ -42,15 +176,6 @@ public void connection_string_with_invalid_host_should_throw(string connectionSt Assert.Throws(() => EventStoreClientSettings.Create(connectionString)); } - [Theory, - InlineData("esdb://user:pass@127.0.0.1"), - InlineData("esdb://user:pass@127.0.0.1:1234"), - InlineData("esdb://user:pass@127.0.0.1/"), - InlineData("esdb://user:pass@127.0.0.1?maxDiscoverAttempts=10"), - InlineData("esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=10")] - public void connection_string_with_empty_path_after_host_should_not_throw(string connectionString) { - EventStoreClientSettings.Create(connectionString); - } [Theory, InlineData("esdb://user:pass@127.0.0.1/test"), @@ -91,77 +216,12 @@ public void connection_string_with_duplicate_key_should_throw(string connectionS InlineData("esdb://user:pass@127.0.0.1/?gossipTimeout=defg"), InlineData("esdb://user:pass@127.0.0.1/?tlsVerifyCert=truee"), InlineData("esdb://user:pass@127.0.0.1/?nodePreference=blabla"), - InlineData("esdb://user:pass@127.0.0.1/?keepAliveInterval=blabla"), - InlineData("esdb://user:pass@127.0.0.1/?keepAliveTimeout=blabla")] + InlineData("esdb://user:pass@127.0.0.1/?keepAliveInterval=-2"), + InlineData("esdb://user:pass@127.0.0.1/?keepAliveTimeout=-2")] public void connection_string_with_invalid_settings_should_throw(string connectionString) { Assert.Throws(() => EventStoreClientSettings.Create(connectionString)); } - [Theory, - InlineData("leader", NodePreference.Leader), - InlineData("Follower", NodePreference.Follower), - InlineData("rAndom", NodePreference.Random), - InlineData("ReadOnlyReplica", NodePreference.ReadOnlyReplica)] - public void with_different_node_preferences(string nodePreference, NodePreference expected) { - Assert.Equal(expected, - EventStoreClientSettings.Create($"esdb://user:pass@127.0.0.1/?nodePreference={nodePreference}") - .ConnectivitySettings.NodePreference); - } - - [Fact] - public void with_valid_single_node_connection_string() { - var settings = EventStoreClientSettings.Create( - "esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=13&DiscoveryInterval=37&gossipTimeout=33&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&keepAliveInterval=10&keepAliveTimeout=2000"); - Assert.Equal("user", settings.DefaultCredentials.Username); - Assert.Equal("pass", settings.DefaultCredentials.Password); - Assert.Equal("https://127.0.0.1:2113/", settings.ConnectivitySettings.Address.ToString()); - Assert.Empty(settings.ConnectivitySettings.GossipSeeds); - Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); - Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); - Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); - Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); - Assert.Equal(33, settings.ConnectivitySettings.GossipTimeout.TotalMilliseconds); - Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); - Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); -#if !GRPC_CORE - Assert.NotNull(settings.CreateHttpMessageHandler); -#endif - settings = EventStoreClientSettings.Create( - "esdb://127.0.0.1?connectionName=test&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tls=true&tlsVerifyCert=true&operationTimeout=330&throwOnAppendFailure=faLse&KEepAliveInTeRvAl=10&KeEpAlIvEtImEoUt=2000"); - Assert.Null(settings.DefaultCredentials); - Assert.Equal("test", settings.ConnectionName); - Assert.Equal("https://127.0.0.1:2113/", settings.ConnectivitySettings.Address.ToString()); - Assert.Empty(settings.ConnectivitySettings.GossipSeeds); - Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); - Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); - Assert.False(settings.ConnectivitySettings.Insecure); - Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); - Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); - Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); - Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); -#if !GRPC_CORE - Assert.NotNull(settings.CreateHttpMessageHandler); -#endif - - Assert.Equal(330, settings.OperationOptions.TimeoutAfter.Value.TotalMilliseconds); - Assert.False(settings.OperationOptions.ThrowOnAppendFailure); - - settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false&keepAliveInterval=-1&keepAliveTimeout=-1"); - Assert.Null(settings.DefaultCredentials); - Assert.Equal("http://hostname:4321/", settings.ConnectivitySettings.Address.ToString()); - Assert.Empty(settings.ConnectivitySettings.GossipSeeds); - Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); - Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); - Assert.True(settings.ConnectivitySettings.Insecure); - Assert.Equal(Timeout.InfiniteTimeSpan, settings.ConnectivitySettings.KeepAliveInterval); - Assert.Equal(Timeout.InfiniteTimeSpan, settings.ConnectivitySettings.KeepAliveTimeout); -#if !GRPC_CORE - Assert.NotNull(settings.CreateHttpMessageHandler); -#endif - } - [Fact] public void with_default_settings() { var settings = EventStoreClientSettings.Create("esdb://hostname:4321/"); @@ -192,131 +252,125 @@ public void with_default_settings() { settings.ConnectivitySettings.KeepAliveTimeout); } - [Fact] - public void with_valid_cluster_connection_string() { - var settings = EventStoreClientSettings.Create( - "esdb://user:pass@127.0.0.1,127.0.0.2:3321,127.0.0.3/?maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRVAl=10&KeEpAlIvEtImEoUt=2000"); - Assert.Equal("user", settings.DefaultCredentials.Username); - Assert.Equal("pass", settings.DefaultCredentials.Password); - Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); - Assert.NotNull(settings.ConnectivitySettings.IpGossipSeeds); - Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); - Assert.False(settings.ConnectivitySettings.Insecure); - Assert.True(settings.ConnectivitySettings.IpGossipSeeds.Length == 3 && - Equals(settings.ConnectivitySettings.IpGossipSeeds[0].Address, IPAddress.Parse("127.0.0.1")) && - Equals(settings.ConnectivitySettings.IpGossipSeeds[0].Port, 2113) && - Equals(settings.ConnectivitySettings.IpGossipSeeds[1].Address, IPAddress.Parse("127.0.0.2")) && - Equals(settings.ConnectivitySettings.IpGossipSeeds[1].Port, 3321) && - Equals(settings.ConnectivitySettings.IpGossipSeeds[2].Address, IPAddress.Parse("127.0.0.3")) && - Equals(settings.ConnectivitySettings.IpGossipSeeds[2].Port, 2113)); - Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); - Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); - Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); - Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); -#if !GRPC_CORE - Assert.NotNull(settings.CreateHttpMessageHandler); -#endif + private static string GetConnectionString(EventStoreClientSettings settings, + Func getKey = default) + => $"{GetScheme(settings)}{GetAuthority(settings)}?{GetKeyValuePairs(settings, getKey)}"; + + private static string GetScheme(EventStoreClientSettings settings) => settings.ConnectivitySettings.IsSingleNode + ? "esdb://" + : "esdb+discover://"; + + private static string GetAuthority(EventStoreClientSettings settings) => + settings.ConnectivitySettings.IsSingleNode + ? $"{settings.ConnectivitySettings.Address.Host}:{settings.ConnectivitySettings.Address.Port}" + : string.Join(",", + settings.ConnectivitySettings.GossipSeeds.Select(x => $"{x.GetHost()}:{x.GetPort()}")); + + private static string GetKeyValuePairs(EventStoreClientSettings settings, + Func getKey = default) { + var pairs = new Dictionary { + ["tls"] = (!settings.ConnectivitySettings.Insecure).ToString(), + ["connectionName"] = settings.ConnectionName, + ["maxDiscoverAttempts"] = settings.ConnectivitySettings.MaxDiscoverAttempts.ToString(), + ["discoveryInterval"] = settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds.ToString(), + ["gossipTimeout"] = settings.ConnectivitySettings.GossipTimeout.TotalMilliseconds.ToString(), + ["nodePreference"] = settings.ConnectivitySettings.NodePreference.ToString(), + ["keepAliveInterval"] = settings.ConnectivitySettings.KeepAliveInterval.TotalMilliseconds.ToString(), + ["keepAliveTimeout"] = settings.ConnectivitySettings.KeepAliveTimeout.TotalMilliseconds.ToString(), + }; + if (settings.OperationOptions.TimeoutAfter.HasValue) { + pairs.Add("operationTimeout", + settings.OperationOptions.TimeoutAfter.Value.TotalMilliseconds.ToString()); + } - settings = EventStoreClientSettings.Create( - "esdb://user:pass@host1,host2:3321,127.0.0.3/?tls=false&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false&KEepAliveInTeRvAl=10&KeEpAlIvEtImEoUt=2000"); - Assert.Equal("user", settings.DefaultCredentials.Username); - Assert.Equal("pass", settings.DefaultCredentials.Password); - Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); - Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); - Assert.NotNull(settings.ConnectivitySettings.DnsGossipSeeds); - Assert.True(settings.ConnectivitySettings.Insecure); - Assert.True(settings.ConnectivitySettings.DnsGossipSeeds.Length == 3 && - Equals(settings.ConnectivitySettings.DnsGossipSeeds[0].Host, "host1") && - Equals(settings.ConnectivitySettings.DnsGossipSeeds[0].Port, 2113) && - Equals(settings.ConnectivitySettings.DnsGossipSeeds[1].Host, "host2") && - Equals(settings.ConnectivitySettings.DnsGossipSeeds[1].Port, 3321) && - Equals(settings.ConnectivitySettings.DnsGossipSeeds[2].Host, "127.0.0.3") && - Equals(settings.ConnectivitySettings.DnsGossipSeeds[2].Port, 2113)); - Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); - Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalMilliseconds); - Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); - Assert.Equal(TimeSpan.FromMilliseconds(10), settings.ConnectivitySettings.KeepAliveInterval); - Assert.Equal(TimeSpan.FromSeconds(2), settings.ConnectivitySettings.KeepAliveTimeout); #if !GRPC_CORE - Assert.NotNull(settings.CreateHttpMessageHandler); + if (settings.CreateHttpMessageHandler != null) { + using var handler = settings.CreateHttpMessageHandler.Invoke(); + if (handler is SocketsHttpHandler socketsHttpHandler && + socketsHttpHandler.SslOptions.RemoteCertificateValidationCallback != null) { + pairs.Add("tlsVerifyCert", "false"); + } + } #endif - } - - [Fact] - public void with_different_tls_settings() { - EventStoreClientSettings settings; - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1/"); - Assert.Equal(Uri.UriSchemeHttps, settings.ConnectivitySettings.Address.Scheme); - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1?tls=true"); - Assert.Equal(Uri.UriSchemeHttps, settings.ConnectivitySettings.Address.Scheme); - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?tls=FaLsE"); - Assert.Equal(Uri.UriSchemeHttp, settings.ConnectivitySettings.Address.Scheme); - settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/"); - Assert.False(settings.ConnectivitySettings.Insecure); - settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3?tls=true"); - Assert.False(settings.ConnectivitySettings.Insecure); - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/?tls=fAlSe"); - Assert.True(settings.ConnectivitySettings.Insecure); + return string.Join("&", pairs.Select(pair => $"{getKey?.Invoke(pair.Key) ?? pair.Key}={pair.Value}")); } -#if !GRPC_CORE - [Fact] - public void with_different_tls_verify_cert_settings() { - EventStoreClientSettings settings; - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1/"); - Assert.NotNull(settings.CreateHttpMessageHandler); - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?tlsVerifyCert=TrUe"); - Assert.NotNull(settings.CreateHttpMessageHandler); - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?tlsVerifyCert=FaLsE"); - Assert.NotNull(settings.CreateHttpMessageHandler); - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/"); - Assert.NotNull(settings.CreateHttpMessageHandler); - - settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/?tlsVerifyCert=true"); - Assert.NotNull(settings.CreateHttpMessageHandler); - - settings = EventStoreClientSettings.Create( - "esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/?tlsVerifyCert=false"); - Assert.NotNull(settings.CreateHttpMessageHandler); + private class EventStoreClientSettingsEqualityComparer : IEqualityComparer { + public static readonly EventStoreClientSettingsEqualityComparer Instance = + new EventStoreClientSettingsEqualityComparer(); + + public bool Equals(EventStoreClientSettings x, EventStoreClientSettings y) { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return x.ConnectionName == y.ConnectionName && + EventStoreClientConnectivitySettingsEqualityComparer.Instance.Equals(x.ConnectivitySettings, + y.ConnectivitySettings) && + EventStoreClientOperationOptionsEqualityComparer.Instance.Equals(x.OperationOptions, + y.OperationOptions) && + Equals(x.DefaultCredentials?.ToString(), y.DefaultCredentials?.ToString()); + } + + public int GetHashCode(EventStoreClientSettings obj) => HashCode.Hash + .Combine(obj.ConnectionName) + .Combine(EventStoreClientConnectivitySettingsEqualityComparer.Instance.GetHashCode( + obj.ConnectivitySettings)) + .Combine(EventStoreClientOperationOptionsEqualityComparer.Instance.GetHashCode(obj.OperationOptions)); } -#endif - public static IEnumerable DiscoverSchemeCases() { - yield return new object[] { - "esdb+discover://hostname:4321", new EndPoint[] { - new DnsEndPoint("hostname", 4321) - } - }; - yield return new object[] { - "esdb+discover://hostname:4321/", new EndPoint[] { - new DnsEndPoint("hostname", 4321) - } - }; - yield return new object[] { - "esdb+discover://hostname0:4321,hostname1:4321,hostname2:4321/", new EndPoint[] { - new DnsEndPoint("hostname0", 4321), - new DnsEndPoint("hostname1", 4321), - new DnsEndPoint("hostname2", 4321) - } - }; + private class EventStoreClientConnectivitySettingsEqualityComparer + : IEqualityComparer { + public static readonly EventStoreClientConnectivitySettingsEqualityComparer Instance = + new EventStoreClientConnectivitySettingsEqualityComparer(); + + public bool Equals(EventStoreClientConnectivitySettings x, EventStoreClientConnectivitySettings y) { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return (!x.IsSingleNode || x.Address.Equals(y.Address)) && + x.MaxDiscoverAttempts == y.MaxDiscoverAttempts && + x.GossipSeeds.SequenceEqual(y.GossipSeeds) && + x.GossipTimeout.Equals(y.GossipTimeout) && + x.DiscoveryInterval.Equals(y.DiscoveryInterval) && + x.NodePreference == y.NodePreference && + x.KeepAliveInterval.Equals(y.KeepAliveInterval) && + x.KeepAliveTimeout.Equals(y.KeepAliveTimeout) && + x.Insecure == y.Insecure; + } + + public int GetHashCode(EventStoreClientConnectivitySettings obj) => + obj.GossipSeeds.Aggregate( + HashCode.Hash + .Combine(obj.Address.GetHashCode()) + .Combine(obj.MaxDiscoverAttempts) + .Combine(obj.GossipTimeout) + .Combine(obj.DiscoveryInterval) + .Combine(obj.NodePreference) + .Combine(obj.KeepAliveInterval) + .Combine(obj.KeepAliveTimeout) + .Combine(obj.Insecure), (hashcode, endpoint) => hashcode.Combine(endpoint.GetHashCode())); } - [Theory, MemberData(nameof(DiscoverSchemeCases))] - public void with_esdb_discover_scheme(string connectionString, EndPoint[] expected) { - var settings = EventStoreClientSettings.Create(connectionString); - Assert.Equal(expected, settings.ConnectivitySettings.GossipSeeds); + private class EventStoreClientOperationOptionsEqualityComparer + : IEqualityComparer { + public static readonly EventStoreClientOperationOptionsEqualityComparer Instance = + new EventStoreClientOperationOptionsEqualityComparer(); + + public bool Equals(EventStoreClientOperationOptions x, EventStoreClientOperationOptions y) { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return Nullable.Equals(x.TimeoutAfter, y.TimeoutAfter); + } + + public int GetHashCode(EventStoreClientOperationOptions obj) => + HashCode.Hash.Combine(obj.TimeoutAfter).Combine(obj.ThrowOnAppendFailure); } } }