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,564 @@
|
|
|
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 'resolv'
|
|
19
|
+
require_relative '../../logging'
|
|
20
|
+
require_relative '../../monitoring/monitor'
|
|
21
|
+
require_relative '../../monitoring/monitor_connection'
|
|
22
|
+
require_relative '../../property_definition'
|
|
23
|
+
require_relative '../../utils/rds_utils'
|
|
24
|
+
require_relative '../../utils/connection_config'
|
|
25
|
+
require_relative '../../services/connection_service'
|
|
26
|
+
require_relative '../../services/service_container'
|
|
27
|
+
require_relative '../../host/rds_host_list_provider'
|
|
28
|
+
require_relative 'phase'
|
|
29
|
+
require_relative 'role'
|
|
30
|
+
require_relative 'status_info'
|
|
31
|
+
require_relative 'interim_status'
|
|
32
|
+
require_relative 'interval_rate'
|
|
33
|
+
|
|
34
|
+
module AwsAdvancedRubyDriverWrapper
|
|
35
|
+
module Plugins
|
|
36
|
+
module BlueGreen
|
|
37
|
+
# Background thread that monitors the Blue/Green status table for one role (SOURCE or TARGET).
|
|
38
|
+
class StatusMonitor < Monitoring::Monitor
|
|
39
|
+
include Logging
|
|
40
|
+
|
|
41
|
+
TERMINATION_TIMEOUT_SEC = 30.0
|
|
42
|
+
BG_CLUSTER = PropertyDefinition::BG_STORAGE_NAMESPACE
|
|
43
|
+
DEFAULT_CHECK_INTERVAL_MS = 100
|
|
44
|
+
LATEST_KNOWN_VERSION = '1.0'
|
|
45
|
+
KNOWN_VERSIONS = Set[LATEST_KNOWN_VERSION].freeze
|
|
46
|
+
|
|
47
|
+
def initialize(
|
|
48
|
+
role,
|
|
49
|
+
bgd_id,
|
|
50
|
+
initial_host,
|
|
51
|
+
service_container,
|
|
52
|
+
monitoring_driver_props:,
|
|
53
|
+
status_check_interval_map:,
|
|
54
|
+
on_change_func:,
|
|
55
|
+
monitoring_wrapper_props: {}
|
|
56
|
+
)
|
|
57
|
+
super(termination_timeout_sec: TERMINATION_TIMEOUT_SEC)
|
|
58
|
+
|
|
59
|
+
@role = role
|
|
60
|
+
@bgd_id = bgd_id.freeze
|
|
61
|
+
@initial_host = initial_host.deep_dup.freeze
|
|
62
|
+
@service_container = service_container
|
|
63
|
+
@status_monitor_driver_props = monitoring_driver_props
|
|
64
|
+
@status_monitor_wrapper_props = monitoring_wrapper_props
|
|
65
|
+
@status_check_interval_map = status_check_interval_map
|
|
66
|
+
@on_change_func = on_change_func
|
|
67
|
+
|
|
68
|
+
@current_phase = Phase::NOT_CREATED
|
|
69
|
+
@version = LATEST_KNOWN_VERSION
|
|
70
|
+
@port = -1
|
|
71
|
+
@connection = Monitoring::MonitorConnection.new(service_container.dialect_service.driver_dialect)
|
|
72
|
+
@connection_mutex = Mutex.new
|
|
73
|
+
@use_ip_address = Concurrent::AtomicBoolean.new(false)
|
|
74
|
+
@panic_mode = Concurrent::AtomicBoolean.new(true)
|
|
75
|
+
@correct_connection_host = Concurrent::AtomicBoolean.new(false)
|
|
76
|
+
@connected_ip_address = Concurrent::AtomicReference.new(nil)
|
|
77
|
+
@start_ip_addresses_by_host_map = Concurrent::Hash.new
|
|
78
|
+
@connection_host_info = Concurrent::AtomicReference.new(nil)
|
|
79
|
+
@open_connection_future = Concurrent::AtomicReference.new(nil)
|
|
80
|
+
@event = Concurrent::Event.new
|
|
81
|
+
@interval_rate = Concurrent::AtomicReference.new(IntervalRate::BASELINE)
|
|
82
|
+
@current_topology = Concurrent::AtomicReference.new(nil)
|
|
83
|
+
@start_topology = nil
|
|
84
|
+
@collect_topology = Concurrent::AtomicBoolean.new(true)
|
|
85
|
+
@collect_ip_addresses = Concurrent::AtomicBoolean.new(true)
|
|
86
|
+
@host_names = Set.new
|
|
87
|
+
@current_ip_addresses_by_host_map = Concurrent::Hash.new
|
|
88
|
+
@all_start_topology_ip_changed = false
|
|
89
|
+
@all_start_topology_endpoints_removed = false
|
|
90
|
+
@all_topology_changed = false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def monitor
|
|
94
|
+
loop do
|
|
95
|
+
break if stopped?
|
|
96
|
+
|
|
97
|
+
begin
|
|
98
|
+
old_phase = @current_phase
|
|
99
|
+
open_connection
|
|
100
|
+
collect_status
|
|
101
|
+
collect_topology
|
|
102
|
+
collect_host_ip_addresses
|
|
103
|
+
update_ip_address_flags
|
|
104
|
+
|
|
105
|
+
logger.debug { "[#{@role}] Status changed to: #{@current_phase}" } if @current_phase && @current_phase != old_phase
|
|
106
|
+
|
|
107
|
+
@on_change_func&.call(
|
|
108
|
+
@role,
|
|
109
|
+
InterimStatus.new(
|
|
110
|
+
@current_phase,
|
|
111
|
+
@version,
|
|
112
|
+
@port,
|
|
113
|
+
@start_topology&.dup,
|
|
114
|
+
@current_topology.get&.dup,
|
|
115
|
+
@start_ip_addresses_by_host_map.dup,
|
|
116
|
+
@current_ip_addresses_by_host_map.dup,
|
|
117
|
+
@host_names.dup,
|
|
118
|
+
@all_start_topology_ip_changed,
|
|
119
|
+
@all_start_topology_endpoints_removed,
|
|
120
|
+
@all_topology_changed
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
delay_ms = @status_check_interval_map.fetch(
|
|
125
|
+
@panic_mode.true? && @current_phase != Phase::NOT_CREATED && !@current_phase.nil? ? IntervalRate::HIGH : @interval_rate.get,
|
|
126
|
+
DEFAULT_CHECK_INTERVAL_MS
|
|
127
|
+
)
|
|
128
|
+
delay(delay_ms)
|
|
129
|
+
rescue Interrupt => e
|
|
130
|
+
logger.debug { "[#{@role}] Interrupted." }
|
|
131
|
+
Thread.current.raise(e)
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
logger.warn { "[#{@role}] Unhandled exception while monitoring blue/green status: #{e.class}: #{e.message}" }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
ensure
|
|
137
|
+
@connection_mutex.synchronize { @connection.close }
|
|
138
|
+
@host_list_provider&.stop_monitor if @host_list_provider.respond_to?(:stop_monitor)
|
|
139
|
+
@host_list_provider = nil
|
|
140
|
+
@open_connection_future.set(nil)
|
|
141
|
+
logger.debug { "[#{@role}] Blue/green status monitoring thread is completed." }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def open_connection
|
|
145
|
+
connection = @connection.get
|
|
146
|
+
return unless connection.nil? || connection_closed?(connection)
|
|
147
|
+
|
|
148
|
+
future = @open_connection_future.get
|
|
149
|
+
if future
|
|
150
|
+
if future.resolved?
|
|
151
|
+
return if @panic_mode.false?
|
|
152
|
+
elsif future.pending?
|
|
153
|
+
return
|
|
154
|
+
end
|
|
155
|
+
# future is rejected — fall through to replace it
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
new_future = Concurrent::Promises.future { attempt_open_connection }
|
|
159
|
+
@open_connection_future.compare_and_set(future, new_future)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def resolve_ip_address(host)
|
|
163
|
+
Resolv.getaddress(host)
|
|
164
|
+
rescue Resolv::ResolvError
|
|
165
|
+
nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Attempts to connect to all known instance IPs in parallel and returns the first
|
|
169
|
+
# successful connection.
|
|
170
|
+
def try_connect_on_all_instances(host_info)
|
|
171
|
+
prev_ip = @connected_ip_address.get
|
|
172
|
+
candidate_ips = @start_ip_addresses_by_host_map.values.compact.to_set
|
|
173
|
+
candidate_ips.add(prev_ip) if prev_ip
|
|
174
|
+
|
|
175
|
+
return nil if candidate_ips.empty?
|
|
176
|
+
|
|
177
|
+
logger.debug { "[#{@role}] Opening monitoring connection (IP) to #{candidate_ips.to_a.join(', ')}." }
|
|
178
|
+
connect_to_first_available_ip(candidate_ips, host_info)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def connect_to_first_available_ip(candidate_ips, host_info)
|
|
182
|
+
winner = Concurrent::AtomicReference.new(nil)
|
|
183
|
+
|
|
184
|
+
futures = candidate_ips.map do |ip|
|
|
185
|
+
Concurrent::Promises.future do
|
|
186
|
+
conn = try_connect_single_ip_address(ip, host_info)
|
|
187
|
+
if conn && !winner.compare_and_set(nil, conn)
|
|
188
|
+
begin
|
|
189
|
+
@service_container.dialect_service.driver_dialect.close_connection(conn)
|
|
190
|
+
rescue StandardError
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
Concurrent::Promises.zip(*futures).wait(TERMINATION_TIMEOUT_SEC)
|
|
198
|
+
winner.get
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def try_connect_single_ip_address(ip_address, host_info)
|
|
202
|
+
original_host = host_info.host
|
|
203
|
+
host_info = host_info.deep_dup(host: ip_address)
|
|
204
|
+
|
|
205
|
+
connect_props = @status_monitor_driver_props.dup
|
|
206
|
+
override_props = iam_enabled? ? map_merge(@status_monitor_wrapper_props, PropertyDefinition::IAM_HOST.name => original_host)
|
|
207
|
+
: @status_monitor_wrapper_props
|
|
208
|
+
|
|
209
|
+
logger.debug { "[#{@role}] Opening monitoring connection (IP) to #{ip_address}." }
|
|
210
|
+
|
|
211
|
+
connection = @service_container.plugin_manager.internal_connect(
|
|
212
|
+
host_info, connect_props, override_props, false
|
|
213
|
+
)
|
|
214
|
+
@connected_ip_address.set(ip_address)
|
|
215
|
+
connection
|
|
216
|
+
rescue StandardError
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def delay(delay_ms)
|
|
221
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + delay_ms
|
|
222
|
+
current_interval_rate = @interval_rate.get
|
|
223
|
+
current_panic = @panic_mode.true?
|
|
224
|
+
min_delay = [delay_ms, 50].min / 1000.0
|
|
225
|
+
|
|
226
|
+
loop do
|
|
227
|
+
@event.wait(min_delay)
|
|
228
|
+
break if @interval_rate.get != current_interval_rate ||
|
|
229
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) >= end_time ||
|
|
230
|
+
@stop_flag.true? ||
|
|
231
|
+
current_panic != @panic_mode.true?
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def update_ip_address_flags
|
|
236
|
+
if @collect_topology.true?
|
|
237
|
+
@all_start_topology_ip_changed = false
|
|
238
|
+
@all_start_topology_endpoints_removed = false
|
|
239
|
+
@all_topology_changed = false
|
|
240
|
+
return
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
unless @collect_ip_addresses.true?
|
|
244
|
+
@all_start_topology_ip_changed = @start_topology&.any? &&
|
|
245
|
+
@start_topology.all? do |x|
|
|
246
|
+
start_ip = @start_ip_addresses_by_host_map[x.host]
|
|
247
|
+
current_ip = @current_ip_addresses_by_host_map[x.host]
|
|
248
|
+
start_ip && current_ip && start_ip != current_ip
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
@all_start_topology_endpoints_removed = @start_topology&.any? &&
|
|
253
|
+
@start_topology.all? do |x|
|
|
254
|
+
start_ip = @start_ip_addresses_by_host_map[x.host]
|
|
255
|
+
current_ip = @current_ip_addresses_by_host_map[x.host]
|
|
256
|
+
start_ip && current_ip.nil?
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
start_hosts = @start_topology&.to_set(&:host) || Set.new
|
|
260
|
+
current_topology = @current_topology.get
|
|
261
|
+
@all_topology_changed = current_topology&.any? &&
|
|
262
|
+
start_hosts.any? &&
|
|
263
|
+
current_topology.none? { |x| start_hosts.include?(x.host) }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def collect_host_ip_addresses
|
|
267
|
+
@current_ip_addresses_by_host_map.clear
|
|
268
|
+
|
|
269
|
+
@host_names.each do |host|
|
|
270
|
+
@current_ip_addresses_by_host_map[host] = resolve_ip_address(host)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
@start_ip_addresses_by_host_map.replace(@current_ip_addresses_by_host_map) if @collect_ip_addresses.true?
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def collect_topology
|
|
277
|
+
return if @host_list_provider.nil?
|
|
278
|
+
|
|
279
|
+
@connection_mutex.synchronize do
|
|
280
|
+
conn = @connection.get
|
|
281
|
+
return if conn.nil? || connection_closed?(conn)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
topology = @host_list_provider.force_refresh(false, Host::RdsHostListProvider::DEFAULT_TOPOLOGY_QUERY_TIMEOUT_SEC)
|
|
285
|
+
return logger.debug { 'Timed out while waiting for force_refresh to return new topology info.' } if topology.nil?
|
|
286
|
+
|
|
287
|
+
@current_topology.set(topology)
|
|
288
|
+
|
|
289
|
+
return unless @collect_topology.true?
|
|
290
|
+
|
|
291
|
+
@start_topology = topology
|
|
292
|
+
@host_names.merge(topology.map(&:host))
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def collect_status
|
|
296
|
+
@connection_mutex.synchronize do
|
|
297
|
+
conn = @connection.get
|
|
298
|
+
return if conn.nil? || connection_closed?(conn)
|
|
299
|
+
|
|
300
|
+
return unless status_available?(conn)
|
|
301
|
+
|
|
302
|
+
dialect = @service_container.dialect_service.db_dialect
|
|
303
|
+
status_entries = parse_status_entries(conn, dialect)
|
|
304
|
+
status_info = resolve_status_info(status_entries)
|
|
305
|
+
apply_status_info(status_info, status_entries)
|
|
306
|
+
verify_connection_host(status_info)
|
|
307
|
+
init_host_list_provider if @correct_connection_host.true? && @host_list_provider.nil?
|
|
308
|
+
end
|
|
309
|
+
rescue StandardError => e
|
|
310
|
+
handle_collect_status_error(e, @connection.get)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def collect_ip_addresses=(val)
|
|
314
|
+
val ? @collect_ip_addresses.make_true : @collect_ip_addresses.make_false
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def collect_topology=(val)
|
|
318
|
+
val ? @collect_topology.make_true : @collect_topology.make_false
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def use_ip_address=(val)
|
|
322
|
+
val ? @use_ip_address.make_true : @use_ip_address.make_false
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def interval_rate=(rate)
|
|
326
|
+
@interval_rate.set(rate)
|
|
327
|
+
notify_changes
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def reset_collected_data
|
|
331
|
+
@start_ip_addresses_by_host_map.clear
|
|
332
|
+
@start_topology = nil
|
|
333
|
+
@host_names.clear
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def notify_changes
|
|
337
|
+
@event.set
|
|
338
|
+
@event.reset
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
private
|
|
342
|
+
|
|
343
|
+
def attempt_open_connection
|
|
344
|
+
@connection_mutex.synchronize { @connection.set(nil) }
|
|
345
|
+
@panic_mode.make_true
|
|
346
|
+
|
|
347
|
+
if @connection_host_info.get.nil?
|
|
348
|
+
@connection_host_info.set(@initial_host)
|
|
349
|
+
@connected_ip_address.set(nil)
|
|
350
|
+
@correct_connection_host.make_false
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
host_info = @connection_host_info.get
|
|
354
|
+
connected_ip = @connected_ip_address.get
|
|
355
|
+
|
|
356
|
+
if @use_ip_address.true? && connected_ip
|
|
357
|
+
established = try_connect_on_all_instances(host_info)
|
|
358
|
+
unless established
|
|
359
|
+
clear_connection
|
|
360
|
+
return
|
|
361
|
+
end
|
|
362
|
+
@connection_mutex.synchronize { @connection.set(established) }
|
|
363
|
+
logger.debug { "[#{@role}] Opened monitoring connection (IP) to #{@connected_ip_address.get}." }
|
|
364
|
+
else
|
|
365
|
+
logger.debug { "[#{@role}] Opening monitoring connection to #{host_info.host}." }
|
|
366
|
+
ip = resolve_ip_address(host_info.host)
|
|
367
|
+
connect_props = @status_monitor_driver_props.dup
|
|
368
|
+
override_props = iam_enabled? ? map_merge(@status_monitor_wrapper_props, PropertyDefinition::IAM_HOST.name => host_info.host)
|
|
369
|
+
: @status_monitor_wrapper_props
|
|
370
|
+
@connection_mutex.synchronize do
|
|
371
|
+
@connection.set(@service_container.plugin_manager.internal_connect(
|
|
372
|
+
host_info, connect_props, override_props, false
|
|
373
|
+
))
|
|
374
|
+
end
|
|
375
|
+
@connected_ip_address.set(ip)
|
|
376
|
+
logger.debug { "[#{@role}] Opened monitoring connection to #{host_info.host}." }
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
@panic_mode.make_false
|
|
380
|
+
notify_changes
|
|
381
|
+
rescue StandardError => e
|
|
382
|
+
logger.debug { "[#{@role}] Failed to open monitoring connection: #{e.class}: #{e.message}" }
|
|
383
|
+
clear_connection
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def map_merge(base, overrides)
|
|
387
|
+
result = base.dup
|
|
388
|
+
overrides.each { |k, v| result[k] = v }
|
|
389
|
+
result
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def iam_enabled?
|
|
393
|
+
@service_container.plugin_manager.plugin_in_use?(Plugins::IamAuthPlugin)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def driver_dialect
|
|
397
|
+
@service_container.dialect_service.driver_dialect
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def clear_connection
|
|
401
|
+
@connection_mutex.synchronize { @connection.set(nil) }
|
|
402
|
+
@panic_mode.make_true
|
|
403
|
+
notify_changes
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Lock-free variant of clear_connection. The caller MUST already hold
|
|
407
|
+
# @connection_mutex
|
|
408
|
+
def clear_connection_locked
|
|
409
|
+
@connection.set(nil)
|
|
410
|
+
@panic_mode.make_true
|
|
411
|
+
notify_changes
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def connection_closed?(conn)
|
|
415
|
+
driver_dialect.closed?(conn)
|
|
416
|
+
rescue StandardError
|
|
417
|
+
true
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def syntax_error?(err)
|
|
421
|
+
driver_dialect.sql_state(err)&.start_with?('42') || false
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def db_error?(err)
|
|
425
|
+
!driver_dialect.sql_state(err).nil?
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# @note Callers must hold @connection_mutex (invoke within a @connection_mutex.synchronize block).
|
|
429
|
+
def status_available?(conn)
|
|
430
|
+
dialect = @service_container.dialect_service.db_dialect
|
|
431
|
+
return true if dialect.blue_green_status_available?(conn)
|
|
432
|
+
|
|
433
|
+
if connection_closed?(conn)
|
|
434
|
+
clear_connection_locked
|
|
435
|
+
else
|
|
436
|
+
@current_phase = Phase::NOT_CREATED
|
|
437
|
+
logger.debug { "[#{@role}] (status not available) current_phase: #{@current_phase}" }
|
|
438
|
+
end
|
|
439
|
+
false
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def parse_status_entries(conn, dialect)
|
|
443
|
+
driver_dialect.execute(conn, dialect.blue_green_status_query).each_with_object([]) do |row, entries|
|
|
444
|
+
version = row['version']
|
|
445
|
+
unless KNOWN_VERSIONS.include?(version)
|
|
446
|
+
logger.warn do
|
|
447
|
+
"[#{@role}] Blue/Green deployment uses version '#{version}' which the driver does not " \
|
|
448
|
+
"support. Version '#{LATEST_KNOWN_VERSION}' will be used instead."
|
|
449
|
+
end
|
|
450
|
+
version = LATEST_KNOWN_VERSION
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
role = Role.parse_role(row['role'], version)
|
|
454
|
+
next if role != @role
|
|
455
|
+
|
|
456
|
+
entries << StatusInfo.new(version, row['endpoint'], row['port'].to_i, Phase.parse_phase(row['status']), role)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def resolve_status_info(status_entries)
|
|
461
|
+
info = status_entries.find { |x| Utils::RdsUtils.writer_cluster_dns?(x.endpoint) && Utils::RdsUtils.not_old_instance?(x.endpoint) }
|
|
462
|
+
@host_names.add(info.endpoint.downcase.gsub('.cluster-', '.cluster-ro-')) if info
|
|
463
|
+
info || status_entries.find { |x| Utils::RdsUtils.rds_instance?(x.endpoint) && Utils::RdsUtils.not_old_instance?(x.endpoint) }
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def apply_status_info(status_info, status_entries)
|
|
467
|
+
if status_info.nil?
|
|
468
|
+
if status_entries.empty?
|
|
469
|
+
@current_phase = nil
|
|
470
|
+
logger.debug { "[#{@role}] No entries in status table." } if @role != Role::SOURCE
|
|
471
|
+
end
|
|
472
|
+
else
|
|
473
|
+
@current_phase = status_info.phase
|
|
474
|
+
@version = status_info.version
|
|
475
|
+
@port = status_info.port
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
return unless @collect_topology.true?
|
|
479
|
+
|
|
480
|
+
@host_names.merge(
|
|
481
|
+
status_entries.filter_map { |x| x.endpoint&.downcase if Utils::RdsUtils.not_old_instance?(x.endpoint) }
|
|
482
|
+
)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Compares the connected IP against the endpoint IP from the status table.
|
|
486
|
+
# Reconnects to the correct host if a mismatch is detected.
|
|
487
|
+
# @note Callers must hold @connection_mutex (invoke within a @connection_mutex.synchronize block).
|
|
488
|
+
def verify_connection_host(status_info)
|
|
489
|
+
return if @correct_connection_host.true? || status_info.nil?
|
|
490
|
+
|
|
491
|
+
status_ip = resolve_ip_address(status_info.endpoint)
|
|
492
|
+
connected_ip = @connected_ip_address.get
|
|
493
|
+
|
|
494
|
+
if connected_ip && connected_ip != status_ip
|
|
495
|
+
@connection_host_info.set(
|
|
496
|
+
@connection_host_info.get.deep_dup(host: status_info.endpoint, port: status_info.port)
|
|
497
|
+
)
|
|
498
|
+
@correct_connection_host.make_true
|
|
499
|
+
clear_connection_locked
|
|
500
|
+
else
|
|
501
|
+
@correct_connection_host.make_true
|
|
502
|
+
@panic_mode.make_false
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def handle_collect_status_error(err, conn)
|
|
507
|
+
if syntax_error?(err)
|
|
508
|
+
@current_phase = Phase::NOT_CREATED
|
|
509
|
+
logger.warn { "[#{@role}] current_phase: #{@current_phase}, exception while querying for blue/green status." }
|
|
510
|
+
elsif db_error?(err)
|
|
511
|
+
if err.message.include?('An error occurred while retrieving the blue/green fast switchover metadata')
|
|
512
|
+
@current_phase = Phase::NOT_CREATED
|
|
513
|
+
return
|
|
514
|
+
end
|
|
515
|
+
# If already in NOT_CREATED phase, this is the expected steady state after switchover
|
|
516
|
+
# (status table/function is gone). Don't enter panic mode — stay at BASELINE rate.
|
|
517
|
+
if @current_phase.nil? || @current_phase == Phase::NOT_CREATED
|
|
518
|
+
logger.debug { "[#{@role}] (status not available) current_phase: #{@current_phase}" }
|
|
519
|
+
return
|
|
520
|
+
end
|
|
521
|
+
logger.debug { "[#{@role}] Unhandled SQLException: #{err.message}" }
|
|
522
|
+
clear_connection unless connection_closed?(conn)
|
|
523
|
+
else
|
|
524
|
+
logger.debug { "[#{@role}] Unhandled exception: #{err.class}: #{err.message}" }
|
|
525
|
+
clear_connection
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Creates a scoped HostListProvider for this monitor using a unique cluster_id
|
|
530
|
+
# to avoid interfering with the application's own provider.
|
|
531
|
+
def init_host_list_provider
|
|
532
|
+
return if @host_list_provider || @correct_connection_host.false?
|
|
533
|
+
|
|
534
|
+
host_info = @connection_host_info.get
|
|
535
|
+
return logger.warn { 'Unable to initialize HostListProvider since connection host information is null.' } if host_info.nil?
|
|
536
|
+
|
|
537
|
+
cluster_id = "#{@bgd_id}::#{@role}::#{BG_CLUSTER}"
|
|
538
|
+
logger.debug { "[#{@role}] Creating a new HostListProvider, cluster_id: #{cluster_id}." }
|
|
539
|
+
|
|
540
|
+
scoped_wrapper_props = map_merge(@status_monitor_wrapper_props, PropertyDefinition::CLUSTER_ID.name => cluster_id)
|
|
541
|
+
scoped_wrapper_props = map_merge(scoped_wrapper_props, PropertyDefinition::IAM_HOST.name => host_info.host) if iam_enabled?
|
|
542
|
+
|
|
543
|
+
config = @service_container.connection_service.config.dup
|
|
544
|
+
config.initial_host_info = host_info
|
|
545
|
+
config.wrapper_props = scoped_wrapper_props
|
|
546
|
+
|
|
547
|
+
scoped_connection_service = Services::ConnectionService.new(@service_container, config)
|
|
548
|
+
scoped_container = Services::ServiceContainer.new(
|
|
549
|
+
scoped_connection_service,
|
|
550
|
+
@service_container.dialect_service,
|
|
551
|
+
@service_container.event_publisher,
|
|
552
|
+
@service_container.host_service,
|
|
553
|
+
@service_container.plugin_manager,
|
|
554
|
+
@service_container.session_state_service,
|
|
555
|
+
@service_container.storage_service,
|
|
556
|
+
@service_container.monitor_service
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
@host_list_provider = @service_container.dialect_service.db_dialect.create_host_list_provider(scoped_container)
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|