dotnet / core

.NET news, announcements, release notes, and more!
https://dot.net
MIT License
20.97k stars 4.91k forks source link

Inconsitent Behavior of service configuration on Windows and on Linux #7292

Open master0luc opened 2 years ago

master0luc commented 2 years ago

Description

During Orleans Silo configuration in UseOrleans, section of "Orleans" settings is not visible on Linux, but visible on Windows:

[...
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseOrleans((context, siloBuilder) => {
                 // HERE CONFIG IS BEEN USING
            BuildSiloHost(context, siloBuilder);
        })
        .ConfigureWebHostDefaults(webBuilder => {
            webBuilder
                                //HERE  CONFIG
                .ConfigureAppConfiguration((hostContext, config) => {
                    var file = GetAppsettingsFile();
                    config.AddJsonFile(file, optional: true, reloadOnChange: true);
                    config.AddEnvironmentVariables();
                })
        });
...]

Order of UseOrleans and ConfigureWebHostDefaults changes nothing Solution which I used is read additionaly appsettings and explicitly provide inside to BuildSiloHost config method, like this:

[...
        private static ISiloBuilder BuildSiloHost(
            Microsoft.Extensions.Hosting.HostBuilderContext context,
            ISiloBuilder siloHostBuilder) {
            //HACK: EXPLICIT PASS ORLEANS CONFIG
            //DEBUG
            var orleansConfiguration = _orleansSection.Get<OrleansConfiguration>();
            Console.WriteLine($"BuildSiloHost: ClusterId: {orleansConfiguration.ClusterId}");
            Console.WriteLine($"BuildSiloHost: ServiceId: {orleansConfiguration.ServiceId}");   
            siloHostBuilder
                .ConfigureServices(services => {
                    services.Configure<OrleansConfiguration>(_orleansSection);
...]

Configuration

Net 5.0.15 on Windows mcr.microsoft.com/dotnet/aspnet:5.0 on Linux

rzikm commented 2 years ago

Looks like problem in Orleans or Hosting/Configuration extensions packages. Maybe @Pilchie, can you please take over?

mairaw commented 2 years ago

@bradygaster @ReubenBond can you help with this?

ReubenBond commented 2 years ago

Would you mind elaborating, @master0luc? I don't quite understand.

I don't see any code which is attempting to access an "Orleans" section in configuration, or subsequently use that section.

master0luc commented 2 years ago

Hello @ReubenBond, Below there is a working example, where I put commentary

namespace Platform.Rest.Api {
    public class Program {
        public static void Main(string[] args) {
            var builder = CreateHostBuilder(args).Build();
            using (var scope = builder.Services.CreateScope()) {
                var service1 = scope.ServiceProvider.GetRequiredService<OrleansClusteringMigrationTask>();
                service1.ExecuteAsync().Wait();
            }
            builder.Run();
            LogManager.Flush();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseNLog()
                .ConfigureWebHostDefaults(webBuilder => {
                    webBuilder
                        .UseContentRoot(Directory.GetCurrentDirectory())
                        .ConfigureKestrel(options => {
                            options.Limits.MaxRequestBodySize = 524288000;
                        })
//HERE WE Get our appsettings.json
                        .ConfigureAppConfiguration((hostContext, config) => {
                            var file = GetAppsettingsFile();
                            config.AddJsonFile(file, optional: true, reloadOnChange: true);
                            config.AddEnvironmentVariables();
                        })
                        .UseStartup<Startup>()
                        .UseSentry();
                })
                .UseOrleans((context, siloBuilder) => {
                    BuildSiloHost(context, siloBuilder);
                });

        private static ISiloBuilder BuildSiloHost(
            Microsoft.Extensions.Hosting.HostBuilderContext context,
            ISiloBuilder siloHostBuilder) {
//HERE, On Wondows I have  got an  object from appsettings.json
//BUT,  On Linux  I have got noting
            var orleansSection = context.Configuration.GetSection("Orleans");
            var orleansConfiguration = orleansSection.Get<OrleansConfiguration>();
            Console.WriteLine($"ClusterId: {orleansConfiguration.ClusterId}");
            Console.WriteLine($"ServiceId: {orleansConfiguration.ServiceId}");

            siloHostBuilder
                .ConfigureServices(services => {
                    services.Configure<OrleansConfiguration>(orleansSection);
                })
                .Configure<SchedulingOptions>(options => options.TurnWarningLengthThreshold = TimeSpan.FromSeconds(10))
                .Configure<SiloMessagingOptions>(options => {
                    options.MaxMessageBodySize = int.MaxValue;
                    options.ClientDropTimeout = orleansConfiguration.ResponseTimeout;
                    options.ResponseTimeout = orleansConfiguration.ResponseTimeout;
                    options.ResponseTimeoutWithDebugger = orleansConfiguration.ResponseTimeoutWithDebugger;
                })
                .Configure<ClusterOptions>(options => {
                    options.ClusterId = orleansConfiguration.ClusterId;
                    options.ServiceId = orleansConfiguration.ServiceId;
                })
                .ConfigureApplicationParts(parts => parts.AddFromApplicationBaseDirectory());

            if (orleansConfiguration.UseLocalhostClustering) {
                siloHostBuilder.UseLocalhostClustering(
                    orleansConfiguration.EndpointSiloPort,
                    orleansConfiguration.EndpointGatewayPort
                );
            } else {
                siloHostBuilder
                    .ConfigureServices(services=> {
                        services.AddScoped<OrleansClusteringMigrationTask>();
                    })
                    .ConfigureEndpoints(orleansConfiguration.EndpointSiloPort, orleansConfiguration.EndpointGatewayPort)
                    .UseAdoNetClustering(options => {
                        options.Invariant = orleansConfiguration.ClusterAdoInvariant;
                        options.ConnectionString = orleansConfiguration.ClusterConnection.ToString();
                    });
            }
            return siloHostBuilder;
        }

        static string GetAppsettingsFile() {
            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

            Console.WriteLine(environment);
            if (environment.Equals("Development")) {
                return $"appsettings.{environment}.json";
            } else {
                return $"appsettings.json";
            }
        }

        public class OrleansConfiguration {
            public string StorageName => "MDBRuntime";
            public string StorageAdoInvariant => "Npgsql";
            public string ClusterAdoInvariant => "Npgsql";

            public PostgresConnection StorageConnection { get; set; }
            public PostgresConnection ClusterConnection { get; set; }

            public string ClusterId { get; set; }
            public string ServiceId { get; set; }
            public int EndpointSiloPort { get; set; } = 11111;
            public int EndpointGatewayPort { get; set; } = 30000;
            public TimeSpan ResponseTimeout { get; set; } = TimeSpan.FromSeconds(30);
            public TimeSpan ResponseTimeoutWithDebugger { get; set; } = TimeSpan.FromMinutes(30);
            public bool UseLocalhostClustering { get; set; }
        }
        public class PostgresConnection {
            public string Host { get; set; }
            public int Port { get; set; } = 5432;
            public string Database { get; set; }
            public string Username { get; set; }
            public string Password { get; set; }
            public string CustomSettings { get; set; } = "";

            public override string ToString() {
                return $"Host={Host};Port={Port};Database={Database};Username={Username};Password={Password};Persist Security Info=True;{CustomSettings}";
            }
        }
    }
}
ReubenBond commented 2 years ago

I wonder if this is because Linux is case-sensitive - is the file named appsettings.development.json or appsettings.Development.json or something else?

I doubt this is an issue with Orleans, since it doesn't touch config in any way - the host builder context is plumbed straight through.

master0luc commented 2 years ago

I dubt it, because on Linux only appsettings.json is been using. And I solved this problem like this:

public class Program {
//HERE ADD A FIELD
private static IConfigurationSection _orleansSection;
public static void Main(string[] args) {
    //HERE EXPLICITLY INITIALIZE
    var initConfig = LoadConfiguration();
    _orleansSection = initConfig.GetSection("Orleans");
    var tmpOrleansConfiguration = _orleansSection.Get<OrleansConfiguration>();
    Console.WriteLine($"Program: ClusterId: {tmpOrleansConfiguration.ClusterId}");
    Console.WriteLine($"Program: ServiceId: {tmpOrleansConfiguration.ServiceId}");

    var builder = CreateHostBuilder(args).Build();
    using (var scope = builder.Services.CreateScope()) {
        var service1 = scope.ServiceProvider.GetRequiredService<OrleansClusteringMigrationTask>();
        service1.ExecuteAsync().Wait();
    }
    builder.Run();
    LogManager.Flush();
}

public static IConfigurationRoot LoadConfiguration() {
    var appFolder = AppDomain.CurrentDomain.BaseDirectory;
    var builder = new ConfigurationBuilder()
            .SetBasePath(appFolder)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();
    return builder.Build();
}
private static ISiloBuilder BuildSiloHost(  Microsoft.Extensions.Hosting.HostBuilderContext context,ISiloBuilder siloHostBuilder) {
    /HERE THE GLOBAL FIELD IS USED
    var orleansConfiguration = _orleansSection.Get<OrleansConfiguration>();
...

But I have got an idea, may be this is because of the difference in the base folder of looking, perhaps: In second variant I added explicit BasePath .SetBasePath(appFolder) but in the previous case I Lost it... But again, this is an inconsistent behavior on Win and Linux... I suppose that on Windows I should have got the same problem...

ReubenBond commented 2 years ago

Why is the code to add appsettings.json inside the ConfigureWebHostDefaults and not directly on the HostBuilder?

Additionally, you are calling Host.CreateDefaultBuilder(), which automatically adds appsettings.json: https://github.com/dotnet/runtime/blob/c9252d5ade0cd48fbd3a92565e43b412400da5e1/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L188

master0luc commented 2 years ago

so, You are perfectly right, according docs appsettings should be loaded, but actually they are not: after removing this block and my hack

.ConfigureAppConfiguration((hostContext, config) => {
    var file = GetAppsettingsFile();
    config.AddJsonFile(file, optional: true, reloadOnChange: true);
    config.AddEnvironmentVariables();
})

we have this

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseNLog()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseOrleans((context, siloBuilder) => {
            BuildSiloHost(context, siloBuilder);

})
.ConfigureWebHostDefaults(webBuilder => {
    webBuilder
        .UseContentRoot(Directory.GetCurrentDirectory())
        .ConfigureKestrel(options => {
            options.Limits.MaxRequestBodySize = 524288000;
        })
        .UseStartup<Startup>()
        .UseSentry();
});
private static ISiloBuilder BuildSiloHost(  Microsoft.Extensions.Hosting.HostBuilderContext context, ISiloBuilder siloHostBuilder) {
    **var _orleansSection = context.Configuration.GetSection("Orleans");**
    var orleansConfiguration = _orleansSection.Get<OrleansConfiguration>();

and after that _orleansSection is null on Linux but not null on Windows

master0luc commented 2 years ago

But, If config has got an explicit path from AppDomain.CurrentDomain.BaseDirectory:

...
var appFolder = AppDomain.CurrentDomain.BaseDirectory 
...
...
.ConfigureWebHostDefaults(webBuilder => {
    webBuilder
        .UseContentRoot(Directory.GetCurrentDirectory())
        .ConfigureKestrel(options => {
            options.Limits.MaxRequestBodySize = 524288000;
        })
//ADD EXPLICIT PATH IN CONFIG => 
                .ConfigureAppConfiguration((hostContext, config) => {
                    config.SetBasePath(_appFolder);
                })
        .UseStartup<Startup>()
        .UseSentry();
...

this is strange

master0luc commented 2 years ago

Problem ,perhaps, is laying there: Directory.GetCurrentDirectory() != AppDomain.CurrentDomain.BaseDirectory

master0luc commented 2 years ago

Problem is there: .UseContentRoot(Directory.GetCurrentDirectory()) but Directory.GetCurrentDirectory() != AppDomain.CurrentDomain.BaseDirectory so .UseContentRoot( AppDomain.CurrentDomain.BaseDirectory) works perfectly and the root of this proble is: dotnet {parentFolder}/{folderProject}/project.dll so Directory.GetCurrentDirectory() => shows {parentFolder}/ but AppDomain.CurrentDomain.BaseDirectory => {parentFolder}/{folderProject}/

ReubenBond commented 2 years ago

I'll leave you to it, since the cause seems to be clear now and it's unrelated to Orleans.

master0luc commented 2 years ago

Thanks