Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,71 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using EdFi.Ods.AdminApi.Common.Infrastructure.Context;
using EdFi.Ods.AdminApi.Common.Infrastructure.MultiTenancy;
using EdFi.Ods.AdminApi.Common.Settings;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace EdFi.Ods.AdminApi.Common.Infrastructure.Helpers;

public class ConfigConnectionStringsProvider : IConfigConnectionStringsProvider
{
public ConfigConnectionStringsProvider(IConfiguration config)
private readonly IConfiguration _config;
private readonly IContextProvider<TenantConfiguration> _tenantConfigurationContextProvider;
private readonly IOptions<AppSettings> _options;

public ConfigConnectionStringsProvider(
IConfiguration config,
IContextProvider<TenantConfiguration> tenantConfigurationContextProvider,
IOptions<AppSettings> options)
{
ConnectionStringProviderByName = config.GetSection("ConnectionStrings")
.GetChildren()
.ToList()
.ToDictionary(k => k.Key, v => v.Value ?? string.Empty);
_config = config;
_tenantConfigurationContextProvider = tenantConfigurationContextProvider;
_options = options;
}

public int Count
{
get => ConnectionStringProviderByName.Keys.Count;
}

public IDictionary<string, string> ConnectionStringProviderByName { get; }
public IDictionary<string, string> ConnectionStringProviderByName => BuildConnectionStringsByName();

public string GetConnectionString(string name) => ConnectionStringProviderByName[name];

private IDictionary<string, string> BuildConnectionStringsByName()
{
// Start from the top-level ConnectionStrings section as the base (works for both single- and multi-tenant).
var connectionStringsByName = _config.GetSection("ConnectionStrings")
.GetChildren()
.ToDictionary(k => k.Key, v => v.Value ?? string.Empty, StringComparer.OrdinalIgnoreCase);

if (_options.Value.MultiTenancy)
{
// In multi-tenant mode, each tenant has its own set of databases (EdFi_Admin, EdFi_Security,
// EdFi_Ods, EdFi_Master). The active tenant is resolved from the ambient context, which is set
// by TenantResolverMiddleware for HTTP requests and by CreateInstanceJob for Quartz jobs.
// Per-tenant values override the top-level defaults when present.
var tenantConfiguration = _tenantConfigurationContextProvider.Get();

OverrideWhenPresent(connectionStringsByName, "EdFi_Admin", tenantConfiguration?.AdminConnectionString);
OverrideWhenPresent(connectionStringsByName, "EdFi_Security", tenantConfiguration?.SecurityConnectionString);
OverrideWhenPresent(connectionStringsByName, "EdFi_Ods", tenantConfiguration?.OdsConnectionString);
OverrideWhenPresent(connectionStringsByName, "EdFi_Master", tenantConfiguration?.MasterConnectionString);
}

return connectionStringsByName;
}

private static void OverrideWhenPresent(
IDictionary<string, string> connectionStringsByName,
string name,
string? connectionString)
{
if (!string.IsNullOrWhiteSpace(connectionString))
{
connectionStringsByName[name] = connectionString;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ public class JobConstants
{
public const string JobTypeKey = "JobType";
public const string TenantNameKey = "TenantName";
public const string DbInstanceIdKey = "DbInstanceId";
public const string OdsInstanceIdKey = "OdsInstanceId";
public const string CreateInstanceJobName = "CreateInstanceJob";
public const string CreatePendingDbInstancesDispatcherJobName = "CreatePendingDbInstancesDispatcherJob";
public const string RefreshEducationOrganizationsJobName = "RefreshEducationOrganizationsJob";
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ public class TenantConfiguration
public string? AdminConnectionString { get; set; }

public string? SecurityConnectionString { get; set; }

public string? OdsConnectionString { get; set; }

public string? MasterConnectionString { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ private static Dictionary<string, TenantConfiguration> InitializeTenantsConfigur
TenantIdentifier = t.Key,
AdminConnectionString = t.Value.ConnectionStrings.GetValueOrDefault("EdFi_Admin"),
SecurityConnectionString = t.Value.ConnectionStrings.GetValueOrDefault("EdFi_Security"),
OdsConnectionString = t.Value.ConnectionStrings.GetValueOrDefault("EdFi_Ods"),
MasterConnectionString = t.Value.ConnectionStrings.GetValueOrDefault("EdFi_Master"),
},
StringComparer.OrdinalIgnoreCase);
}
Expand Down
2 changes: 2 additions & 0 deletions Application/EdFi.Ods.AdminApi.Common/Settings/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class AppSettings
public bool PreventDuplicateApplications { get; set; }
public bool EnableApplicationResetEndpoint { get; set; }
public int EdOrgsRefreshIntervalInMins { get; set; }
public int CreateDbInstancesSweepIntervalInMins { get; set; } = 5;
public int CreateDbInstancesMaxRetryAttempts { get; set; } = 3;
public int MaxDegreeOfParallelism { get; set; } = 10;
public string? AdminApiMode { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ protected SandboxProvisionerBase(IConfiguration configuration,
CommandTimeout = int.TryParse(_configuration.GetSection("SandboxAdminSQLCommandTimeout").Value, out int timeout)
? timeout
: 30;

ConnectionString = _connectionStringsProvider.GetConnectionString("EdFi_Master");
}

protected int CommandTimeout { get; }

protected string ConnectionString { get; }
// Evaluated at call time (not cached in a field) so that multi-tenant jobs, which set the tenant
// context immediately before provisioning, always receive the connection string for the active tenant.
// Using a field would capture the startup value (typically localhost), which is wrong in Docker.
protected string ConnectionString => _connectionStringsProvider.GetConnectionString("EdFi_Master");

public void AddSandbox(string sandboxKey, SandboxType sandboxType)
=> AddSandboxAsync(sandboxKey, sandboxType).WaitSafely();
Expand Down
Loading
Loading