Skip to content

Commit f7008b5

Browse files
authored
feat: blue/green support (#244)
1 parent 4227114 commit f7008b5

52 files changed

Lines changed: 5953 additions & 570 deletions

Some content is hidden

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

AwsWrapperDataProvider/AwsWrapperConnectionStringBuilder.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,42 @@ public int? MonitorDisposalTime
439439
set => this.SetValue(PropertyDefinition.MonitorDisposalTimeMs.Name, value?.ToString());
440440
}
441441

442+
public string? BgdId
443+
{
444+
get => this.GetValue(PropertyDefinition.BgdId.Name);
445+
set => this.SetValue(PropertyDefinition.BgdId.Name, value);
446+
}
447+
448+
public int? BgBaselineMs
449+
{
450+
get => this.GetIntValue(PropertyDefinition.BgIntervalBaselineMs.Name);
451+
set => this.SetValue(PropertyDefinition.BgIntervalBaselineMs.Name, value?.ToString());
452+
}
453+
454+
public int? BgIncreasedMs
455+
{
456+
get => this.GetIntValue(PropertyDefinition.BgIntervalIncreasedMs.Name);
457+
set => this.SetValue(PropertyDefinition.BgIntervalIncreasedMs.Name, value?.ToString());
458+
}
459+
460+
public int? BgHighMs
461+
{
462+
get => this.GetIntValue(PropertyDefinition.BgIntervalHighMs.Name);
463+
set => this.SetValue(PropertyDefinition.BgIntervalHighMs.Name, value?.ToString());
464+
}
465+
466+
public int? BgSwitchoverTimeoutMs
467+
{
468+
get => this.GetIntValue(PropertyDefinition.BgSwitchoverTimeoutMs.Name);
469+
set => this.SetValue(PropertyDefinition.BgSwitchoverTimeoutMs.Name, value?.ToString());
470+
}
471+
472+
public int? BgConnectTimeoutMs
473+
{
474+
get => this.GetIntValue(PropertyDefinition.BgConnectTimeout.Name);
475+
set => this.SetValue(PropertyDefinition.BgConnectTimeout.Name, value?.ToString());
476+
}
477+
442478
private string? GetValue(string key)
443479
{
444480
return this.TryGetValue(key, out object? value) ? value?.ToString() : null;

AwsWrapperDataProvider/Driver/ConnectionPluginManager.cs

Lines changed: 10 additions & 2 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 = [];
3032
protected IList<IConnectionPlugin> plugins = [];
33+
protected string[] activePluginCodes = [];
3134
protected IConnectionProvider defaultConnProvider;
3235
protected IConnectionProvider? effectiveConnProvider;
3336
protected ConfigurationProfile? configurationProfile;
@@ -90,6 +93,7 @@ public void InitConnectionPluginChain(
9093
this.effectiveConnProvider,
9194
props,
9295
this.configurationProfile);
96+
this.activePluginCodes = pluginChainBuilder.GetPluginCodes(this.pluginService, props);
9397
}
9498

9599
private async Task<T> ExecuteWithSubscribedPlugins<T>(
@@ -104,7 +108,7 @@ private async Task<T> ExecuteWithSubscribedPlugins<T>(
104108
if (!this.pluginChainDelegates.TryGetValue(methodName, out Delegate? del))
105109
{
106110
del = this.MakePluginChainDelegate<T>(methodName);
107-
this.pluginChainDelegates.Add(methodName, del);
111+
this.pluginChainDelegates[methodName] = del;
108112
}
109113

110114
if (del is not PluginChainADONetDelegate<T> pluginChainDelegate)
@@ -130,7 +134,6 @@ private async Task<T> ExecuteWithSubscribedPlugins<T>(
130134
private PluginChainADONetDelegate<T> MakePluginChainDelegate<T>(string methodName)
131135
{
132136
PluginChainADONetDelegate<T>? pluginChainDelegate = null;
133-
134137
for (int i = this.plugins.Count - 1; i >= 0; i--)
135138
{
136139
IConnectionPlugin plugin = this.plugins[i];
@@ -248,4 +251,9 @@ public virtual bool AcceptsStrategy(string strategy)
248251
{
249252
return this.defaultConnProvider.AcceptsStrategy(strategy);
250253
}
254+
255+
public bool IsPluginActive(string pluginCode)
256+
{
257+
return this.activePluginCodes.Contains(pluginCode);
258+
}
251259
}

AwsWrapperDataProvider/Driver/Dialects/AuroraMySqlDialect.cs

Lines changed: 16 additions & 2 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

@@ -39,6 +39,10 @@ public class AuroraMySqlDialect : MySqlDialect
3939
private static readonly string IsWriterQuery = "SELECT SERVER_ID FROM information_schema.replica_host_status "
4040
+ "WHERE SESSION_ID = 'MASTER_SESSION_ID' AND SERVER_ID = @@aurora_server_id";
4141

42+
private static readonly string AuroraMySqlBgTopologyExistsQuery = "SELECT 1 AS tmp FROM information_schema.tables WHERE table_schema = 'mysql' AND table_name = 'rds_topology'";
43+
44+
private static readonly string AuroraMySqlBgStatusQuery = "SELECT * FROM mysql.rds_topology";
45+
4246
public override IList<Type> DialectUpdateCandidates { get; } = [
4347
typeof(RdsMultiAzDbClusterMySqlDialect),
4448
];
@@ -54,7 +58,7 @@ public override async Task<bool> IsDialect(DbConnection connection)
5458
}
5559
catch (Exception ex) when (this.ExceptionHandler.IsSyntaxError(ex))
5660
{
57-
// Syntax error - expected when querying against incorrect dialect
61+
Logger.LogTrace(ex, Resources.Error_CantCheckDialect_Syntax, nameof(AuroraMySqlDialect));
5862
}
5963
catch (Exception ex)
6064
{
@@ -85,4 +89,14 @@ private HostListProviderSupplier GetHostListProviderSupplier()
8589
NodeIdQuery,
8690
IsReaderQuery);
8791
}
92+
93+
public async Task<bool> IsBlueGreenStatusAvailable(DbConnection connection)
94+
{
95+
return await DialectUtils.CheckExistenceQueries(connection, this.ExceptionHandler, Logger, AuroraMySqlBgTopologyExistsQuery);
96+
}
97+
98+
public string GetBlueGreenStatusQuery()
99+
{
100+
return AuroraMySqlBgStatusQuery;
101+
}
88102
}

AwsWrapperDataProvider/Driver/Dialects/AuroraPgDialect.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
using System.Data.Common;
16+
using System.Reflection;
1617
using AwsWrapperDataProvider.Driver.HostListProviders;
1718
using AwsWrapperDataProvider.Driver.HostListProviders.Monitoring;
1819
using AwsWrapperDataProvider.Driver.Utils;
@@ -21,7 +22,7 @@
2122

2223
namespace AwsWrapperDataProvider.Driver.Dialects;
2324

24-
public class AuroraPgDialect : PgDialect, IAuroraLimitlessDialect
25+
public class AuroraPgDialect : PgDialect, IAuroraLimitlessDialect, IBlueGreenDialect
2526
{
2627
private const string ReaderOrdinal = "aurora_stat_utils";
2728

@@ -48,6 +49,11 @@ public class AuroraPgDialect : PgDialect, IAuroraLimitlessDialect
4849
private static readonly string IsWriterQuery = "SELECT SERVER_ID FROM pg_catalog.aurora_replica_status() "
4950
+ "WHERE SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' AND SERVER_ID OPERATOR(pg_catalog.=) aurora_db_instance_identifier()";
5051

52+
protected static readonly string AuroraPostgreSqlBgTopologyExistsQuery = "SELECT 'pg_catalog.get_blue_green_fast_switchover_metadata'::regproc";
53+
54+
protected static readonly string DriverVersion = "1.0.1";
55+
protected static readonly string AuroraPostgreSqlBgStatusQuery = $"SELECT * FROM pg_catalog.get_blue_green_fast_switchover_metadata('aws_advanced_dotnet_data_provider_wrapper-{DriverVersion}')";
56+
5157
public override IList<Type> DialectUpdateCandidates { get; } = [
5258
typeof(RdsMultiAzDbClusterPgDialect),
5359
];
@@ -115,5 +121,15 @@ private HostListProviderSupplier GetHostListProviderSupplier()
115121
IsReaderQuery);
116122
}
117123

124+
public async Task<bool> IsBlueGreenStatusAvailable(DbConnection connection)
125+
{
126+
return await DialectUtils.CheckExistenceQueries(connection, this.ExceptionHandler, Logger, AuroraPostgreSqlBgTopologyExistsQuery);
127+
}
128+
129+
public string GetBlueGreenStatusQuery()
130+
{
131+
return AuroraPostgreSqlBgStatusQuery;
132+
}
133+
118134
public string LimitlessRouterEndpointQuery { get => "SELECT router_endpoint, load from aurora_limitless_router_endpoints()"; }
119135
}
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_advanced_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
}

AwsWrapperDataProvider/Driver/Dialects/RdsPgDialect.cs

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

2020
namespace AwsWrapperDataProvider.Driver.Dialects;
2121

22-
public class RdsPgDialect : PgDialect
22+
public class RdsPgDialect : PgDialect, IBlueGreenDialect
2323
{
2424
internal const string ExtensionsSql = "SELECT (setting LIKE '%rds_tools%') AS rds_tools, "
2525
+ "(setting LIKE '%aurora_stat_utils%') AS aurora_stat_utils "
2626
+ "FROM pg_catalog.pg_settings "
2727
+ "WHERE name OPERATOR(pg_catalog.=) 'rds.extensions'";
2828

29+
protected static readonly string RdsPgTopologyTableExistsQuery = "SELECT 'rds_tools.show_topology'::regproc";
30+
31+
protected static readonly string RdsPgBgStatusQuery = "SELECT * FROM rds_tools.show_topology('aws_advanced_dotnet_data_provider_wrapper')";
32+
2933
private static readonly ILogger<RdsPgDialect> Logger = LoggerUtils.GetLogger<RdsPgDialect>();
3034

3135
public override IList<Type> DialectUpdateCandidates { get; } =
@@ -58,7 +62,7 @@ public override async Task<bool> IsDialect(DbConnection conn)
5862
}
5963
catch (Exception ex) when (this.ExceptionHandler.IsSyntaxError(ex))
6064
{
61-
// Syntax error - expected when querying against incorrect dialect
65+
Logger.LogTrace(ex, Resources.Error_CantCheckDialect_Syntax, nameof(RdsPgDialect));
6266
}
6367
catch (Exception ex)
6468
{
@@ -67,4 +71,14 @@ public override async Task<bool> IsDialect(DbConnection conn)
6771

6872
return false;
6973
}
74+
75+
public async Task<bool> IsBlueGreenStatusAvailable(DbConnection connection)
76+
{
77+
return await DialectUtils.CheckExistenceQueries(connection, this.ExceptionHandler, Logger, RdsPgTopologyTableExistsQuery);
78+
}
79+
80+
public string GetBlueGreenStatusQuery()
81+
{
82+
return RdsPgBgStatusQuery;
83+
}
7084
}

AwsWrapperDataProvider/Driver/HostInfo/HostSpec.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,11 @@ public override string ToString()
197197
this.LastUpdateTime,
198198
this.HostId);
199199
}
200+
201+
public HostSpec Clone()
202+
{
203+
HostSpec copy = new(this.Host, this.Port, this.HostId, this.Role, this.RawAvailability, this.Weight, this.LastUpdateTime);
204+
copy.AddAlias(this.aliases.Keys.ToArray());
205+
return copy;
206+
}
200207
}

0 commit comments

Comments
 (0)