Skip to content

Commit

Permalink
Merge pull request #91 from thefringeninja/dns
Browse files Browse the repository at this point in the history
Add Support for Single DNS Gossip Seed
  • Loading branch information
thefringeninja authored Dec 9, 2020
2 parents 3da36a4 + 2205327 commit 0059470
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 260 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
run: |
docker pull docker.pkg.github.com/eventstore/eventstore/eventstore:${{ matrix.docker-tag }}
- name: Install Dotnet
uses: actions/setup-dotnet@v1.4.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
- name: Compile
Expand Down Expand Up @@ -103,7 +103,7 @@ jobs:
run: |
git fetch --prune --unshallow
- name: Setup Dotnet
uses: actions/setup-dotnet@v1.4.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
- name: Dotnet Pack
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
run: |
git fetch --prune --unshallow
- name: Install Dotnet
uses: actions/setup-dotnet@v1.4.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
- name: Compile
Expand Down
2 changes: 1 addition & 1 deletion src/EventStore.Client/ClusterAwareHttpHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
} catch (Exception) {
Interlocked.CompareExchange(ref _endpoint,
new Lazy<Task<EndPoint>>(() => _endpointDiscoverer.DiscoverAsync(),
new Lazy<Task<EndPoint>>(_endpointDiscoverer.DiscoverAsync,
LazyThreadSafetyMode.ExecutionAndPublication), endpointResolver);

throw;
Expand Down
147 changes: 77 additions & 70 deletions src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,35 @@ private static class ConnectionStringParser {
private const string Ampersand = "&";
private const string Equal = "=";
private const string QuestionMark = "?";
private static readonly string[] Schemes = { "esdb" };

private const string Tls = nameof(Tls);
private const string ConnectionName = "ConnectionName";
private const string MaxDiscoverAttempts = "MaxDiscoverAttempts";
private const string DiscoveryInterval = "DiscoveryInterval";
private const string GossipTimeout = "GossipTimeout";
private const string NodePreference = "NodePreference";
private const string TlsVerifyCert = "TlsVerifyCert";
private const string OperationTimeout = "OperationTimeout";
private const string ThrowOnAppendFailure = "ThrowOnAppendFailure";

private const string UriSchemeDiscover = "esdb+discover";

private static readonly string[] Schemes = {"esdb", UriSchemeDiscover};
private static readonly int DefaultPort = EventStoreClientConnectivitySettings.Default.Address.Port;
private static readonly bool DefaultUseTls = true;

private static readonly Dictionary<string, Type> SettingsType = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase) {
{"ConnectionName", typeof(string)},
{"MaxDiscoverAttempts", typeof(int)},
{"DiscoveryInterval", typeof(int)},
{"GossipTimeout", typeof(int)},
{"NodePreference", typeof(string)},
{"Tls", typeof(bool)},
{"TlsVerifyCert", typeof(bool)},
{"OperationTimeout", typeof(int)},
{"ThrowOnAppendFailure", typeof(bool)}
};
private static readonly Dictionary<string, Type> SettingsType =
new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase) {
{ConnectionName, typeof(string)},
{MaxDiscoverAttempts, typeof(int)},
{DiscoveryInterval, typeof(int)},
{GossipTimeout, typeof(int)},
{NodePreference, typeof(string)},
{Tls, typeof(bool)},
{TlsVerifyCert, typeof(bool)},
{OperationTimeout, typeof(int)},
{ThrowOnAppendFailure, typeof(bool)}
};

public static EventStoreClientSettings Parse(string connectionString) {
var currentIndex = 0;
Expand All @@ -57,23 +71,26 @@ public static EventStoreClientSettings Parse(string connectionString) {


var slashIndex = connectionString.IndexOf(Slash, currentIndex, StringComparison.Ordinal);
var questionMarkIndex = connectionString.IndexOf(QuestionMark, Math.Max(currentIndex, slashIndex), StringComparison.Ordinal);
var questionMarkIndex = connectionString.IndexOf(QuestionMark, Math.Max(currentIndex, slashIndex),
StringComparison.Ordinal);
var endIndex = connectionString.Length;

//for simpler substring operations:
if (slashIndex == -1) slashIndex = int.MaxValue;
if (questionMarkIndex == -1) questionMarkIndex = int.MaxValue;

var hostSeparatorIndex = Math.Min(Math.Min(slashIndex, questionMarkIndex), endIndex);
var hosts = ParseHosts(connectionString.Substring(currentIndex,hostSeparatorIndex - currentIndex));
var hosts = ParseHosts(connectionString.Substring(currentIndex, hostSeparatorIndex - currentIndex));
currentIndex = hostSeparatorIndex;

string path = "";
if (slashIndex != int.MaxValue)
path = connectionString.Substring(currentIndex,Math.Min(questionMarkIndex, endIndex) - currentIndex);
path = connectionString.Substring(currentIndex,
Math.Min(questionMarkIndex, endIndex) - currentIndex);

if (path != "" && path != "/")
throw new ConnectionStringParseException($"The specified path must be either an empty string or a forward slash (/) but the following path was found instead: '{path}'");
throw new ConnectionStringParseException(
$"The specified path must be either an empty string or a forward slash (/) but the following path was found instead: '{path}'");

var options = new Dictionary<string, string>();
if (questionMarkIndex != int.MaxValue) {
Expand All @@ -84,7 +101,8 @@ public static EventStoreClientSettings Parse(string connectionString) {
return CreateSettings(scheme, userInfo, hosts, options);
}

private static EventStoreClientSettings CreateSettings(string scheme, (string user, string pass) userInfo, EndPoint[] hosts, Dictionary<string, string> options) {
private static EventStoreClientSettings CreateSettings(string scheme, (string user, string pass) userInfo,
EndPoint[] hosts, Dictionary<string, string> options) {
var settings = new EventStoreClientSettings {
ConnectivitySettings = EventStoreClientConnectivitySettings.Default,
OperationOptions = EventStoreClientOperationOptions.Default
Expand All @@ -94,61 +112,52 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us
settings.DefaultCredentials = new UserCredentials(userInfo.user, userInfo.pass);

var typedOptions = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
foreach (var option in options) {
if (!SettingsType.TryGetValue(option.Key, out var type)) throw new InvalidSettingException($"Unknown option: {option.Key}");
if(type == typeof(int)){
if (!int.TryParse(option.Value, out var intValue))
throw new InvalidSettingException($"{option.Key} must be an integer value");
typedOptions.Add(option.Key, intValue);
foreach (var (key, value) in options) {
if (!SettingsType.TryGetValue(key, out var type))
throw new InvalidSettingException($"Unknown option: {key}");
if (type == typeof(int)) {
if (!int.TryParse(value, out var intValue))
throw new InvalidSettingException($"{key} must be an integer value");
typedOptions.Add(key, intValue);
} else if (type == typeof(bool)) {
if (!bool.TryParse(option.Value, out var boolValue))
throw new InvalidSettingException($"{option.Key} must be either true or false");
typedOptions.Add(option.Key, boolValue);
if (!bool.TryParse(value, out var boolValue))
throw new InvalidSettingException($"{key} must be either true or false");
typedOptions.Add(key, boolValue);
} else if (type == typeof(string)) {
typedOptions.Add(option.Key, option.Value);
typedOptions.Add(key, value);
}
}

if (typedOptions.TryGetValue("ConnectionName", out object connectionName))
settings.ConnectionName = (string) connectionName;
if (typedOptions.TryGetValue(ConnectionName, out object connectionName))
settings.ConnectionName = (string)connectionName;

var connSettings = settings.ConnectivitySettings;

if (typedOptions.TryGetValue("MaxDiscoverAttempts", out object maxDiscoverAttempts))
if (typedOptions.TryGetValue(MaxDiscoverAttempts, out object maxDiscoverAttempts))
connSettings.MaxDiscoverAttempts = (int)maxDiscoverAttempts;

if (typedOptions.TryGetValue("DiscoveryInterval", out object discoveryInterval))
if (typedOptions.TryGetValue(DiscoveryInterval, out object discoveryInterval))
connSettings.DiscoveryInterval = TimeSpan.FromMilliseconds((int)discoveryInterval);

if (typedOptions.TryGetValue("GossipTimeout", out object gossipTimeout))
if (typedOptions.TryGetValue(GossipTimeout, out object gossipTimeout))
connSettings.GossipTimeout = TimeSpan.FromMilliseconds((int)gossipTimeout);

if (typedOptions.TryGetValue("NodePreference", out object nodePreference)) {
var nodePreferenceLowerCase = ((string)nodePreference).ToLowerInvariant();
switch (nodePreferenceLowerCase) {
case "leader":
connSettings.NodePreference = NodePreference.Leader;
break;
case "follower":
connSettings.NodePreference = NodePreference.Follower;
break;
case "random":
connSettings.NodePreference = NodePreference.Random;
break;
case "readonlyreplica":
connSettings.NodePreference = NodePreference.ReadOnlyReplica;
break;
default:
throw new InvalidSettingException($"Invalid NodePreference: {nodePreference}");
}
if (typedOptions.TryGetValue(NodePreference, out object nodePreference)) {
connSettings.NodePreference = ((string)nodePreference).ToLowerInvariant() switch {
"leader" => EventStore.Client.NodePreference.Leader,
"follower" => EventStore.Client.NodePreference.Follower,
"random" => EventStore.Client.NodePreference.Random,
"readonlyreplica" => EventStore.Client.NodePreference.ReadOnlyReplica,
_ => throw new InvalidSettingException($"Invalid NodePreference: {nodePreference}")
};
}

var useTls = DefaultUseTls;
if(typedOptions.TryGetValue("Tls", out object tls)) {
if (typedOptions.TryGetValue(Tls, out object tls)) {
useTls = (bool)tls;
}

if (typedOptions.TryGetValue("TlsVerifyCert", out object tlsVerifyCert)) {
if (typedOptions.TryGetValue(TlsVerifyCert, out object tlsVerifyCert)) {
if (!(bool)tlsVerifyCert) {
#if NETCOREAPP3_1
settings.CreateHttpMessageHandler = () => new SocketsHttpHandler {
Expand All @@ -164,32 +173,31 @@ private static EventStoreClientSettings CreateSettings(string scheme, (string us
}
}

if(typedOptions.TryGetValue("OperationTimeout", out object operationTimeout))
settings.OperationOptions.TimeoutAfter = TimeSpan.FromMilliseconds((int) operationTimeout);
if (typedOptions.TryGetValue(OperationTimeout, out object operationTimeout))
settings.OperationOptions.TimeoutAfter = TimeSpan.FromMilliseconds((int)operationTimeout);

if(typedOptions.TryGetValue("ThrowOnAppendFailure", out object throwOnAppendFailure))
settings.OperationOptions.ThrowOnAppendFailure = (bool) throwOnAppendFailure;
if (typedOptions.TryGetValue(ThrowOnAppendFailure, out object throwOnAppendFailure))
settings.OperationOptions.ThrowOnAppendFailure = (bool)throwOnAppendFailure;

if (hosts.Length == 1) {
connSettings.Address = new Uri(hosts[0].ToHttpUrl(useTls?Uri.UriSchemeHttps:Uri.UriSchemeHttp));
if (hosts.Length == 1 && scheme != UriSchemeDiscover) {
connSettings.Address = new Uri(hosts[0].ToHttpUrl(useTls ? Uri.UriSchemeHttps : Uri.UriSchemeHttp));
} else {
if (hosts.Any(x => x is DnsEndPoint))
connSettings.DnsGossipSeeds = hosts.Select(x => new DnsEndPoint(x.GetHost(), x.GetPort())).ToArray();
connSettings.DnsGossipSeeds =
Array.ConvertAll(hosts, x => new DnsEndPoint(x.GetHost(), x.GetPort()));
else
connSettings.IpGossipSeeds = hosts.Select(x => x as IPEndPoint).ToArray();
connSettings.IpGossipSeeds = Array.ConvertAll(hosts, x => x as IPEndPoint);

connSettings.GossipOverHttps = useTls;
}

return settings;
}

private static string ParseScheme(string s) {
if (!Schemes.Contains(s)) throw new InvalidSchemeException(s, Schemes);
return s;
}
private static string ParseScheme(string s) =>
!Schemes.Contains(s) ? throw new InvalidSchemeException(s, Schemes) : s;

private static (string,string) ParseUserInfo(string s) {
private static (string, string) ParseUserInfo(string s) {
var tokens = s.Split(Colon);
if (tokens.Length != 2) throw new InvalidUserCredentialsException(s);
return (tokens[0], tokens[1]);
Expand All @@ -202,14 +210,12 @@ private static EndPoint[] ParseHosts(string s) {
var hostPortToken = hostToken.Split(Colon);
string host;
int port;
switch (hostPortToken.Length)
{
switch (hostPortToken.Length) {
case 1:
host = hostPortToken[0];
port = DefaultPort;
break;
case 2:
{
case 2: {
host = hostPortToken[0];
if (!int.TryParse(hostPortToken[1], out port))
throw new InvalidHostException(hostToken);
Expand Down Expand Up @@ -244,10 +250,11 @@ private static Dictionary<string, string> ParseKeyValuePairs(string s) {
throw new DuplicateKeyException(key);
}
}

return options;
}

private static (string,string) ParseKeyValuePair(string s) {
private static (string, string) ParseKeyValuePair(string s) {
var keyValueToken = s.Split(Equal);
if (keyValueToken.Length != 2) {
throw new InvalidKeyValuePairException(s);
Expand Down
27 changes: 8 additions & 19 deletions test/EventStore.Client.Tests.Common/EventStoreClientFixtureBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,14 @@ protected EventStoreClientFixtureBase(EventStoreClientSettings? clientSettings,
IDictionary<string, string>? env = null) {
_disposables = new List<IDisposable>();
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Settings = clientSettings ?? new EventStoreClientSettings {
CreateHttpMessageHandler = () => new SocketsHttpHandler {
SslOptions = {
RemoteCertificateValidationCallback = delegate { return true; }
}
},
OperationOptions = {
TimeoutAfter = Debugger.IsAttached
? new TimeSpan?()
: TimeSpan.FromSeconds(30)
},
ConnectivitySettings = {
Address = new UriBuilder {
Scheme = Uri.UriSchemeHttps,
Port = 2113
}.Uri
},
LoggerFactory = new SerilogLoggerFactory()
};

Settings = clientSettings ?? EventStoreClientSettings.Create("esdb://localhost:2113/?tlsVerifyCert=false");

Settings.OperationOptions.TimeoutAfter = Debugger.IsAttached
? new TimeSpan?()
: TimeSpan.FromSeconds(30);

Settings.LoggerFactory ??= new SerilogLoggerFactory();

TestServer = new EventStoreTestServer(Settings.ConnectivitySettings.Address, env);
}
Expand Down
Loading

0 comments on commit 0059470

Please sign in to comment.