aws_advanced_ruby_driver_wrapper 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +175 -0
- data/NOTICE +1 -0
- data/README.md +168 -0
- data/THIRD-PARTY-LICENSES +473 -0
- data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
- data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
- data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
- data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
- metadata +227 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'concurrent'
|
|
18
|
+
require_relative '../errors'
|
|
19
|
+
require_relative '../host/host_info'
|
|
20
|
+
require_relative '../host/host_availability'
|
|
21
|
+
|
|
22
|
+
module AwsAdvancedRubyDriverWrapper
|
|
23
|
+
module Plugins
|
|
24
|
+
class DefaultPlugin
|
|
25
|
+
SUBSCRIBED_METHODS = Set['*'].freeze
|
|
26
|
+
HOST_PORT_KEYS = %i[host port].freeze
|
|
27
|
+
|
|
28
|
+
def initialize(service_container, _props)
|
|
29
|
+
@service_container = service_container
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def subscribed_methods
|
|
33
|
+
SUBSCRIBED_METHODS
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def connect(host_info, driver_props, is_initial_connection, _pipeline_callable)
|
|
37
|
+
connection_service = @service_container.connection_service
|
|
38
|
+
driver_dialect = @service_container.dialect_service.driver_dialect
|
|
39
|
+
target_host_info = if is_initial_connection && connection_service.multi_host_url?
|
|
40
|
+
# If the user specified a multi-host URL, we should always pass the same hosts/ports they
|
|
41
|
+
# specified. Note that host_info may have a different value than initial_host_info.
|
|
42
|
+
connection_service.initial_host_info
|
|
43
|
+
else
|
|
44
|
+
host_info
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
conn = driver_dialect.connect(target_host_info, driver_props)
|
|
48
|
+
|
|
49
|
+
if conn.nil?
|
|
50
|
+
raise Errors::AwsError,
|
|
51
|
+
"Failed to connect to #{target_host_info&.host}: driver returned nil connection"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# If host was not specified (Unix socket / localhost), fill in from the live connection.
|
|
55
|
+
if host_info.nil? || !host_info.host_specified?
|
|
56
|
+
host_info = Host::HostInfo.new(
|
|
57
|
+
host: conn.respond_to?(:host) && conn.host ? conn.host : Host::HostInfo::NO_HOST,
|
|
58
|
+
port: conn.respond_to?(:port) && conn.port ? conn.port.to_s : Host::HostInfo::NO_PORT
|
|
59
|
+
)
|
|
60
|
+
else
|
|
61
|
+
@service_container.host_service.set_availability(host_info, Host::HostAvailability::AVAILABLE)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
connection_service.update_current_connection(conn, host_info)
|
|
65
|
+
|
|
66
|
+
if is_initial_connection
|
|
67
|
+
@service_container.dialect_service.update_dialect(conn)
|
|
68
|
+
@service_container.host_service.refresh_host_list
|
|
69
|
+
|
|
70
|
+
if connection_service.pg? && connection_service.multi_host_url?
|
|
71
|
+
connection_service.config.initial_host_info = Host::HostInfo.new(
|
|
72
|
+
host: conn.host,
|
|
73
|
+
port: conn.port.to_i
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
init_func = AwsAdvancedRubyDriverWrapper.config.connection_init_func
|
|
79
|
+
init_func&.call(conn, host_info)
|
|
80
|
+
|
|
81
|
+
conn
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def internal_connect(host_info, driver_props, _wrapper_props, _is_initial_connection, _pipeline_callable)
|
|
85
|
+
driver_dialect = @service_container.dialect_service.driver_dialect
|
|
86
|
+
driver_dialect.connect(host_info, driver_props)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def execute(target_method_name, target_callable, *args, **, &)
|
|
90
|
+
session = @service_container.session_state_service
|
|
91
|
+
autocommit_before = session&.autocommit?
|
|
92
|
+
succeeded = false
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
result = target_callable.call(*args, **, &)
|
|
96
|
+
succeeded = true
|
|
97
|
+
result
|
|
98
|
+
ensure
|
|
99
|
+
if session && (conn = @service_container.connection_service.current_connection)
|
|
100
|
+
dialect = @service_container.dialect_service.driver_dialect
|
|
101
|
+
session.update_transaction_state(target_method_name, args, autocommit_before,
|
|
102
|
+
dialect, conn, succeeded: succeeded)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
module AwsAdvancedRubyDriverWrapper
|
|
18
|
+
module Plugins
|
|
19
|
+
module FailoverMode
|
|
20
|
+
STRICT_WRITER = :strict_writer
|
|
21
|
+
STRICT_READER = :strict_reader
|
|
22
|
+
READER_OR_WRITER = :reader_or_writer
|
|
23
|
+
|
|
24
|
+
def self.from_value(value)
|
|
25
|
+
return nil if value.nil?
|
|
26
|
+
|
|
27
|
+
normalized = value.to_s.strip.downcase
|
|
28
|
+
return nil if normalized.empty?
|
|
29
|
+
|
|
30
|
+
case normalized
|
|
31
|
+
when 'strict_writer', 'strict-writer', 'strictwriter'
|
|
32
|
+
STRICT_WRITER
|
|
33
|
+
when 'strict_reader', 'strict-reader', 'strictreader'
|
|
34
|
+
STRICT_READER
|
|
35
|
+
when 'reader_or_writer', 'reader-or-writer', 'readerorwriter'
|
|
36
|
+
READER_OR_WRITER
|
|
37
|
+
else
|
|
38
|
+
raise ArgumentError, "Invalid failover mode: '#{value}'"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require 'concurrent'
|
|
18
|
+
require_relative '../errors'
|
|
19
|
+
require_relative '../host/host_info'
|
|
20
|
+
require_relative '../host/host_role'
|
|
21
|
+
require_relative '../host/host_availability'
|
|
22
|
+
require_relative '../logging'
|
|
23
|
+
require_relative '../utils/rds_utils'
|
|
24
|
+
require_relative '../utils/rds_url_type'
|
|
25
|
+
require_relative '../utils/retry_util'
|
|
26
|
+
require_relative '../wrapper_property'
|
|
27
|
+
require_relative 'failover_mode'
|
|
28
|
+
|
|
29
|
+
module AwsAdvancedRubyDriverWrapper
|
|
30
|
+
module Plugins
|
|
31
|
+
class FailoverPlugin
|
|
32
|
+
include Logging
|
|
33
|
+
|
|
34
|
+
ReaderFailoverResult = Data.define(:connection, :host_info)
|
|
35
|
+
|
|
36
|
+
def initialize(service_container, props = ::Concurrent::Map.new)
|
|
37
|
+
@service_container = service_container
|
|
38
|
+
@wrapper_props = props
|
|
39
|
+
|
|
40
|
+
@retry_util = Utils::RetryUtil.new(service_container)
|
|
41
|
+
@failover_timeout = PropertyDefinition::FAILOVER_TIMEOUT_SEC.get_float(props)
|
|
42
|
+
@reader_selector_strategy = PropertyDefinition::FAILOVER_READER_HOST_SELECTOR_STRATEGY.get(props)
|
|
43
|
+
@failover_mode = nil
|
|
44
|
+
@rds_url_type = nil
|
|
45
|
+
@closed_explicitly = false
|
|
46
|
+
|
|
47
|
+
network_methods = @service_container.dialect_service.driver_dialect.network_bound_methods
|
|
48
|
+
@subscribed_methods = (Set['connect'] | network_methods).freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_reader :subscribed_methods
|
|
52
|
+
|
|
53
|
+
def connect(host_info, driver_props, is_initial_connection, pipeline_callable)
|
|
54
|
+
init_failover_mode
|
|
55
|
+
|
|
56
|
+
unless PropertyDefinition::ENABLE_CONNECT_FAILOVER.get_bool(@wrapper_props)
|
|
57
|
+
return verified_connection(is_initial_connection, host_info, driver_props, pipeline_callable)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
topology_host = host_service.hosts.find { |h| h.host_and_port == host_info&.host_and_port }
|
|
61
|
+
|
|
62
|
+
if !topology_host.nil? && topology_host.availability == Host::HostAvailability::UNAVAILABLE
|
|
63
|
+
host_service.refresh_host_list
|
|
64
|
+
return connect_via_failover(is_initial_connection)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
conn = verified_connection(is_initial_connection, host_info, driver_props, pipeline_callable)
|
|
69
|
+
host_service.refresh_host_list if is_initial_connection
|
|
70
|
+
conn
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
raise unless trigger_failover?(e)
|
|
73
|
+
|
|
74
|
+
host_service.set_availability(host_info, Host::HostAvailability::UNAVAILABLE)
|
|
75
|
+
connect_via_failover(is_initial_connection)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def execute(method_name, pipeline_callable, *, **, &)
|
|
80
|
+
if can_direct_execute?(method_name)
|
|
81
|
+
@closed_explicitly = true if method_name == RubyMethod::CONNECTION_CLOSE.name
|
|
82
|
+
return pipeline_callable.call
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
conn = connection_service.current_connection
|
|
86
|
+
if conn.nil?
|
|
87
|
+
logger.warn do
|
|
88
|
+
"[execute] current_connection is nil for #{method_name} — " \
|
|
89
|
+
"host_info=#{connection_service.current_host_info&.host}, " \
|
|
90
|
+
"closed_explicitly=#{@closed_explicitly}, failover_mode=#{@failover_mode.inspect}"
|
|
91
|
+
end
|
|
92
|
+
elsif !@closed_explicitly && driver_dialect.closed?(conn)
|
|
93
|
+
logger.warn("#{method_name} was called on closed connection #{conn} to #{connection_service.current_host_info}. " \
|
|
94
|
+
'The driver will attempt to failover and then execute.')
|
|
95
|
+
failover
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
pipeline_callable.call
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
handle_error(e)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def connect_via_failover(is_initial_connection)
|
|
108
|
+
begin
|
|
109
|
+
failover
|
|
110
|
+
rescue Errors::FailoverSuccessError => _e
|
|
111
|
+
conn = connection_service.current_connection
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
host_service.refresh_host_list if is_initial_connection
|
|
115
|
+
conn
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def connection_service
|
|
119
|
+
@service_container.connection_service
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def host_service
|
|
123
|
+
@service_container.host_service
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def dialect_service
|
|
127
|
+
@service_container.dialect_service
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def driver_dialect
|
|
131
|
+
dialect_service.driver_dialect
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def db_dialect
|
|
135
|
+
dialect_service.db_dialect
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def init_failover_mode
|
|
139
|
+
return unless @rds_url_type.nil?
|
|
140
|
+
|
|
141
|
+
@failover_mode = FailoverMode.from_value(PropertyDefinition::FAILOVER_MODE.get(@wrapper_props))
|
|
142
|
+
initial_host = connection_service.initial_host_info
|
|
143
|
+
|
|
144
|
+
@rds_url_type = Utils::RdsUtils.identify_rds_type(initial_host&.host)
|
|
145
|
+
reject_rds_proxy_endpoint
|
|
146
|
+
|
|
147
|
+
if @failover_mode.nil?
|
|
148
|
+
@failover_mode = if @rds_url_type == Utils::RdsUrlType::RDS_READER_CLUSTER
|
|
149
|
+
FailoverMode::READER_OR_WRITER
|
|
150
|
+
else
|
|
151
|
+
FailoverMode::STRICT_WRITER
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
logger.debug { "failover_mode=#{@failover_mode}" }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# RDS Proxy handles failover internally, so a failover plugin must not be layered on top of it.
|
|
159
|
+
#
|
|
160
|
+
# @raise [Errors::AwsError] if the initial endpoint is an RDS Proxy endpoint
|
|
161
|
+
def reject_rds_proxy_endpoint
|
|
162
|
+
return unless [Utils::RdsUrlType::RDS_PROXY, Utils::RdsUrlType::RDS_PROXY_ENDPOINT].include?(@rds_url_type)
|
|
163
|
+
|
|
164
|
+
raise Errors::AwsError,
|
|
165
|
+
'The failover plugin is not compatible with RDS Proxy endpoints. ' \
|
|
166
|
+
'RDS Proxy handles failover internally - please remove the failover plugin from your configuration.'
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def can_direct_execute?(method_name)
|
|
170
|
+
method_name == RubyMethod::CONNECTION_CLOSE.name ||
|
|
171
|
+
method_name == RubyMethod::CONNECTION_PING.name
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def handle_error(error)
|
|
175
|
+
logger.debug { "Detected error: #{error.message}" }
|
|
176
|
+
raise error unless trigger_failover?(error)
|
|
177
|
+
|
|
178
|
+
invalidate_current_connection
|
|
179
|
+
host_service.set_availability(
|
|
180
|
+
connection_service.current_host_info,
|
|
181
|
+
Host::HostAvailability::UNAVAILABLE
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
failover
|
|
185
|
+
# Failover will be skipped if the connection was explicitly closed. In this case we raise the error here.
|
|
186
|
+
raise error
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def trigger_failover?(error)
|
|
190
|
+
if host_service.all_hosts.empty?
|
|
191
|
+
logger.debug do
|
|
192
|
+
"Skipping failover for #{connection_service.current_host_info}: no topology available."
|
|
193
|
+
end
|
|
194
|
+
return false
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
return true if dialect_service.network_error?(error)
|
|
198
|
+
|
|
199
|
+
dialect_service.read_only_error?(error) && failover_on_read_only_error?
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Whether a read-only error should trigger failover. It should only do so when failover would
|
|
203
|
+
# target a writer, since a read-only error means the current connection is not one.
|
|
204
|
+
#
|
|
205
|
+
# @return [Boolean]
|
|
206
|
+
def failover_on_read_only_error?
|
|
207
|
+
@failover_mode == FailoverMode::STRICT_WRITER
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Rolls back an open transaction on the current connection so it does not linger server-side.
|
|
211
|
+
#
|
|
212
|
+
# The broken connection is intentionally left open rather than closed here. A real pg/mysql2
|
|
213
|
+
# connection is left broken-but-open on network failure; its owner (ActiveRecord via
|
|
214
|
+
# disconnect!/discard!, or a raw-driver caller) is responsible for closing it. Closing it eagerly
|
|
215
|
+
# mid-error-handling would leave the facade pointing at a freed object on a failed failover, so
|
|
216
|
+
# introspection calls AR makes during its error path (e.g. transaction_status, ping) would raise
|
|
217
|
+
# instead of reporting a broken state. On a successful failover the old connection is closed when
|
|
218
|
+
# the new one is swapped in via ConnectionService#update_current_connection.
|
|
219
|
+
def invalidate_current_connection
|
|
220
|
+
conn = connection_service.current_connection
|
|
221
|
+
return if conn.nil?
|
|
222
|
+
return unless @service_container.session_state_service.in_transaction?
|
|
223
|
+
|
|
224
|
+
begin
|
|
225
|
+
driver_dialect.execute(conn, 'ROLLBACK')
|
|
226
|
+
rescue StandardError
|
|
227
|
+
nil
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def failover
|
|
232
|
+
if @closed_explicitly
|
|
233
|
+
logger.debug { 'Connection was explicitly closed, skipping failover' }
|
|
234
|
+
return
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
if @failover_mode == FailoverMode::STRICT_WRITER
|
|
238
|
+
failover_writer
|
|
239
|
+
else
|
|
240
|
+
failover_reader
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def failover_reader
|
|
245
|
+
failover_start = Time.now
|
|
246
|
+
failover_deadline = failover_start + @failover_timeout
|
|
247
|
+
|
|
248
|
+
logger.info { 'Starting reader failover' }
|
|
249
|
+
|
|
250
|
+
unless host_service.force_refresh_host_list?(verify_writer: false, timeout_sec: 0)
|
|
251
|
+
raise Errors::FailoverFailedError, 'The request to discover the new topology was unsuccessful'
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
begin
|
|
255
|
+
was_in_transaction = @service_container.session_state_service.in_transaction?
|
|
256
|
+
result = reader_failover_connection(failover_deadline)
|
|
257
|
+
raise Errors::FailoverFailedError, 'Unable to connect to a reader instance' unless result&.connection
|
|
258
|
+
|
|
259
|
+
connection_service.update_current_connection(result.connection, result.host_info)
|
|
260
|
+
rescue Timeout::Error
|
|
261
|
+
raise Errors::FailoverFailedError, 'Unable to connect to a reader instance'
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
raise_failover_success_error(was_in_transaction)
|
|
265
|
+
ensure
|
|
266
|
+
duration_ms = ((Time.now - failover_start) * 1000).round
|
|
267
|
+
logger.debug { "Reader failover duration: #{duration_ms}ms" }
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def failover_writer
|
|
271
|
+
failover_start = Time.now
|
|
272
|
+
failover_deadline = failover_start + @failover_timeout
|
|
273
|
+
result = nil
|
|
274
|
+
|
|
275
|
+
logger.info { 'Starting writer failover' }
|
|
276
|
+
|
|
277
|
+
begin
|
|
278
|
+
unless host_service.force_refresh_host_list?(verify_writer: true, timeout_sec: @failover_timeout)
|
|
279
|
+
raise Errors::FailoverFailedError, 'The request to discover the new topology timed out or was unsuccessful'
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
was_in_transaction = @service_container.session_state_service.in_transaction?
|
|
283
|
+
result = @retry_util.connect_to_writer(self, @service_container.plugin_manager, deadline: failover_deadline)
|
|
284
|
+
raise Errors::FailoverFailedError, 'Unable to connect to a writer instance' unless result&.connection && result.host_info
|
|
285
|
+
|
|
286
|
+
connection_service.update_current_connection(result.connection, result.host_info)
|
|
287
|
+
rescue Timeout::Error
|
|
288
|
+
close_quietly(result&.connection)
|
|
289
|
+
raise Errors::FailoverFailedError,
|
|
290
|
+
"Writer failover timed out after #{@failover_timeout}s. Unable to connect to a new writer instance."
|
|
291
|
+
ensure
|
|
292
|
+
duration_ms = ((Time.now - failover_start) * 1000).round
|
|
293
|
+
logger.debug { "Writer failover duration: #{duration_ms}ms" }
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
raise_failover_success_error(was_in_transaction)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def raise_failover_success_error(was_in_transaction)
|
|
300
|
+
logger.debug { "Established connection to: #{connection_service.current_host_info}" }
|
|
301
|
+
raise Errors::FailoverSuccessError unless was_in_transaction
|
|
302
|
+
|
|
303
|
+
@service_container.session_state_service.in_transaction = false
|
|
304
|
+
raise Errors::TransactionStateUnknownError
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def reader_failover_connection(deadline)
|
|
308
|
+
original_writer = nil
|
|
309
|
+
original_writer_still_writer = false
|
|
310
|
+
|
|
311
|
+
loop do
|
|
312
|
+
break if Time.now >= deadline
|
|
313
|
+
|
|
314
|
+
hosts = host_service.hosts
|
|
315
|
+
reader_candidates = hosts.select { |h| h.role == Host::HostRole::READER }
|
|
316
|
+
original_writer ||= hosts.find { |h| h.role == Host::HostRole::WRITER }
|
|
317
|
+
|
|
318
|
+
result = try_reader_candidates(reader_candidates, deadline)
|
|
319
|
+
return result if result
|
|
320
|
+
|
|
321
|
+
result = try_original_writer(original_writer, original_writer_still_writer)
|
|
322
|
+
case result
|
|
323
|
+
when ReaderFailoverResult
|
|
324
|
+
return result
|
|
325
|
+
when :still_writer
|
|
326
|
+
original_writer_still_writer = true
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
sleep(0.1)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
raise Timeout::Error, 'The reader failover process was not able to establish a connection before timing out.'
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def try_reader_candidates(reader_candidates, deadline)
|
|
336
|
+
remaining = reader_candidates.dup
|
|
337
|
+
|
|
338
|
+
while !remaining.empty? && Time.now < deadline
|
|
339
|
+
candidate = select_reader_candidate(remaining)
|
|
340
|
+
if candidate.nil?
|
|
341
|
+
# Unable to find available candidate in the host list. Let's try assuming all hosts are available.
|
|
342
|
+
available = remaining.map do |h|
|
|
343
|
+
h.deep_dup.tap { |dup| dup.availability = Host::HostAvailability::AVAILABLE }
|
|
344
|
+
end
|
|
345
|
+
candidate = select_reader_candidate(available)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
if candidate.nil?
|
|
349
|
+
logger.debug { 'Unable to find reader in the updated host list.' }
|
|
350
|
+
break
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
outcome, result = attempt_reader_connection(candidate)
|
|
354
|
+
case outcome
|
|
355
|
+
when :success
|
|
356
|
+
return result
|
|
357
|
+
when :writer
|
|
358
|
+
reader_candidates.delete(candidate)
|
|
359
|
+
remaining.delete(candidate)
|
|
360
|
+
else
|
|
361
|
+
remaining.delete(candidate)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
nil
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def try_original_writer(original_writer, original_writer_still_writer)
|
|
369
|
+
return nil if original_writer.nil?
|
|
370
|
+
return nil if @failover_mode == FailoverMode::STRICT_READER && original_writer_still_writer
|
|
371
|
+
|
|
372
|
+
outcome, result = attempt_reader_connection(original_writer)
|
|
373
|
+
case outcome
|
|
374
|
+
when :success
|
|
375
|
+
result
|
|
376
|
+
when :writer
|
|
377
|
+
:still_writer
|
|
378
|
+
else
|
|
379
|
+
logger.debug { "Failed to connect to host: #{original_writer.url}" }
|
|
380
|
+
nil
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def attempt_reader_connection(host_info)
|
|
385
|
+
conn = @service_container.plugin_manager.connect(host_info, connection_service.driver_props, false, plugin_to_skip: self)
|
|
386
|
+
# Since the roles in the host list might not be accurate, we execute a query to check the instance's role.
|
|
387
|
+
role = db_dialect.host_role(conn)
|
|
388
|
+
|
|
389
|
+
if role == Host::HostRole::READER || @failover_mode != FailoverMode::STRICT_READER
|
|
390
|
+
updated_host = host_info.deep_dup(role: role)
|
|
391
|
+
return [:success, ReaderFailoverResult.new(conn, updated_host)]
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# The role is WRITER or UNKNOWN, and we are in STRICT_READER mode, so the connection is not valid.
|
|
395
|
+
close_quietly(conn)
|
|
396
|
+
if role == Host::HostRole::WRITER
|
|
397
|
+
[:writer, nil]
|
|
398
|
+
else
|
|
399
|
+
logger.debug do
|
|
400
|
+
"Unable to determine host role for #{host_info.url}. " \
|
|
401
|
+
'Since failover mode is set to STRICT_READER and the host may be a writer, ' \
|
|
402
|
+
'it will not be selected for reader failover.'
|
|
403
|
+
end
|
|
404
|
+
[:unknown, nil]
|
|
405
|
+
end
|
|
406
|
+
rescue StandardError
|
|
407
|
+
close_quietly(conn)
|
|
408
|
+
[:failed, nil]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def select_reader_candidate(hosts)
|
|
412
|
+
host_service.select_host(
|
|
413
|
+
hosts,
|
|
414
|
+
Host::HostRole::READER,
|
|
415
|
+
@reader_selector_strategy
|
|
416
|
+
)
|
|
417
|
+
rescue StandardError
|
|
418
|
+
nil
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def verified_connection(is_initial_connection, host_info, driver_props, connect_func)
|
|
422
|
+
url_type = Utils::RdsUtils.identify_rds_type(host_info&.host)
|
|
423
|
+
if url_type != Utils::RdsUrlType::RDS_WRITER_CLUSTER
|
|
424
|
+
# We are not using a writer cluster endpoint. No verification needed - continue with the regular workflow.
|
|
425
|
+
return connect_func.call
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
conn = connect_func.call
|
|
429
|
+
if db_dialect.host_role(conn) == Host::HostRole::WRITER
|
|
430
|
+
host_service.refresh_host_list
|
|
431
|
+
return conn
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# The writer cluster URL resolved to a reader. We will try to redirect to the writer instance.
|
|
435
|
+
host_service.force_refresh_host_list?(verify_writer: false, timeout_sec: 5.0)
|
|
436
|
+
writer = host_service.all_hosts.find { |h| h.role == Host::HostRole::WRITER }
|
|
437
|
+
if writer.nil? || Utils::RdsUtils.rds_cluster_dns?(writer.host)
|
|
438
|
+
# Writer instance endpoint not found - unable to redirect.
|
|
439
|
+
close_quietly(conn)
|
|
440
|
+
raise Errors::AwsError, 'Stale DNS detected - a writer was requested, but the writer cluster endpoint resolved to a reader'
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
allowed_hosts = host_service.hosts
|
|
444
|
+
unless allowed_hosts.any? { |h| h.host_and_port == writer.host_and_port }
|
|
445
|
+
raise Errors::AwsError, "Current writer #{writer.host_and_port} is not in allowed hosts"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Attempt to correct the stale DNS problem by connecting to the writer instance.
|
|
449
|
+
logger.debug { "Stale DNS data detected. Opening a connection to #{writer.host}" }
|
|
450
|
+
writer_conn = @service_container.plugin_manager.connect(writer, driver_props, false, plugin_to_skip: self)
|
|
451
|
+
connection_service.initial_host_info = writer if is_initial_connection
|
|
452
|
+
|
|
453
|
+
# Close the incorrect reader connection.
|
|
454
|
+
close_quietly(conn)
|
|
455
|
+
writer_conn
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def close_quietly(conn)
|
|
459
|
+
return if conn.nil?
|
|
460
|
+
|
|
461
|
+
driver_dialect.close_connection(conn)
|
|
462
|
+
rescue StandardError
|
|
463
|
+
# ignore
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
module AwsAdvancedRubyDriverWrapper
|
|
18
|
+
module Plugins
|
|
19
|
+
module Gdb
|
|
20
|
+
# The host roles and regions that the GDB failover plugin may target during failover.
|
|
21
|
+
module GdbFailoverMode
|
|
22
|
+
STRICT_WRITER = :strict_writer
|
|
23
|
+
STRICT_HOME_READER = :strict_home_reader
|
|
24
|
+
STRICT_OUT_OF_HOME_READER = :strict_out_of_home_reader
|
|
25
|
+
STRICT_ANY_READER = :strict_any_reader
|
|
26
|
+
HOME_READER_OR_WRITER = :home_reader_or_writer
|
|
27
|
+
OUT_OF_HOME_READER_OR_WRITER = :out_of_home_reader_or_writer
|
|
28
|
+
ANY_READER_OR_WRITER = :any_reader_or_writer
|
|
29
|
+
|
|
30
|
+
ALL = [
|
|
31
|
+
STRICT_WRITER,
|
|
32
|
+
STRICT_HOME_READER,
|
|
33
|
+
STRICT_OUT_OF_HOME_READER,
|
|
34
|
+
STRICT_ANY_READER,
|
|
35
|
+
HOME_READER_OR_WRITER,
|
|
36
|
+
OUT_OF_HOME_READER_OR_WRITER,
|
|
37
|
+
ANY_READER_OR_WRITER
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
# Maps every accepted spelling of a mode to the mode itself. Kebab-case, snake_case and
|
|
41
|
+
# squashed spellings are all accepted, e.g. 'strict-home-reader', 'strict_home_reader'
|
|
42
|
+
# and 'stricthomereader'.
|
|
43
|
+
NAME_TO_VALUE = ALL.each_with_object({}) do |mode, mapping|
|
|
44
|
+
snake = mode.to_s
|
|
45
|
+
mapping[snake] = mode
|
|
46
|
+
mapping[snake.tr('_', '-')] = mode
|
|
47
|
+
mapping[snake.delete('_')] = mode
|
|
48
|
+
end.freeze
|
|
49
|
+
|
|
50
|
+
# Resolves a configured mode string into one of the mode constants.
|
|
51
|
+
#
|
|
52
|
+
# @param value [String, Symbol, nil] the configured value
|
|
53
|
+
# @return [Symbol, nil] the mode, or nil when no value was configured
|
|
54
|
+
# @raise [ArgumentError] if the value does not name a known mode
|
|
55
|
+
def self.from_value(value)
|
|
56
|
+
return nil if value.nil?
|
|
57
|
+
|
|
58
|
+
normalized = value.to_s.strip.downcase
|
|
59
|
+
return nil if normalized.empty?
|
|
60
|
+
|
|
61
|
+
NAME_TO_VALUE.fetch(normalized) do
|
|
62
|
+
raise ArgumentError, "Invalid global database failover mode: '#{value}'"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|