diff --git a/src/Squidex.Infrastructure/IPAddressComparer.cs b/src/Squidex.Infrastructure/IPAddressComparer.cs new file mode 100644 index 000000000..fb4bcb9e7 --- /dev/null +++ b/src/Squidex.Infrastructure/IPAddressComparer.cs @@ -0,0 +1,42 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Net; + +namespace Squidex.Infrastructure +{ + public sealed class IPAddressComparer : IComparer + { + public static readonly IPAddressComparer Instance = new IPAddressComparer(); + + private IPAddressComparer() + { + } + + public int Compare(IPAddress x, IPAddress y) + { + var lbytes = x.GetAddressBytes(); + var rbytes = y.GetAddressBytes(); + + if (lbytes.Length != rbytes.Length) + { + return lbytes.Length - rbytes.Length; + } + + for (var i = 0; i < lbytes.Length; i++) + { + if (lbytes[i] != rbytes[i]) + { + return lbytes[i] - rbytes[i]; + } + } + + return 0; + } + } +} diff --git a/src/Squidex/Config/Orleans/Helper.cs b/src/Squidex/Config/Orleans/Helper.cs new file mode 100644 index 000000000..b296f2d8f --- /dev/null +++ b/src/Squidex/Config/Orleans/Helper.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Squidex.Infrastructure; + +namespace Squidex.Config.Orleans +{ + public static class Helper + { + internal static async Task ResolveIPAddressAsync(string addressOrHost, AddressFamily family) + { + var loopback = family == AddressFamily.InterNetwork ? IPAddress.Loopback : IPAddress.IPv6Loopback; + + if (addressOrHost.Equals("loopback", StringComparison.OrdinalIgnoreCase)) + { + return loopback; + } + + if (IPAddress.TryParse(addressOrHost, out var address)) + { + return address; + } + + var candidates = await Dns.GetHostAddressesAsync(addressOrHost); + + var chosen = candidates.OrderBy(x => x, IPAddressComparer.Instance).FirstOrDefault(); + + if (chosen == null) + { + throw new ConfigurationException($"Hostname {addressOrHost} with family {family} is not a valid IP address or DNS name"); + } + + return chosen; + } + } +} diff --git a/src/Squidex/Config/Orleans/OrleansServices.cs b/src/Squidex/Config/Orleans/OrleansServices.cs index d30ed84a0..458dc225a 100644 --- a/src/Squidex/Config/Orleans/OrleansServices.cs +++ b/src/Squidex/Config/Orleans/OrleansServices.cs @@ -6,11 +6,13 @@ // ========================================================================== using System.Net; +using System.Net.Sockets; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.Configuration; using Orleans.Hosting; +using Orleans.Providers.MongoDB.Configuration; using OrleansDashboard; using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; @@ -28,21 +30,6 @@ namespace Squidex.Config.Orleans { builder.ConfigureServices(siloServices => { - siloServices.Configure(options => - { - options.Configure(); - }); - - siloServices.Configure(options => - { - options.FastKillOnProcessExit = false; - }); - - siloServices.Configure(options => - { - options.HideTrace = true; - }); - siloServices.AddSingleton(); }); @@ -52,35 +39,54 @@ namespace Squidex.Config.Orleans parts.AddApplicationPart(SquidexInfrastructure.Assembly); }); + builder.Configure(options => + { + options.Configure(); + }); + + builder.Configure(options => + { + options.FastKillOnProcessExit = false; + }); + + builder.Configure(options => + { + options.HideTrace = true; + }); + builder.UseDashboard(options => { options.HostSelf = false; }); - var gatewayPort = config.GetOptionalValue("orleans:gatewayPort", 40000); + var orleansPortSilo = config.GetOptionalValue("orleans:siloPort", 11111); + var orleansPortGateway = config.GetOptionalValue("orleans:gatewayPort", 40000); - var siloPort = config.GetOptionalValue("orleans:siloPort", 11111); + var address = Helper.ResolveIPAddressAsync(Dns.GetHostName(), AddressFamily.InterNetwork).Result; + + builder.ConfigureEndpoints( + address, + orleansPortSilo, + orleansPortGateway, + true); config.ConfigureByOption("orleans:clustering", new Alternatives { ["MongoDB"] = () => { - builder.ConfigureEndpoints(Dns.GetHostName(), siloPort, gatewayPort, listenOnAnyHostAddress: true); - - var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); - var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); - builder.UseMongoDBClustering(options => { - options.ConnectionString = mongoConfiguration; - options.CollectionPrefix = "Orleans_"; - options.DatabaseName = mongoDatabaseName; + options.Configure(config); }); }, ["Development"] = () => { - builder.UseLocalhostClustering(siloPort, gatewayPort, null, Constants.OrleansClusterId, Constants.OrleansClusterId); - builder.Configure(options => options.ExpectedClusterSize = 1); + builder.UseDevelopmentClustering(new IPEndPoint(address, orleansPortSilo)); + + builder.Configure(options => + { + options.ExpectedClusterSize = 1; + }); } }); @@ -88,14 +94,9 @@ namespace Squidex.Config.Orleans { ["MongoDB"] = () => { - var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); - var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); - builder.UseMongoDBReminders(options => { - options.ConnectionString = mongoConfiguration; - options.CollectionPrefix = "Orleans_"; - options.DatabaseName = mongoDatabaseName; + options.Configure(config); }); } }); @@ -104,7 +105,17 @@ namespace Squidex.Config.Orleans return services; } - public static void Configure(this ClusterOptions options) + private static void Configure(this MongoDBOptions options, IConfiguration config) + { + var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); + var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); + + options.ConnectionString = mongoConfiguration; + options.CollectionPrefix = "Orleans_"; + options.DatabaseName = mongoDatabaseName; + } + + private static void Configure(this ClusterOptions options) { options.ClusterId = Constants.OrleansClusterId; options.ServiceId = Constants.OrleansClusterId; diff --git a/tests/Squidex.Infrastructure.Tests/IPAddressComparerTests.cs b/tests/Squidex.Infrastructure.Tests/IPAddressComparerTests.cs new file mode 100644 index 000000000..f4743ce29 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/IPAddressComparerTests.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Linq; +using System.Net; +using Xunit; + +namespace Squidex.Infrastructure +{ + public class IPAddressComparerTests + { + [Fact] + public void Should_sort_ip_addresses() + { + var source = new[] + { + IPAddress.IPv6Loopback, + IPAddress.Parse("127.0.0.200"), + IPAddress.Parse("127.0.0.255"), + IPAddress.Parse("129.0.0.1"), + IPAddress.Parse("127.0.0.1"), + IPAddress.Parse("127.0.0.200"), + }; + + var sorted = source.OrderBy(x => x, IPAddressComparer.Instance); + + var expected = new[] + { + IPAddress.Parse("127.0.0.1"), + IPAddress.Parse("127.0.0.200"), + IPAddress.Parse("127.0.0.200"), + IPAddress.Parse("127.0.0.255"), + IPAddress.Parse("129.0.0.1"), + IPAddress.IPv6Loopback, + }; + + Assert.Equal(expected, sorted); + } + } +}