Skip to content
Draft
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
2 changes: 1 addition & 1 deletion src/Cli.Tests/EndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ public void TestUpdateGraphQLPathRuntimeSettings(string path, bool isSuccess)
/// Ensures that invalid links provided for Cors.Origins result in failed engine startup
/// due to validation failure.
/// </summary>
[Ignore]

[DataTestMethod]
[DataRow("http://locahost1 https://localhost2", true, DisplayName = "Success in updating Host.Cors.Origins.")]
public void TestUpdateHostCorsOriginsRuntimeSettings(string path, bool isSuccess)
Expand Down
4 changes: 3 additions & 1 deletion src/Config/DabConfigEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ public static class DabConfigEvents
public const string POSTGRESQL_QUERY_EXECUTOR_ON_CONFIG_CHANGED = "POSTGRESQL_QUERY_EXECUTOR_ON_CONFIG_CHANGED";
public const string DOCUMENTOR_ON_CONFIG_CHANGED = "DOCUMENTOR_ON_CONFIG_CHANGED";
public const string AUTHZ_RESOLVER_ON_CONFIG_CHANGED = "AUTHZ_RESOLVER_ON_CONFIG_CHANGED";
public const string GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED = "GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED";
//public const string GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED = "GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED";
public const string GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED = "GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED";
public const string GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED = "GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED";
public const string LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE = "LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE";
// Fired after all other hot-reload events have been invoked.
public const string HOT_RELOAD_ALL_DONE = "HOT_RELOAD_ALL_DONE";
}
6 changes: 4 additions & 2 deletions src/Config/HotReloadEventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ public HotReloadEventHandler()
{ DOCUMENTOR_ON_CONFIG_CHANGED, null },
{ AUTHZ_RESOLVER_ON_CONFIG_CHANGED, null },
{ GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED, null },
{ GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED, null },
//{ GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED, null },
{ GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED, null },
{ LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE, null }
{ LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE, null },
// Completion signal for tests/consumers that need to await full hot-reload lifecycle.
{ HOT_RELOAD_ALL_DONE, null }
};
}

Expand Down
5 changes: 4 additions & 1 deletion src/Config/RuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,14 @@ protected void SignalConfigChanged(string message = "")
// Order of event firing matters: Eviction must be done before creating a new schema and then updating the schema.
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED, message));
//OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED, message));
}

// Log Level Initializer is outside of if statement as it can be updated on both development and production mode.
OnConfigChangedEvent(new HotReloadEventArgs(LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE, message));

// Finally, fire completion signal so tests can deterministically await full hot-reload.
OnConfigChangedEvent(new HotReloadEventArgs(HOT_RELOAD_ALL_DONE, message));
}

/// <summary>
Expand Down
25 changes: 22 additions & 3 deletions src/Core/Configurations/RuntimeConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,30 @@ public RuntimeConfigProvider(RuntimeConfigLoader runtimeConfigLoader)
/// </seealso>
private void RaiseChanged()
{
//First use of GetConfig during hot reload, in order to do validation of
//config file before any changes are made for hot reload.
//In case validation fails, an exception will be thrown and hot reload will be canceled.
// First, validate and apply the hot-reloaded config (sets LKG or restores previous).
ValidateConfig();

// Ensure dependent components (metadata providers, schema, etc.) are refreshed BEFORE signalling the change token.
// This makes consumers that await the provider's change token observe the updated state deterministically.
// Handlers are invoked synchronously here to avoid races.
try
{
if (_configLoader.RuntimeConfig is not null && RuntimeConfigLoadedHandlers.Count > 0)
{
InvokeConfigLoadedHandlersAsync().GetAwaiter().GetResult();
}
}
catch (Exception)
{
// If handler refresh fails, restore LKG and propagate a validation error similar to ValidateConfig failure path.
_configLoader.RestoreLkgConfig();
throw new DataApiBuilderException(
message: "Failed to refresh runtime components after hot reload.",
statusCode: HttpStatusCode.ServiceUnavailable,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}

// Finally, rotate and signal the change token so listeners can react.
DabChangeToken previousToken = Interlocked.Exchange(ref _changeToken, new DabChangeToken());
previousToken.SignalChange();
}
Expand Down
8 changes: 8 additions & 0 deletions src/Core/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"Azure.DataApiBuilder.Core": {
"commandName": "Project",
"commandLineArgs": "validate"
}
}
}
30 changes: 27 additions & 3 deletions src/Core/Services/MetadataProviders/MetadataProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,34 @@ private void ConfigureMetadataProviders()

public void OnConfigChanged(object? sender, HotReloadEventArgs args)
{
// Build new providers from current config
var newProviders = new Dictionary<string, ISqlMetadataProvider>();
foreach ((string dataSourceName, DataSource dataSource) in _runtimeConfigProvider.GetConfig().GetDataSourceNamesToDataSourcesIterator())
{
ISqlMetadataProvider provider = dataSource.DatabaseType switch
{
DatabaseType.CosmosDB_NoSQL => new CosmosSqlMetadataProvider(_runtimeConfigProvider, _fileSystem),
DatabaseType.MSSQL => new MsSqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
DatabaseType.DWSQL => new MsSqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
DatabaseType.PostgreSQL => new PostgreSqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
DatabaseType.MySQL => new MySqlMetadataProvider(_runtimeConfigProvider, _queryManagerFactory, _logger, dataSourceName, _isValidateOnly),
_ => throw new NotSupportedException(dataSource.DatabaseTypeNotSupportedMessage),
};
newProviders.Add(dataSourceName, provider);
}

// Initialize all new providers before making them visible
foreach ((_, ISqlMetadataProvider provider) in newProviders)
{
provider.InitializeAsync().GetAwaiter().GetResult();
}

// Atomically swap the provider set
_metadataProviders.Clear();
ConfigureMetadataProviders();
// Blocks the current thread until initialization is finished.
this.InitializeAsync().GetAwaiter().GetResult();
foreach ((string name, ISqlMetadataProvider provider) in newProviders)
{
_metadataProviders[name] = provider;
}
}

/// <inheritdoc />
Expand Down
3 changes: 1 addition & 2 deletions src/Service.Tests/Configuration/ConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3068,7 +3068,6 @@ private static async void ValidateMutationSucceededAtDbLayer(TestServer server,
/// </summary>
/// <param name="entityType">Type of the entity</param>
/// <param name="requestPath">Request path for performing POST API requests on the entity</param>
[Ignore]
[DataTestMethod]
[TestCategory(TestCategory.MSSQL)]
[DataRow(EntitySourceType.Table, "/api/Book", DisplayName = "Location Header validation - Table, Base Route not configured")]
Expand Down Expand Up @@ -3920,7 +3919,7 @@ public void LogLevelSerialization(LogLevel expectedLevel)
/// <summary>
/// Tests different log level filters that are valid and check that they are deserialized correctly
/// </summary>
[Ignore]

[DataTestMethod]
[TestCategory(TestCategory.MSSQL)]
[DataRow(LogLevel.Trace, typeof(RuntimeConfigValidator))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class AuthorizationResolverHotReloadTests
/// and uses the same test server instance.
/// This test is currently flakey, failing intermittently in our pipeline, and is therefore ignored.
/// </summary>
[Ignore]

[TestMethod]
[DoNotParallelize]
[TestCategory(TestCategory.MSSQL)]
Expand Down
Loading