Skip to content

Commit 0473cd6

Browse files
committed
feat: bg implementation
1 parent 4d4846b commit 0473cd6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+5690
-553
lines changed

AwsWrapperDataProvider.Plugin.SecretsManager/SecretsManager/SecretsManagerAuthPlugin.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
23
//
34
// Licensed under the Apache License, Version 2.0 (the "License").

AwsWrapperDataProvider.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsWrapperDataProviderExamp
4141
EndProject
4242
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsWrapperDataProvider.Performance.Tests", "AwsWrapperDataProvider.Performance.Tests\AwsWrapperDataProvider.Performance.Tests.csproj", "{1A00C448-9549-4995-9180-C70C908963A5}"
4343
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsWrapperDataProvider.Plugin.BlueGreenConnection.Tests", "AwsWrapperDataProvider.Plugin.BlueGreenConnection.Tests\AwsWrapperDataProvider.Plugin.BlueGreenConnection.Tests.csproj", "{304AEAED-F4A9-4B95-9F4D-8183CB47E4EB}"
45+
EndProject
4446
Global
4547
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4648
Debug|Any CPU = Debug|Any CPU
@@ -99,6 +101,10 @@ Global
99101
{D87E0507-5E62-4852-BDFC-CB9E167A85FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
100102
{D87E0507-5E62-4852-BDFC-CB9E167A85FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
101103
{D87E0507-5E62-4852-BDFC-CB9E167A85FB}.Release|Any CPU.Build.0 = Release|Any CPU
104+
{304AEAED-F4A9-4B95-9F4D-8183CB47E4EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
105+
{304AEAED-F4A9-4B95-9F4D-8183CB47E4EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
106+
{304AEAED-F4A9-4B95-9F4D-8183CB47E4EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
107+
{304AEAED-F4A9-4B95-9F4D-8183CB47E4EB}.Release|Any CPU.Build.0 = Release|Any CPU
102108
{C3E1A7B2-8D4F-4E90-B2A1-5F6E7D8C9B0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
103109
{C3E1A7B2-8D4F-4E90-B2A1-5F6E7D8C9B0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
104110
{C3E1A7B2-8D4F-4E90-B2A1-5F6E7D8C9B0A}.Release|Any CPU.ActiveCfg = Release|Any CPU

AwsWrapperDataProvider/AwsWrapperConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class AwsWrapperConnection : DbConnection, IWrapper
3939

4040
private bool deferredInitialization = false;
4141

42-
internal ConnectionPluginManager? PluginManager { get; private set; }
42+
public ConnectionPluginManager? PluginManager { get; private set; }
4343

4444
internal Dictionary<string, string>? ConnectionProperties { get; private set; }
4545

AwsWrapperDataProvider/Driver/ConnectionPluginManager.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@
2020
using AwsWrapperDataProvider.Driver.HostInfo;
2121
using AwsWrapperDataProvider.Driver.HostListProviders;
2222
using AwsWrapperDataProvider.Driver.Plugins;
23+
using AwsWrapperDataProvider.Driver.Utils;
2324
using AwsWrapperDataProvider.Properties;
25+
using Microsoft.Extensions.Logging;
2426

2527
namespace AwsWrapperDataProvider.Driver;
2628

2729
public class ConnectionPluginManager
2830
{
2931
private readonly Dictionary<string, Delegate> pluginChainDelegates = [];
30-
protected IList<IConnectionPlugin> plugins = [];
32+
public IList<IConnectionPlugin> Plugins = [];
33+
public string[] ActivePluginCodes = [];
3134
protected IConnectionProvider defaultConnProvider;
3235
protected IConnectionProvider? effectiveConnProvider;
3336
protected ConfigurationProfile? configurationProfile;
@@ -38,6 +41,8 @@ public class ConnectionPluginManager
3841
private const string ConnectMethod = "DbConnection.Open";
3942
private const string ForceConnectMethod = "DbConnection.ForceOpen";
4043
private const string InitHostMethod = "initHostProvider";
44+
45+
private static readonly ILogger<ConnectionPluginManager> Logger = LoggerUtils.GetLogger<ConnectionPluginManager>();
4146

4247
private delegate Task<T> PluginPipelineDelegate<T>(IConnectionPlugin plugin, ADONetDelegate<T> methodFunc);
4348

@@ -74,7 +79,7 @@ public ConnectionPluginManager(
7479
{
7580
this.defaultConnProvider = defaultConnectionProvider;
7681
this.effectiveConnProvider = effectiveConnectionProvider;
77-
this.plugins = plugins;
82+
this.Plugins = plugins;
7883
this.ConnectionWrapper = connection;
7984
}
8085

@@ -84,12 +89,13 @@ public void InitConnectionPluginChain(
8489
{
8590
this.pluginService = pluginService;
8691
ConnectionPluginChainBuilder pluginChainBuilder = new();
87-
this.plugins = pluginChainBuilder.GetPlugins(
92+
this.Plugins = pluginChainBuilder.GetPlugins(
8893
this.pluginService,
8994
this.defaultConnProvider,
9095
this.effectiveConnProvider,
9196
props,
9297
this.configurationProfile);
98+
this.ActivePluginCodes = pluginChainBuilder.GetPluginCodes(this.pluginService, props);
9399
}
94100

95101
private async Task<T> ExecuteWithSubscribedPlugins<T>(
@@ -104,7 +110,8 @@ private async Task<T> ExecuteWithSubscribedPlugins<T>(
104110
if (!this.pluginChainDelegates.TryGetValue(methodName, out Delegate? del))
105111
{
106112
del = this.MakePluginChainDelegate<T>(methodName);
107-
this.pluginChainDelegates.Add(methodName, del);
113+
Logger.LogDebug("Created plugin chain delegate for method {Delegate}", del);
114+
this.pluginChainDelegates[methodName] = del;
108115
}
109116

110117
if (del is not PluginChainADONetDelegate<T> pluginChainDelegate)
@@ -129,16 +136,24 @@ private async Task<T> ExecuteWithSubscribedPlugins<T>(
129136

130137
private PluginChainADONetDelegate<T> MakePluginChainDelegate<T>(string methodName)
131138
{
132-
PluginChainADONetDelegate<T>? pluginChainDelegate = null;
139+
var pluginsInChain = new List<string>();
133140

134-
for (int i = this.plugins.Count - 1; i >= 0; i--)
141+
PluginChainADONetDelegate<T>? pluginChainDelegate = null;
142+
Logger.LogDebug("MakePluginChainDelegate {Plugins}", this.Plugins);
143+
for (int i = this.Plugins.Count - 1; i >= 0; i--)
135144
{
136-
IConnectionPlugin plugin = this.plugins[i];
145+
IConnectionPlugin plugin = this.Plugins[i];
137146
IReadOnlySet<string> subscribedMethods = plugin.SubscribedMethods;
138147
bool isSubscribed = subscribedMethods.Contains(AllMethods) || subscribedMethods.Contains(methodName);
139148

140149
if (isSubscribed)
141150
{
151+
string pluginName = plugin.GetType().Name;
152+
pluginsInChain.Add(pluginName);
153+
154+
Console.WriteLine($" [{pluginsInChain.Count}] {pluginName}");
155+
Logger.LogDebug(" Added plugin to chain: {PluginName}", pluginName);
156+
142157
if (pluginChainDelegate == null)
143158
{
144159
// DefaultConnectionPlugin always terminates the list of plugins
@@ -166,6 +181,7 @@ public virtual Task<T> Execute<T>(
166181
ADONetDelegate<T> methodFunc,
167182
params object[] methodArgs)
168183
{
184+
Logger.LogTrace("ConnectionPluginManager Execute");
169185
return this.ExecuteWithSubscribedPlugins(
170186
methodName,
171187
(plugin, methodFunc) => plugin.Execute(methodInvokeOn, methodName, methodFunc, methodArgs),
@@ -248,4 +264,9 @@ public virtual bool AcceptsStrategy(string strategy)
248264
{
249265
return this.defaultConnProvider.AcceptsStrategy(strategy);
250266
}
267+
268+
public bool IsPluginActive(string pluginCode)
269+
{
270+
return this.ActivePluginCodes.Contains(pluginCode);
271+
}
251272
}

AwsWrapperDataProvider/Driver/Dialects/AuroraMySqlDialect.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
namespace AwsWrapperDataProvider.Driver.Dialects;
2323

24-
public class AuroraMySqlDialect : MySqlDialect
24+
public class AuroraMySqlDialect : MySqlDialect, IBlueGreenDialect
2525
{
2626
private static readonly ILogger<AuroraMySqlDialect> Logger = LoggerUtils.GetLogger<AuroraMySqlDialect>();
2727

@@ -34,11 +34,16 @@ public class AuroraMySqlDialect : MySqlDialect
3434

3535
private static readonly string NodeIdQuery = "SELECT @@aurora_server_id, @@aurora_server_id";
3636

37-
internal static readonly string IsDialectQuery = "SHOW VARIABLES LIKE 'aurora_version'";
37+
internal static readonly string
38+
IsDialectQuery = "SHOW VARIABLES LIKE 'aurora_version'";
3839

3940
private static readonly string IsWriterQuery = "SELECT SERVER_ID FROM information_schema.replica_host_status "
4041
+ "WHERE SESSION_ID = 'MASTER_SESSION_ID' AND SERVER_ID = @@aurora_server_id";
4142

43+
private static readonly string AuroraMySqlBgTopologyExistsQuery = "SELECT 1 AS tmp FROM information_schema.tables WHERE table_schema = 'mysql' AND table_name = 'rds_topology'";
44+
45+
protected static readonly string AuroraMySqlBgStatusQuery = "SELECT * FROM mysql.rds_topology";
46+
4247
public override IList<Type> DialectUpdateCandidates { get; } = [
4348
typeof(RdsMultiAzDbClusterMySqlDialect),
4449
];
@@ -54,7 +59,7 @@ public override async Task<bool> IsDialect(DbConnection connection)
5459
}
5560
catch (Exception ex) when (this.ExceptionHandler.IsSyntaxError(ex))
5661
{
57-
// Syntax error - expected when querying against incorrect dialect
62+
Logger.LogTrace(ex, Resources.Error_CantCheckDialect_Syntax, nameof(RdsPgDialect));
5863
}
5964
catch (Exception ex)
6065
{
@@ -85,4 +90,14 @@ private HostListProviderSupplier GetHostListProviderSupplier()
8590
NodeIdQuery,
8691
IsReaderQuery);
8792
}
93+
94+
public async Task<bool> IsBlueGreenStatusAvailable(DbConnection connection)
95+
{
96+
return await DialectUtils.CheckExistenceQueries(connection, this.ExceptionHandler, Logger, AuroraMySqlBgTopologyExistsQuery);
97+
}
98+
99+
public string GetBlueGreenStatusQuery()
100+
{
101+
return AuroraMySqlBgStatusQuery;
102+
}
88103
}

AwsWrapperDataProvider/Driver/Dialects/AuroraPgDialect.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public class AuroraPgDialect : PgDialect, IAuroraLimitlessDialect
4848
private static readonly string IsWriterQuery = "SELECT SERVER_ID FROM pg_catalog.aurora_replica_status() "
4949
+ "WHERE SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' AND SERVER_ID OPERATOR(pg_catalog.=) aurora_db_instance_identifier()";
5050

51+
protected static readonly string AuroraPostgreSqlBgTopologyExistsQuery = "SELECT 'pg_catalog.get_blue_green_fast_switchover_metadata'::regproc";
52+
53+
protected static readonly string AuroraPostgreSqlBgStatusQuery = "SELECT * FROM pg_catalog.get_blue_green_fast_switchover_metadata('aws_wrapper_dotnet_data_provider_wrapper')";
54+
5155
public override IList<Type> DialectUpdateCandidates { get; } = [
5256
typeof(RdsMultiAzDbClusterPgDialect),
5357
];
@@ -83,7 +87,7 @@ public override async Task<bool> IsDialect(DbConnection connection)
8387
}
8488
catch (Exception ex) when (this.ExceptionHandler.IsSyntaxError(ex))
8589
{
86-
// Syntax error - expected when querying against incorrect dialect
90+
Logger.LogTrace(ex, Resources.Error_CantCheckDialect_Syntax, nameof(RdsPgDialect));
8791
}
8892
catch (Exception ex)
8993
{
@@ -115,5 +119,15 @@ private HostListProviderSupplier GetHostListProviderSupplier()
115119
IsReaderQuery);
116120
}
117121

122+
public async Task<bool> IsBlueGreenStatusAvailable(DbConnection connection)
123+
{
124+
return await DialectUtils.CheckExistenceQueries(connection, this.ExceptionHandler, Logger, AuroraPostgreSqlBgTopologyExistsQuery);
125+
}
126+
127+
public string GetBlueGreenStatusQuery()
128+
{
129+
return AuroraPostgreSqlBgStatusQuery;
130+
}
131+
118132
public string LimitlessRouterEndpointQuery { get => "SELECT router_endpoint, load from aurora_limitless_router_endpoints()"; }
119133
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License").
4+
// You may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Data.Common;
16+
using AwsWrapperDataProvider.Driver.Exceptions;
17+
using AwsWrapperDataProvider.Properties;
18+
using Microsoft.Extensions.Logging;
19+
20+
namespace AwsWrapperDataProvider.Driver.Dialects;
21+
22+
public static class DialectUtils
23+
{
24+
public static async Task<bool> CheckExistenceQueries(DbConnection connection, IExceptionHandler exceptionHandler, ILogger logger, string query)
25+
{
26+
try
27+
{
28+
await using var command = connection.CreateCommand();
29+
command.CommandText = query;
30+
await using var reader = await command.ExecuteReaderAsync();
31+
return await reader.ReadAsync();
32+
}
33+
catch (Exception ex) when (exceptionHandler.IsSyntaxError(ex))
34+
{
35+
// Syntax error - expected when querying against incorrect dialect
36+
}
37+
catch (Exception ex)
38+
{
39+
logger.LogTrace(ex, Resources.Error_CantCheckDialect, nameof(AuroraMySqlDialect));
40+
}
41+
42+
return false;
43+
}
44+
45+
public static bool IsBlueGreenConnectionDialect(IDialect dialect)
46+
{
47+
return dialect is IBlueGreenDialect;
48+
}
49+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License").
4+
// You may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Data.Common;
16+
17+
namespace AwsWrapperDataProvider.Driver.Dialects;
18+
19+
public interface IBlueGreenDialect
20+
{
21+
Task<bool> IsBlueGreenStatusAvailable(DbConnection connection);
22+
23+
string GetBlueGreenStatusQuery();
24+
}

AwsWrapperDataProvider/Driver/Dialects/RdsMultiAzDbClusterPgDialect.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class RdsMultiAzDbClusterPgDialect : PgDialect
3232
private static readonly ILogger<RdsMultiAzDbClusterPgDialect> Logger = LoggerUtils.GetLogger<RdsMultiAzDbClusterPgDialect>();
3333

3434
private static readonly string TopologyQuery =
35-
$"SELECT id, endpoint, port FROM rds_tools.show_topology('aws_dotnet_driver-{DriverVersion}')";
35+
$"SELECT id, endpoint, port FROM rds_tools.show_topology('aws_wrapper_dotnet_data_provider_wrapper-{DriverVersion}')";
3636

3737
private static readonly string FetchWriterNodeQuery =
3838
"SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()"

AwsWrapperDataProvider/Driver/Dialects/RdsMySqlDialect.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@
1919

2020
namespace AwsWrapperDataProvider.Driver.Dialects;
2121

22-
public class RdsMySqlDialect : MySqlDialect
22+
public class RdsMySqlDialect : MySqlDialect, IBlueGreenDialect
2323
{
24+
protected static readonly string RdsMySqlTopologyTableExistsQuery = "SELECT 1 AS tmp FROM information_schema.tables WHERE" +
25+
" table_schema = 'mysql' AND table_name = 'rds_topology'";
26+
27+
protected static readonly string RdsMySqlBgStatusQuery = "SELECT * FROM mysql.rds_topology";
28+
2429
private static readonly ILogger<RdsMySqlDialect> Logger = LoggerUtils.GetLogger<RdsMySqlDialect>();
2530

2631
public override IList<Type> DialectUpdateCandidates { get; } =
@@ -56,7 +61,7 @@ public override async Task<bool> IsDialect(DbConnection conn)
5661
}
5762
catch (Exception ex) when (this.ExceptionHandler.IsSyntaxError(ex))
5863
{
59-
// Syntax error - expected when querying against incorrect dialect
64+
Logger.LogTrace(ex, Resources.Error_CantCheckDialect_Syntax, nameof(RdsPgDialect));
6065
}
6166
catch (Exception ex)
6267
{
@@ -65,4 +70,14 @@ public override async Task<bool> IsDialect(DbConnection conn)
6570

6671
return false;
6772
}
73+
74+
public async Task<bool> IsBlueGreenStatusAvailable(DbConnection connection)
75+
{
76+
return await DialectUtils.CheckExistenceQueries(connection, this.ExceptionHandler, Logger, RdsMySqlTopologyTableExistsQuery);
77+
}
78+
79+
public string GetBlueGreenStatusQuery()
80+
{
81+
return RdsMySqlBgStatusQuery;
82+
}
6883
}

0 commit comments

Comments
 (0)