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.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +175 -0
  4. data/NOTICE +1 -0
  5. data/README.md +168 -0
  6. data/THIRD-PARTY-LICENSES +473 -0
  7. data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
  8. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
  9. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
  10. data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
  11. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
  12. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
  13. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
  14. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
  15. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
  16. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
  17. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
  18. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
  19. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
  20. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
  21. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
  22. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
  23. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
  24. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
  25. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
  26. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
  27. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
  28. data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
  29. data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
  30. data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
  31. data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
  32. data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
  33. data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
  34. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
  35. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
  36. data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
  37. data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
  38. data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
  39. data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
  40. data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
  41. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
  42. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
  43. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
  44. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
  45. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
  46. data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
  47. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
  48. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
  49. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
  50. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
  51. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
  52. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
  53. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
  54. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
  55. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
  56. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
  57. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
  58. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
  59. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
  60. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
  61. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
  62. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
  63. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
  64. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
  65. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
  66. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
  67. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
  68. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
  69. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
  70. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
  71. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
  72. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
  73. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
  74. data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
  75. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
  76. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
  77. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
  78. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
  79. data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
  80. data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
  81. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
  82. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
  83. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
  84. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
  85. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
  86. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
  87. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
  88. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
  89. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
  90. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
  91. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
  92. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
  93. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
  94. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
  95. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
  96. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
  97. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
  98. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
  99. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
  100. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
  101. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
  102. data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
  103. data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
  104. data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
  105. data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
  106. data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
  107. data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
  108. data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
  109. data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
  110. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
  111. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
  112. data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
  113. data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
  114. data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
  115. data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
  116. data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
  117. data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
  118. data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
  119. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
  120. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
  121. data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
  122. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
  123. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
  124. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
  125. data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
  126. data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
  127. data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
  128. data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
  129. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
  130. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
  131. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
  132. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
  133. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
  134. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
  135. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
  136. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
  137. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
  138. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
  139. data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
  140. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
  141. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
  142. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
  143. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
  144. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
  145. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
  146. data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
  147. data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
  148. data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
  149. data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
  150. metadata +227 -0
@@ -0,0 +1,205 @@
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 '../../logging'
19
+ require_relative '../../ruby_method'
20
+ require_relative '../../property_definition'
21
+ require_relative '../../utils/rds_utils'
22
+ require_relative 'phase'
23
+ require_relative 'role'
24
+ require_relative 'status'
25
+ require_relative 'status_provider'
26
+
27
+ module AwsAdvancedRubyDriverWrapper
28
+ module Plugins
29
+ module BlueGreen
30
+ class BlueGreenPlugin
31
+ include Logging
32
+
33
+ BLUE_GREEN_NAME = :blue_green
34
+
35
+ # When present in wrapper_props, internal_connect calls bypass BG routing.
36
+ # Used by BG monitoring connections to avoid being intercepted by their own plugin.
37
+ BG_SKIP_ROUTING_KEY = :'bg.skip_routing'
38
+
39
+ CLOSING_METHODS = Set[
40
+ RubyMethod::CONNECTION_CLOSE.name,
41
+ RubyMethod::STATEMENT_CLOSE.name
42
+ ].freeze
43
+
44
+ PROVIDERS = Concurrent::Map.new
45
+
46
+ def initialize(service_container, props = ::Concurrent::Map.new)
47
+ @service_container = service_container
48
+ @wrapper_props = props
49
+ @bgd_id = PropertyDefinition::BGD_ID.get(props).to_s.strip.downcase
50
+ @bg_status = nil
51
+ @cluster_id = nil
52
+ @start_time_ns = Concurrent::AtomicReference.new(0)
53
+ @end_time_ns = Concurrent::AtomicReference.new(0)
54
+
55
+ service_container.storage_service.register(BLUE_GREEN_NAME, ttl: 3600)
56
+
57
+ network_methods = service_container.dialect_service.driver_dialect.network_bound_methods
58
+ @subscribed_methods = (Set['connect', 'internal_connect'] | network_methods).freeze
59
+ end
60
+
61
+ attr_reader :subscribed_methods
62
+
63
+ def connect(host_info, driver_props, is_initial_connection, pipeline_callable)
64
+ route_connect(host_info, driver_props, is_initial_connection, pipeline_callable, is_internal: false)
65
+ end
66
+
67
+ def internal_connect(host_info, driver_props, wrapper_props, is_initial_connection, pipeline_callable)
68
+ # BG monitoring connections set BG_SKIP_ROUTING_KEY to bypass routing.
69
+ return pipeline_callable.call if wrapper_props[BG_SKIP_ROUTING_KEY]
70
+
71
+ route_connect(host_info, driver_props, is_initial_connection, pipeline_callable, is_internal: true)
72
+ end
73
+
74
+ def execute(method_name, pipeline_callable, *_, **, &)
75
+ reset_routing_time_ns
76
+
77
+ begin
78
+ init_provider
79
+
80
+ return pipeline_callable.call if CLOSING_METHODS.include?(method_name)
81
+
82
+ @bg_status = storage_service.get(BLUE_GREEN_NAME, @bgd_id)
83
+ if @bg_status.nil? || @bg_status.current_phase == Phase::NOT_CREATED || @bg_status.current_phase == Phase::COMPLETED
84
+ return pipeline_callable.call
85
+ end
86
+
87
+ current_host = @service_container.connection_service.current_host_info
88
+ host_role = @bg_status.role(current_host)
89
+ return pipeline_callable.call if host_role.nil?
90
+
91
+ routing = @bg_status.execute_routing.find { |r| r.match?(current_host, host_role) }
92
+ return pipeline_callable.call if routing.nil?
93
+
94
+ @start_time_ns.set(nano_time)
95
+ result = nil
96
+
97
+ while routing && result.nil?
98
+ result = routing.apply(method_name, @wrapper_props, storage_service)
99
+
100
+ next unless result.nil?
101
+
102
+ @bg_status = storage_service.get(BLUE_GREEN_NAME, @bgd_id)
103
+ if @bg_status.nil?
104
+ @end_time_ns.set(nano_time)
105
+ return pipeline_callable.call
106
+ end
107
+ routing = @bg_status.execute_routing.find { |r| r.match?(current_host, host_role) }
108
+ end
109
+
110
+ @end_time_ns.set(nano_time)
111
+ result.nil? ? pipeline_callable.call : result
112
+ ensure
113
+ @end_time_ns.set(nano_time) if @start_time_ns.get.positive? && @end_time_ns.get.zero?
114
+ end
115
+ end
116
+
117
+ def hold_time_ns
118
+ return 0 if @start_time_ns.get.zero?
119
+
120
+ end_ns = @end_time_ns.get
121
+ end_ns.zero? ? (nano_time - @start_time_ns.get) : (end_ns - @start_time_ns.get)
122
+ end
123
+
124
+ def reset_routing_time_ns
125
+ @start_time_ns.set(0)
126
+ @end_time_ns.set(0)
127
+ end
128
+
129
+ def self.clean_up_providers
130
+ PROVIDERS.each_key { |k| PROVIDERS.delete(k)&.stop }
131
+ end
132
+
133
+ private
134
+
135
+ def route_connect(host_info, driver_props, is_initial_connection, pipeline_callable, is_internal: false)
136
+ reset_routing_time_ns
137
+
138
+ begin
139
+ @bg_status = storage_service.get(BLUE_GREEN_NAME, @bgd_id)
140
+ return regular_connect(pipeline_callable, is_initial_connection) if @bg_status.nil?
141
+
142
+ host_role = @bg_status.role(host_info)
143
+ return regular_connect(pipeline_callable, is_initial_connection) if host_role.nil?
144
+
145
+ routing = @bg_status.connect_routing.find { |r| r.match?(host_info, host_role) }
146
+ return regular_connect(pipeline_callable, is_initial_connection) if routing.nil?
147
+
148
+ @start_time_ns.set(nano_time)
149
+ conn = nil
150
+
151
+ while routing && conn.nil?
152
+ # Pass self so a routing that opens a substitute connection can skip this plugin in the
153
+ # nested pipeline, preventing the connect path from re-entering BG routing (infinite recursion).
154
+ conn = routing.apply(host_info, driver_props, @wrapper_props, is_initial_connection, @service_container,
155
+ is_internal: is_internal, bg_plugin: self)
156
+
157
+ next unless conn.nil?
158
+
159
+ # Routing passed — re-read status in case it changed while we were waiting.
160
+ @bg_status = storage_service.get(BLUE_GREEN_NAME, @bgd_id)
161
+ if @bg_status.nil?
162
+ @end_time_ns.set(nano_time)
163
+ return regular_connect(pipeline_callable, is_initial_connection)
164
+ end
165
+ routing = @bg_status.connect_routing.find { |r| r.match?(host_info, host_role) }
166
+ end
167
+
168
+ @end_time_ns.set(nano_time)
169
+ conn ||= pipeline_callable.call
170
+
171
+ init_provider if is_initial_connection
172
+ conn
173
+ ensure
174
+ @end_time_ns.set(nano_time) if @start_time_ns.get.positive? && @end_time_ns.get.zero?
175
+ end
176
+ end
177
+
178
+ def regular_connect(pipeline_callable, is_initial_connection)
179
+ conn = pipeline_callable.call
180
+ init_provider if is_initial_connection
181
+ conn
182
+ end
183
+
184
+ def init_provider
185
+ return if @cluster_id && PROVIDERS.key?(@bgd_id)
186
+
187
+ host_list_provider = @service_container.host_service.host_list_provider
188
+ @cluster_id = host_list_provider.respond_to?(:cluster_id) ? host_list_provider.cluster_id :
189
+ @service_container.connection_service.config.cluster_id
190
+ PROVIDERS.compute_if_absent(@bgd_id) do
191
+ StatusProvider.new(@service_container, @wrapper_props, @bgd_id, @cluster_id)
192
+ end
193
+ end
194
+
195
+ def storage_service
196
+ @service_container.storage_service
197
+ end
198
+
199
+ def nano_time
200
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,132 @@
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 '../../host/host_info'
19
+ require_relative '../../host/host_role'
20
+ require_relative '../../utils/rds_utils'
21
+ require_relative 'role'
22
+
23
+ module AwsAdvancedRubyDriverWrapper
24
+ module Plugins
25
+ module BlueGreen
26
+ class HostMapper
27
+ attr_reader :corresponding_hosts, :role_by_host, :host_ip_addresses
28
+
29
+ def initialize
30
+ @corresponding_hosts = Concurrent::Hash.new
31
+ @role_by_host = Concurrent::Hash.new
32
+ @host_ip_addresses = Concurrent::Hash.new
33
+ end
34
+
35
+ def update(source_status, target_status)
36
+ @corresponding_hosts.clear
37
+
38
+ build_instance_pairs(source_status, target_status)
39
+ build_cluster_pairs(source_status, target_status)
40
+ end
41
+
42
+ def merge_ips(ip_map)
43
+ @host_ip_addresses.merge!(ip_map)
44
+ end
45
+
46
+ def register_role(host_names, role)
47
+ host_names.each { |x| @role_by_host[x.downcase] = role }
48
+ end
49
+
50
+ def clear
51
+ @corresponding_hosts.clear
52
+ @role_by_host.clear
53
+ @host_ip_addresses.clear
54
+ end
55
+
56
+ def to_debug_s
57
+ @corresponding_hosts.map { |k, v| " #{k} -> #{v[1]&.host_and_port || '<null>'}" }.join("\n")
58
+ end
59
+
60
+ private
61
+
62
+ def build_instance_pairs(source_status, target_status)
63
+ return unless source_status&.start_topology&.any? && target_status&.start_topology&.any?
64
+
65
+ blue_writer = writer_host(source_status)
66
+ green_writer = writer_host(target_status)
67
+ blue_readers = reader_hosts(source_status)
68
+ green_readers = reader_hosts(target_status)
69
+
70
+ @corresponding_hosts[blue_writer.host] = [blue_writer, green_writer] if blue_writer
71
+
72
+ return unless blue_readers&.any?
73
+
74
+ if green_readers&.any?
75
+ blue_readers.each_with_index do |blue, i|
76
+ @corresponding_hosts[blue.host] = [blue, green_readers[i % green_readers.size]]
77
+ end
78
+ else
79
+ blue_readers.each { |blue| @corresponding_hosts[blue.host] = [blue, green_writer] }
80
+ end
81
+ end
82
+
83
+ def build_cluster_pairs(source_status, target_status)
84
+ return unless source_status&.host_names&.any? && target_status&.host_names&.any?
85
+
86
+ blue_hosts = source_status.host_names
87
+ green_hosts = target_status.host_names
88
+
89
+ pair_by_dns_type(blue_hosts, green_hosts, :writer_cluster_dns?)
90
+ pair_by_dns_type(blue_hosts, green_hosts, :reader_cluster_dns?)
91
+ pair_custom_clusters(blue_hosts, green_hosts)
92
+ end
93
+
94
+ def pair_by_dns_type(blue_hosts, green_hosts, dns_method)
95
+ blue = blue_hosts.find { |h| Utils::RdsUtils.public_send(dns_method, h) }
96
+ green = green_hosts.find { |h| Utils::RdsUtils.public_send(dns_method, h) }
97
+ return unless blue && green
98
+
99
+ @corresponding_hosts[blue] ||= [Host::HostInfo.new(host: blue), Host::HostInfo.new(host: green)]
100
+ end
101
+
102
+ def pair_custom_clusters(blue_hosts, green_hosts)
103
+ blue_hosts.select { |h| Utils::RdsUtils.rds_custom_cluster_dns?(h) }.each do |blue_host|
104
+ custom_name = Utils::RdsUtils.rds_cluster_id(blue_host)
105
+ next unless custom_name
106
+
107
+ green_host = green_hosts.find do |x|
108
+ Utils::RdsUtils.rds_custom_cluster_dns?(x) &&
109
+ custom_name == Utils::RdsUtils.remove_green_instance_prefix(Utils::RdsUtils.rds_cluster_id(x))
110
+ end
111
+ next unless green_host
112
+
113
+ @corresponding_hosts[blue_host] ||= [
114
+ Host::HostInfo.new(host: blue_host),
115
+ Host::HostInfo.new(host: green_host)
116
+ ]
117
+ end
118
+ end
119
+
120
+ def writer_host(interim_status)
121
+ interim_status.start_topology&.find { |x| x.role == Host::HostRole::WRITER }
122
+ end
123
+
124
+ def reader_hosts(interim_status)
125
+ interim_status.start_topology
126
+ &.reject { |x| x.role == Host::HostRole::WRITER }
127
+ &.sort_by(&:host)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,84 @@
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 '../../logging'
19
+
20
+ module AwsAdvancedRubyDriverWrapper
21
+ module Plugins
22
+ module BlueGreen
23
+ # Tracks which green host have successfully connected using a non-prefixed (blue) IAM hostname,
24
+ # indicating the host has completed its DNS rename.
25
+ class IamHostTracker
26
+ include Logging
27
+
28
+ attr_reader :green_host_change_name_times
29
+
30
+ def initialize(on_all_changed:)
31
+ @successful_connects = Concurrent::Hash.new
32
+ @green_host_change_name_times = Concurrent::Hash.new
33
+ @all_changed = Concurrent::AtomicBoolean.new(false)
34
+ @on_all_changed = on_all_changed
35
+ end
36
+
37
+ def register(connect_host, iam_host)
38
+ names_differ = connect_host && connect_host != iam_host
39
+
40
+ if names_differ && !connected?(connect_host, iam_host)
41
+ @green_host_change_name_times[connect_host] ||= Time.now
42
+ logger.debug { "Green host '#{connect_host}' has changed its name to '#{iam_host}'." }
43
+ end
44
+
45
+ (@successful_connects[connect_host] ||= Set.new).add(iam_host)
46
+
47
+ return unless names_differ
48
+
49
+ all_changed = @successful_connects
50
+ .reject { |_, v| v.empty? }
51
+ .all? { |k, v| v.any? { |y| k != y } }
52
+
53
+ return unless all_changed && !@all_changed.true?
54
+
55
+ logger.debug { 'All green hosts changed' }
56
+ @all_changed.make_true
57
+ @on_all_changed&.call
58
+ end
59
+
60
+ def connected?(connect_host, iam_host)
61
+ @successful_connects[connect_host]&.include?(iam_host) || false
62
+ end
63
+
64
+ def all_changed?
65
+ @all_changed.true?
66
+ end
67
+
68
+ def size
69
+ @successful_connects.size
70
+ end
71
+
72
+ def clear
73
+ @green_host_change_name_times.clear
74
+ @all_changed.make_false
75
+ @successful_connects.clear
76
+ end
77
+
78
+ def to_debug_s
79
+ @green_host_change_name_times.map { |k, v| " #{k} -> #{v}" }.join("\n")
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,92 @@
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 BlueGreen
20
+ InterimStatus = Struct.new(
21
+ :blue_green_phase,
22
+ :version,
23
+ :port,
24
+ :start_topology,
25
+ :current_topology,
26
+ :start_ip_addresses_by_host,
27
+ :current_ip_addresses_by_host,
28
+ :host_names,
29
+ :all_start_topology_ip_changed,
30
+ :all_start_topology_endpoints_removed,
31
+ :all_topology_changed
32
+ ) do
33
+ def to_s
34
+ start_ip_map = format_map(start_ip_addresses_by_host)
35
+ current_ip_map = format_map(current_ip_addresses_by_host)
36
+ host_names_str = host_names&.join("\n ")
37
+ start_topo_str = log_topology(start_topology)
38
+ current_topo_str = log_topology(current_topology)
39
+
40
+ <<~STATUS
41
+ InterimStatus [
42
+ phase #{blue_green_phase || '<null>'},
43
+ version '#{version}',
44
+ port #{port},
45
+ host_names:
46
+ #{blank?(host_names_str) ? '-' : host_names_str}
47
+ Start #{blank?(start_topo_str) ? '-' : start_topo_str}
48
+ Start IP map:
49
+ #{blank?(start_ip_map) ? '-' : start_ip_map}
50
+ Current #{blank?(current_topo_str) ? '-' : current_topo_str}
51
+ Current IP map:
52
+ #{blank?(current_ip_map) ? '-' : current_ip_map}
53
+ all_start_topology_ip_changed: #{all_start_topology_ip_changed}
54
+ all_start_topology_endpoints_removed: #{all_start_topology_endpoints_removed}
55
+ all_topology_changed: #{all_topology_changed}
56
+ ]
57
+ STATUS
58
+ end
59
+
60
+ def hash
61
+ [blue_green_phase, version, port, all_start_topology_ip_changed,
62
+ all_start_topology_endpoints_removed, all_topology_changed,
63
+ host_names&.sort, topology_hash_str(start_topology),
64
+ topology_hash_str(current_topology), ip_map_hash_str(start_ip_addresses_by_host),
65
+ ip_map_hash_str(current_ip_addresses_by_host)].hash
66
+ end
67
+
68
+ private
69
+
70
+ def format_map(map)
71
+ map&.map { |k, v| "#{k} -> #{v}" }&.join("\n ").to_s
72
+ end
73
+
74
+ def log_topology(topology)
75
+ topology&.map { |h| "#{h.host_and_port} #{h.role}" }&.join(', ').to_s
76
+ end
77
+
78
+ def topology_hash_str(topology)
79
+ topology&.map { |h| "#{h.host_and_port}#{h.role}" }&.sort&.join(',').to_s
80
+ end
81
+
82
+ def ip_map_hash_str(map)
83
+ map&.map { |k, v| "#{k}#{v}" }&.sort&.join(',').to_s
84
+ end
85
+
86
+ def blank?(str)
87
+ str.nil? || str.empty?
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,27 @@
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 BlueGreen
20
+ module IntervalRate
21
+ BASELINE = :baseline
22
+ INCREASED = :increased
23
+ HIGH = :high
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,69 @@
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 BlueGreen
20
+ class Phase
21
+ include Comparable
22
+
23
+ attr_reader :value, :name
24
+
25
+ def initialize(value, active_switchover_or_completed, name)
26
+ @value = value
27
+ @active_switchover_or_completed = active_switchover_or_completed
28
+ @name = name
29
+ end
30
+ private_class_method :new
31
+
32
+ NOT_CREATED = new(0, false, 'NOT_CREATED')
33
+ CREATED = new(1, false, 'CREATED')
34
+ PREPARATION = new(2, true, 'PREPARATION')
35
+ IN_PROGRESS = new(3, true, 'IN_PROGRESS')
36
+ POST = new(4, true, 'POST')
37
+ COMPLETED = new(5, true, 'COMPLETED')
38
+
39
+ STATUS_MAPPING = {
40
+ 'AVAILABLE' => CREATED,
41
+ 'SWITCHOVER_INITIATED' => PREPARATION,
42
+ 'SWITCHOVER_IN_PROGRESS' => IN_PROGRESS,
43
+ 'SWITCHOVER_IN_POST_PROCESSING' => POST,
44
+ 'SWITCHOVER_COMPLETED' => COMPLETED
45
+ }.freeze
46
+
47
+ def self.parse_phase(value)
48
+ return NOT_CREATED if value.nil? || value.empty?
49
+
50
+ STATUS_MAPPING.fetch(value.upcase) do
51
+ raise ArgumentError, "Unknown Blue/Green status: #{value}"
52
+ end
53
+ end
54
+
55
+ def active_switchover_or_completed?
56
+ @active_switchover_or_completed
57
+ end
58
+
59
+ def to_s
60
+ @name
61
+ end
62
+
63
+ def <=>(other)
64
+ value <=> other.value
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,85 @@
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 'phase'
19
+ require_relative 'phase_time_info'
20
+
21
+ module AwsAdvancedRubyDriverWrapper
22
+ module Plugins
23
+ module BlueGreen
24
+ # Ordered log of timestamped events during a switchover (phase transitions, DNS changes,
25
+ # monitor resets). Used to produce the final switchover summary printed at completion.
26
+ class PhaseEventLog
27
+ def initialize
28
+ @entries = Concurrent::Hash.new
29
+ end
30
+
31
+ # Records an event with the current wall-clock and monotonic timestamps.
32
+ # Only the first occurrence of each label is kept (idempotent).
33
+ def record(label, rollback_suffix, phase: nil)
34
+ @entries["#{label}#{rollback_suffix}"] ||= PhaseTimeInfo.new(Time.now, nano_time, phase)
35
+ end
36
+
37
+ def clear
38
+ @entries.clear
39
+ end
40
+
41
+ def any?(&)
42
+ @entries.any?(&)
43
+ end
44
+
45
+ # Formats a human-readable switchover summary table with timestamps and ms offsets
46
+ # relative to the IN_PROGRESS (or PREPARATION for rollback) phase start time.
47
+ def summary(bgd_id, rollback)
48
+ left_pad = 5
49
+ default_field = 31
50
+ max_name_len = @entries.keys.map { |k| k.length + left_pad }.max || default_field
51
+
52
+ time_zero_phase = rollback ? Phase::PREPARATION : Phase::IN_PROGRESS
53
+ suffix = rollback ? ' (rollback)' : ''
54
+ time_zero = @entries["#{time_zero_phase}#{suffix}"] || (rollback && @entries[time_zero_phase.to_s])
55
+
56
+ divider = "---------------------------------------------------#{'-' * max_name_len}\n"
57
+ status_label = rollback ? 'ROLLED BACK' : 'COMPLETED'
58
+
59
+ header = "#{'timestamp'.ljust(28)} #{'time offset (ms)'.rjust(21)} #{'event'.rjust(max_name_len)}"
60
+
61
+ rows = @entries
62
+ .sort_by { |_, v| v.timestamp_nano }
63
+ .map do |k, v|
64
+ offset = time_zero ? ((v.timestamp_nano - time_zero.timestamp_nano) / 1_000_000) : ''
65
+ "#{v.timestamp.to_s.rjust(28)} #{offset.to_s.rjust(18)} ms #{k.rjust(max_name_len)}"
66
+ end
67
+ .join("\n")
68
+
69
+ "[bgd_id: '#{bgd_id}'] Blue/Green Deployment Switchover #{status_label}\n" \
70
+ "#{divider}#{header}\n#{divider}#{rows}\n#{divider}"
71
+ end
72
+
73
+ def to_debug_s
74
+ @entries.map { |k, v| " #{k} -> #{v.timestamp}" }.join("\n")
75
+ end
76
+
77
+ private
78
+
79
+ def nano_time
80
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end