Skip to content

Commit 748b74b

Browse files
authored
feat: blue green support (#498)
1 parent 49b300b commit 748b74b

File tree

88 files changed

+6678
-582
lines changed

Some content is hidden

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

88 files changed

+6678
-582
lines changed

common/lib/aws_client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ export abstract class AwsClient extends EventEmitter {
177177

178178
abstract rollback(): Promise<any>;
179179

180+
unwrapPlugin<T>(iface: new (...args: any[]) => T): T | null {
181+
return this.pluginManager.unwrapPlugin(iface);
182+
}
183+
180184
async isValid(): Promise<boolean> {
181185
if (!this.targetClient) {
182186
return Promise.resolve(false);

common/lib/connection_plugin_chain_builder.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,16 @@ import { ReadWriteSplittingPluginFactory } from "./plugins/read_write_splitting_
3333
import { OktaAuthPluginFactory } from "./plugins/federated_auth/okta_auth_plugin_factory";
3434
import { HostMonitoringPluginFactory } from "./plugins/efm/host_monitoring_plugin_factory";
3535
import { AuroraInitialConnectionStrategyFactory } from "./plugins/aurora_initial_connection_strategy_plugin_factory";
36-
import {
37-
AuroraConnectionTrackerPluginFactory
38-
} from "./plugins/connection_tracker/aurora_connection_tracker_plugin_factory";
36+
import { AuroraConnectionTrackerPluginFactory } from "./plugins/connection_tracker/aurora_connection_tracker_plugin_factory";
3937
import { ConnectionProviderManager } from "./connection_provider_manager";
4038
import { DeveloperConnectionPluginFactory } from "./plugins/dev/developer_connection_plugin_factory";
4139
import { ConnectionPluginFactory } from "./plugin_factory";
4240
import { LimitlessConnectionPluginFactory } from "./plugins/limitless/limitless_connection_plugin_factory";
43-
import {
44-
FastestResponseStrategyPluginFactory
45-
} from "./plugins/strategy/fastest_response/fastest_respose_strategy_plugin_factory";
41+
import { FastestResponseStrategyPluginFactory } from "./plugins/strategy/fastest_response/fastest_respose_strategy_plugin_factory";
4642
import { CustomEndpointPluginFactory } from "./plugins/custom_endpoint/custom_endpoint_plugin_factory";
4743
import { ConfigurationProfile } from "./profile/configuration_profile";
4844
import { HostMonitoring2PluginFactory } from "./plugins/efm2/host_monitoring2_plugin_factory";
45+
import { BlueGreenPluginFactory } from "./plugins/bluegreen/blue_green_plugin_factory";
4946

5047
/*
5148
Type alias used for plugin factory sorting. It holds a reference to a plugin
@@ -64,6 +61,7 @@ export class ConnectionPluginChainBuilder {
6461
["initialConnection", { factory: AuroraInitialConnectionStrategyFactory, weight: 390 }],
6562
["auroraConnectionTracker", { factory: AuroraConnectionTrackerPluginFactory, weight: 400 }],
6663
["staleDns", { factory: StaleDnsPluginFactory, weight: 500 }],
64+
["bg", { factory: BlueGreenPluginFactory, weight: 550 }],
6765
["readWriteSplitting", { factory: ReadWriteSplittingPluginFactory, weight: 600 }],
6866
["failover", { factory: FailoverPluginFactory, weight: 700 }],
6967
["failover2", { factory: Failover2PluginFactory, weight: 710 }],
@@ -84,6 +82,7 @@ export class ConnectionPluginChainBuilder {
8482
[AuroraInitialConnectionStrategyFactory, 390],
8583
[AuroraConnectionTrackerPluginFactory, 400],
8684
[StaleDnsPluginFactory, 500],
85+
[BlueGreenPluginFactory, 550],
8786
[ReadWriteSplittingPluginFactory, 600],
8887
[FailoverPluginFactory, 700],
8988
[Failover2PluginFactory, 710],
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License").
5+
You may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { ClientWrapper } from "../client_wrapper";
18+
19+
export class BlueGreenResult {
20+
private readonly _version: string;
21+
private readonly _endpoint: string;
22+
private readonly _port: number;
23+
private readonly _role: string;
24+
private readonly _status: string;
25+
26+
constructor(version: string, endpoint: string, port: number, role: string, status: string) {
27+
this._version = version;
28+
this._endpoint = endpoint;
29+
this._port = port;
30+
this._role = role;
31+
this._status = status;
32+
}
33+
34+
get version(): string {
35+
return this._version;
36+
}
37+
38+
get endpoint(): string {
39+
return this._endpoint;
40+
}
41+
42+
get port(): number {
43+
return this._port;
44+
}
45+
46+
get role(): string {
47+
return this._role;
48+
}
49+
50+
get status(): string {
51+
return this._status;
52+
}
53+
}
54+
55+
export interface BlueGreenDialect {
56+
isBlueGreenStatusAvailable(clientWrapper: ClientWrapper): Promise<boolean>;
57+
getBlueGreenStatus(clientWrapper: ClientWrapper): Promise<BlueGreenResult[] | null>;
58+
}

common/lib/driver_connection_provider.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { promisify } from "util";
2727
import { lookup } from "dns";
2828
import { PluginService } from "./plugin_service";
2929
import { logger } from "../logutils";
30-
import { maskProperties } from "./utils/utils";
3130
import { ClientWrapper } from "./client_wrapper";
3231
import { RoundRobinHostSelector } from "./round_robin_host_selector";
3332
import { DriverDialect } from "./driver_dialect/driver_dialect";
@@ -88,14 +87,7 @@ export class DriverConnectionProvider implements ConnectionProvider {
8887
const fixedHost: string = this.rdsUtils.removeGreenInstancePrefix(hostInfo.host);
8988
resultProps.set(WrapperProperties.HOST.name, fixedHost);
9089

91-
logger.info(
92-
"Connecting to " +
93-
fixedHost +
94-
" after correcting the hostname from " +
95-
originalHost +
96-
"\nwith properties: \n" +
97-
JSON.stringify(Object.fromEntries(maskProperties(resultProps)))
98-
);
90+
logger.info("Connecting to " + fixedHost + " after correcting the hostname from " + originalHost);
9991

10092
resultTargetClient = driverDialect.connect(hostInfo, resultProps);
10193
}

common/lib/error_handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export interface ErrorHandler {
2121

2222
isNetworkError(e: Error): boolean;
2323

24+
isSyntaxError(e: Error): boolean;
25+
2426
/**
2527
* Checks whether there has been an unexpected error emitted and if the error is a type of login error.
2628
*/

common/lib/plugin_manager.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,32 @@ export class PluginManager {
413413
throw new AwsWrapperError(Messages.get("PluginManager.unableToRetrievePlugin"));
414414
}
415415

416+
isPluginInUse(plugin: any): boolean {
417+
for (const p of this._plugins) {
418+
if (p instanceof plugin) {
419+
return true;
420+
}
421+
}
422+
return false;
423+
}
424+
416425
static registerPlugin(pluginCode: string, pluginFactory: typeof ConnectionPluginFactory) {
417426
ConnectionPluginChainBuilder.PLUGIN_FACTORIES.set(pluginCode, {
418427
factory: pluginFactory,
419428
weight: ConnectionPluginChainBuilder.WEIGHT_RELATIVE_TO_PRIOR_PLUGIN
420429
});
421430
}
431+
432+
unwrapPlugin<T>(iface: new (...args: any[]) => T): T | null {
433+
if (!this._plugins) {
434+
return null;
435+
}
436+
437+
for (const p of this._plugins) {
438+
if (p instanceof iface) {
439+
return p as any;
440+
}
441+
}
442+
return null;
443+
}
422444
}

common/lib/plugin_service.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ export interface PluginService extends ErrorHandler {
140140
getTelemetryFactory(): TelemetryFactory;
141141

142142
setAllowedAndBlockedHosts(allowedAndBlockedHosts: AllowedAndBlockedHosts): void;
143+
144+
setStatus<T>(clazz: any, status: T | null, clusterBound: boolean): void;
145+
146+
setStatus<T>(clazz: any, status: T | null, key: string): void;
147+
148+
getStatus<T>(clazz: any, clusterBound: boolean): T;
149+
150+
getStatus<T>(clazz: any, key: string): T;
151+
152+
isPluginInUse(plugin: any): boolean;
143153
}
144154

145155
export class PluginServiceImpl implements PluginService, HostListProviderService {
@@ -159,6 +169,8 @@ export class PluginServiceImpl implements PluginService, HostListProviderService
159169
protected static readonly hostAvailabilityExpiringCache: CacheMap<string, HostAvailability> = new CacheMap<string, HostAvailability>();
160170
readonly props: Map<string, any>;
161171
private allowedAndBlockedHosts: AllowedAndBlockedHosts | null = null;
172+
protected static readonly statusesExpiringCache: CacheMap<string, any> = new CacheMap();
173+
protected static readonly DEFAULT_STATUS_CACHE_EXPIRE_NANO: number = 3_600_000_000_000; // 60 minutes
162174

163175
constructor(
164176
container: PluginServiceManagerContainer,
@@ -686,6 +698,10 @@ export class PluginServiceImpl implements PluginService, HostListProviderService
686698
return this.getDialect().getErrorHandler().isNetworkError(e);
687699
}
688700

701+
isSyntaxError(e: Error): boolean {
702+
return this.getDialect().getErrorHandler().isSyntaxError(e);
703+
}
704+
689705
hasLoginError(): boolean {
690706
return this.getDialect().getErrorHandler().hasLoginError();
691707
}
@@ -713,4 +729,53 @@ export class PluginServiceImpl implements PluginService, HostListProviderService
713729
static clearHostAvailabilityCache(): void {
714730
PluginServiceImpl.hostAvailabilityExpiringCache.clear();
715731
}
732+
733+
getStatus<T>(clazz: any, clusterBound: boolean): T;
734+
getStatus<T>(clazz: any, key: string): T;
735+
getStatus<T>(clazz: any, clusterBound: boolean | string): T {
736+
if (typeof clusterBound === "string") {
737+
return <T>PluginServiceImpl.statusesExpiringCache.get(this.getStatusCacheKey(clazz, clusterBound));
738+
}
739+
let clusterId: string = null;
740+
if (clusterBound) {
741+
try {
742+
clusterId = this._hostListProvider.getClusterId();
743+
} catch (e) {
744+
// Do nothing
745+
}
746+
}
747+
return this.getStatus(clazz, clusterId);
748+
}
749+
750+
protected getStatusCacheKey<T>(clazz: T, key: string): string {
751+
return `${!key ? "" : key.trim().toLowerCase()}::${clazz.toString()}`;
752+
}
753+
754+
setStatus<T>(clazz: any, status: T | null, clusterBound: boolean): void;
755+
setStatus<T>(clazz: any, status: T | null, key: string): void;
756+
setStatus<T>(clazz: any, status: T, clusterBound: boolean | string): void {
757+
if (typeof clusterBound === "string") {
758+
const cacheKey: string = this.getStatusCacheKey(clazz, clusterBound);
759+
if (!status) {
760+
PluginServiceImpl.statusesExpiringCache.delete(cacheKey);
761+
} else {
762+
PluginServiceImpl.statusesExpiringCache.put(cacheKey, status, PluginServiceImpl.DEFAULT_STATUS_CACHE_EXPIRE_NANO);
763+
}
764+
return;
765+
}
766+
767+
let clusterId: string | null = null;
768+
if (clusterBound) {
769+
try {
770+
clusterId = this._hostListProvider.getClusterId();
771+
} catch (e) {
772+
// Do nothing
773+
}
774+
}
775+
this.setStatus(clazz, status, clusterId);
776+
}
777+
778+
isPluginInUse(plugin: any) {
779+
return this.pluginServiceManagerContainer.pluginManager!.isPluginInUse(plugin);
780+
}
716781
}

0 commit comments

Comments
 (0)