aws-advanced-python-wrapper 1.1.0__tar.gz → 1.2.0__tar.gz
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.
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/PKG-INFO +4 -2
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/README.md +3 -1
- aws_advanced_python_wrapper-1.2.0/aws_advanced_python_wrapper/allowed_and_blocked_hosts.py +29 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/aurora_connection_tracker_plugin.py +6 -7
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/aurora_initial_connection_strategy_plugin.py +3 -3
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/aws_secrets_manager_plugin.py +16 -15
- aws_advanced_python_wrapper-1.2.0/aws_advanced_python_wrapper/custom_endpoint_plugin.py +344 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/driver_dialect_codes.py +0 -1
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/driver_info.py +1 -1
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/failover_plugin.py +13 -6
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/fastest_response_strategy_plugin.py +3 -1
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/federated_plugin.py +7 -1
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/host_list_provider.py +6 -3
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/iam_plugin.py +8 -2
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/mysql_driver_dialect.py +12 -2
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/okta_plugin.py +7 -1
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/plugin_service.py +52 -10
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/read_write_splitting_plugin.py +5 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/reader_failover_handler.py +1 -1
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/resources/aws_advanced_python_wrapper_messages.properties +21 -3
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/sql_alchemy_connection_provider.py +4 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/stale_dns_plugin.py +12 -2
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/iam_utils.py +0 -19
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/mysql_exception_handler.py +2 -2
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/properties.py +22 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/rdsutils.py +137 -74
- aws_advanced_python_wrapper-1.2.0/aws_advanced_python_wrapper/utils/region_utils.py +58 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/sliding_expiration_cache.py +20 -10
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/writer_failover_handler.py +2 -2
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/pyproject.toml +1 -1
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/CONTRIBUTING.md +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/LICENSE +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/__init__.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/connect_time_plugin.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/connection_provider.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/credentials_provider_factory.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/database_dialect.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/default_plugin.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/developer_plugin.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/driver_configuration_profiles.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/driver_dialect.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/driver_dialect_manager.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/errors.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/exception_handling.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/execute_time_plugin.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/failover_result.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/host_availability.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/host_monitoring_plugin.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/host_selector.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/hostinfo.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/pep249.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/pg_driver_dialect.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/plugin.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/sqlalchemy_driver_dialect.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/states/__init__.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/states/session_state.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/states/session_state_service.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/__init__.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/atomic.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/cache_map.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/concurrent.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/decorators.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/failover_mode.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/log.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/messages.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/notifications.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/pg_exception_handler.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/rds_url_type.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/saml_utils.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/telemetry/default_telemetry_factory.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/telemetry/null_telemetry.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/telemetry/open_telemetry.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/telemetry/telemetry.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/telemetry/xray_telemetry.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/utils/utils.py +0 -0
- {aws_advanced_python_wrapper-1.1.0 → aws_advanced_python_wrapper-1.2.0}/aws_advanced_python_wrapper/wrapper.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aws-advanced-python-wrapper
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Amazon Web Services (AWS) Advanced Python Wrapper
|
|
5
5
|
Home-page: https://github.com/awslabs/aws-advanced-python-wrapper
|
|
6
6
|
License: Apache-2.0
|
|
@@ -257,12 +257,14 @@ This `aws-advanced-python-wrapper` is being tested against the following Communi
|
|
|
257
257
|
|
|
258
258
|
| Database | Versions |
|
|
259
259
|
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
260
|
-
| MySQL | 8.
|
|
260
|
+
| MySQL | 8.4.0 |
|
|
261
261
|
| PostgreSQL | 16.2 |
|
|
262
262
|
| Aurora MySQL | - LTS version, see [here](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Updates.Versions.html#AuroraMySQL.Updates.LTS) for more details. <br><br> - Latest release, as shown on [this page](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.30Updates.html). |
|
|
263
263
|
| Aurora PostgreSQL | - LTS version, see [here](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.Updates.LTS.html) for more details. <br><br> - Latest release, as shown on [this page](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraPostgreSQLReleaseNotes/AuroraPostgreSQL.Updates.html).) |
|
|
264
264
|
|
|
265
265
|
The `aws-advanced-python-wrapper` is compatible with MySQL 5.7 and MySQL 8.0 as per MySQL Connector/Python.
|
|
266
|
+
> [!WARNING]\
|
|
267
|
+
> Due to recent internal changes with the `v9.0.0` MySQL Connector/Python driver in regards to connection handling, the AWS Advanced Python Wrapper is not recommended for usage with `v9.0.0`. The AWS Advanced Python Wrapper will be updated in the future for `v9.0.0` compatibility with the community driver.
|
|
266
268
|
|
|
267
269
|
## License
|
|
268
270
|
|
|
@@ -222,12 +222,14 @@ This `aws-advanced-python-wrapper` is being tested against the following Communi
|
|
|
222
222
|
|
|
223
223
|
| Database | Versions |
|
|
224
224
|
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
225
|
-
| MySQL | 8.
|
|
225
|
+
| MySQL | 8.4.0 |
|
|
226
226
|
| PostgreSQL | 16.2 |
|
|
227
227
|
| Aurora MySQL | - LTS version, see [here](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Updates.Versions.html#AuroraMySQL.Updates.LTS) for more details. <br><br> - Latest release, as shown on [this page](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.30Updates.html). |
|
|
228
228
|
| Aurora PostgreSQL | - LTS version, see [here](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.Updates.LTS.html) for more details. <br><br> - Latest release, as shown on [this page](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraPostgreSQLReleaseNotes/AuroraPostgreSQL.Updates.html).) |
|
|
229
229
|
|
|
230
230
|
The `aws-advanced-python-wrapper` is compatible with MySQL 5.7 and MySQL 8.0 as per MySQL Connector/Python.
|
|
231
|
+
> [!WARNING]\
|
|
232
|
+
> Due to recent internal changes with the `v9.0.0` MySQL Connector/Python driver in regards to connection handling, the AWS Advanced Python Wrapper is not recommended for usage with `v9.0.0`. The AWS Advanced Python Wrapper will be updated in the future for `v9.0.0` compatibility with the community driver.
|
|
231
233
|
|
|
232
234
|
## License
|
|
233
235
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import Optional, Set
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AllowedAndBlockedHosts:
|
|
19
|
+
def __init__(self, allowed_host_ids: Optional[Set[str]], blocked_host_ids: Optional[Set[str]]):
|
|
20
|
+
self._allowed_host_ids = None if not allowed_host_ids else allowed_host_ids
|
|
21
|
+
self._blocked_host_ids = None if not blocked_host_ids else blocked_host_ids
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def allowed_host_ids(self) -> Optional[Set[str]]:
|
|
25
|
+
return self._allowed_host_ids
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def blocked_host_ids(self) -> Optional[Set[str]]:
|
|
29
|
+
return self._blocked_host_ids
|
|
@@ -50,13 +50,12 @@ class OpenedConnectionTracker:
|
|
|
50
50
|
"""
|
|
51
51
|
|
|
52
52
|
aliases: FrozenSet[str] = host_info.as_aliases()
|
|
53
|
-
host: str = host_info.as_alias()
|
|
54
53
|
|
|
55
|
-
if self._rds_utils.is_rds_instance(host):
|
|
56
|
-
self._track_connection(
|
|
54
|
+
if self._rds_utils.is_rds_instance(host_info.host):
|
|
55
|
+
self._track_connection(host_info.as_alias(), conn)
|
|
57
56
|
return
|
|
58
57
|
|
|
59
|
-
instance_endpoint: Optional[str] = next((alias for alias in aliases if self._rds_utils.is_rds_instance(alias)),
|
|
58
|
+
instance_endpoint: Optional[str] = next((alias for alias in aliases if self._rds_utils.is_rds_instance(self._rds_utils.remove_port(alias))),
|
|
60
59
|
None)
|
|
61
60
|
if not instance_endpoint:
|
|
62
61
|
logger.debug("OpenedConnectionTracker.UnableToPopulateOpenedConnectionSet")
|
|
@@ -82,7 +81,7 @@ class OpenedConnectionTracker:
|
|
|
82
81
|
return
|
|
83
82
|
|
|
84
83
|
for instance in host:
|
|
85
|
-
if instance is not None and self._rds_utils.is_rds_instance(instance):
|
|
84
|
+
if instance is not None and self._rds_utils.is_rds_instance(self._rds_utils.remove_port(instance)):
|
|
86
85
|
instance_endpoint = instance
|
|
87
86
|
break
|
|
88
87
|
|
|
@@ -202,7 +201,7 @@ class AuroraConnectionTrackerPlugin(Plugin):
|
|
|
202
201
|
|
|
203
202
|
def execute(self, target: object, method_name: str, execute_func: Callable, *args: Any, **kwargs: Any) -> Any:
|
|
204
203
|
if self._current_writer is None or self._need_update_current_writer:
|
|
205
|
-
self._current_writer = self._get_writer(self._plugin_service.
|
|
204
|
+
self._current_writer = self._get_writer(self._plugin_service.all_hosts)
|
|
206
205
|
self._need_update_current_writer = False
|
|
207
206
|
|
|
208
207
|
try:
|
|
@@ -210,7 +209,7 @@ class AuroraConnectionTrackerPlugin(Plugin):
|
|
|
210
209
|
|
|
211
210
|
except Exception as e:
|
|
212
211
|
# Check that e is a FailoverError and that the writer has changed
|
|
213
|
-
if isinstance(e, FailoverError) and self._get_writer(self._plugin_service.
|
|
212
|
+
if isinstance(e, FailoverError) and self._get_writer(self._plugin_service.all_hosts) != self._current_writer:
|
|
214
213
|
self._tracker.invalidate_all_connections(host_info=self._current_writer)
|
|
215
214
|
self._tracker.log_opened_connections()
|
|
216
215
|
self._need_update_current_writer = True
|
|
@@ -198,7 +198,7 @@ class AuroraInitialConnectionStrategyPlugin(Plugin):
|
|
|
198
198
|
sleep(delay_ms / 1000)
|
|
199
199
|
|
|
200
200
|
def _get_writer(self) -> Optional[HostInfo]:
|
|
201
|
-
for host in self._plugin_service.
|
|
201
|
+
for host in self._plugin_service.all_hosts:
|
|
202
202
|
if host.role == HostRole.WRITER:
|
|
203
203
|
return host
|
|
204
204
|
|
|
@@ -225,10 +225,10 @@ class AuroraInitialConnectionStrategyPlugin(Plugin):
|
|
|
225
225
|
init_host_provider_func(props)
|
|
226
226
|
|
|
227
227
|
def _has_no_readers(self) -> bool:
|
|
228
|
-
if len(self._plugin_service.
|
|
228
|
+
if len(self._plugin_service.all_hosts) == 0:
|
|
229
229
|
return False
|
|
230
230
|
|
|
231
|
-
for host in self._plugin_service.
|
|
231
|
+
for host in self._plugin_service.all_hosts:
|
|
232
232
|
if host.role == HostRole.READER:
|
|
233
233
|
return False
|
|
234
234
|
|
|
@@ -35,6 +35,7 @@ from aws_advanced_python_wrapper.utils.log import Logger
|
|
|
35
35
|
from aws_advanced_python_wrapper.utils.messages import Messages
|
|
36
36
|
from aws_advanced_python_wrapper.utils.properties import (Properties,
|
|
37
37
|
WrapperProperties)
|
|
38
|
+
from aws_advanced_python_wrapper.utils.region_utils import RegionUtils
|
|
38
39
|
from aws_advanced_python_wrapper.utils.telemetry.telemetry import \
|
|
39
40
|
TelemetryTraceLevel
|
|
40
41
|
|
|
@@ -63,6 +64,7 @@ class AwsSecretsManagerPlugin(Plugin):
|
|
|
63
64
|
Messages.get_formatted("AwsSecretsManagerPlugin.MissingRequiredConfigParameter",
|
|
64
65
|
WrapperProperties.SECRETS_MANAGER_SECRET_ID.name))
|
|
65
66
|
|
|
67
|
+
self._region_utils = RegionUtils()
|
|
66
68
|
region: str = self._get_rds_region(secret_id, props)
|
|
67
69
|
|
|
68
70
|
secrets_endpoint = WrapperProperties.SECRETS_MANAGER_ENDPOINT.get(props)
|
|
@@ -194,23 +196,22 @@ class AwsSecretsManagerPlugin(Plugin):
|
|
|
194
196
|
WrapperProperties.PASSWORD.set(properties, self._secret.password)
|
|
195
197
|
|
|
196
198
|
def _get_rds_region(self, secret_id: str, props: Properties) -> str:
|
|
197
|
-
region: Optional[str] = props.get(WrapperProperties.SECRETS_MANAGER_REGION.name)
|
|
198
|
-
if not region:
|
|
199
|
-
match = search(self._SECRETS_ARN_PATTERN, secret_id)
|
|
200
|
-
if match:
|
|
201
|
-
region = match.group("region")
|
|
202
|
-
else:
|
|
203
|
-
raise AwsWrapperError(
|
|
204
|
-
Messages.get_formatted("AwsSecretsManagerPlugin.MissingRequiredConfigParameter",
|
|
205
|
-
WrapperProperties.SECRETS_MANAGER_REGION.name))
|
|
206
|
-
|
|
207
199
|
session = self._session if self._session else boto3.Session()
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
200
|
+
region = self._region_utils.get_region(props, WrapperProperties.SECRETS_MANAGER_REGION.name, session=session)
|
|
201
|
+
|
|
202
|
+
if region:
|
|
203
|
+
return region
|
|
212
204
|
|
|
213
|
-
|
|
205
|
+
match = search(self._SECRETS_ARN_PATTERN, secret_id)
|
|
206
|
+
if match:
|
|
207
|
+
region = match.group("region")
|
|
208
|
+
|
|
209
|
+
if region:
|
|
210
|
+
return self._region_utils.verify_region(region)
|
|
211
|
+
else:
|
|
212
|
+
raise AwsWrapperError(
|
|
213
|
+
Messages.get_formatted("AwsSecretsManagerPlugin.MissingRequiredConfigParameter",
|
|
214
|
+
WrapperProperties.SECRETS_MANAGER_REGION.name))
|
|
214
215
|
|
|
215
216
|
|
|
216
217
|
class AwsSecretsManagerPluginFactory(PluginFactory):
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
4
|
+
# You may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from threading import Event, Thread
|
|
18
|
+
from time import perf_counter_ns, sleep
|
|
19
|
+
from typing import (TYPE_CHECKING, Any, Callable, ClassVar, Dict, List,
|
|
20
|
+
Optional, Set, Union, cast)
|
|
21
|
+
|
|
22
|
+
from aws_advanced_python_wrapper.allowed_and_blocked_hosts import \
|
|
23
|
+
AllowedAndBlockedHosts
|
|
24
|
+
from aws_advanced_python_wrapper.errors import AwsWrapperError
|
|
25
|
+
from aws_advanced_python_wrapper.utils.cache_map import CacheMap
|
|
26
|
+
from aws_advanced_python_wrapper.utils.messages import Messages
|
|
27
|
+
from aws_advanced_python_wrapper.utils.region_utils import RegionUtils
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from aws_advanced_python_wrapper.driver_dialect import DriverDialect
|
|
31
|
+
from aws_advanced_python_wrapper.hostinfo import HostInfo
|
|
32
|
+
from aws_advanced_python_wrapper.pep249 import Connection
|
|
33
|
+
from aws_advanced_python_wrapper.plugin_service import PluginService
|
|
34
|
+
from aws_advanced_python_wrapper.utils.properties import Properties
|
|
35
|
+
|
|
36
|
+
from enum import Enum
|
|
37
|
+
|
|
38
|
+
from boto3 import Session
|
|
39
|
+
|
|
40
|
+
from aws_advanced_python_wrapper.plugin import Plugin, PluginFactory
|
|
41
|
+
from aws_advanced_python_wrapper.utils.log import Logger
|
|
42
|
+
from aws_advanced_python_wrapper.utils.properties import WrapperProperties
|
|
43
|
+
from aws_advanced_python_wrapper.utils.rdsutils import RdsUtils
|
|
44
|
+
from aws_advanced_python_wrapper.utils.sliding_expiration_cache import \
|
|
45
|
+
SlidingExpirationCacheWithCleanupThread
|
|
46
|
+
from aws_advanced_python_wrapper.utils.telemetry.telemetry import (
|
|
47
|
+
TelemetryCounter, TelemetryFactory)
|
|
48
|
+
|
|
49
|
+
logger = Logger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CustomEndpointRoleType(Enum):
|
|
53
|
+
"""
|
|
54
|
+
Enum representing the possible roles of instances specified by a custom endpoint. Note that, currently, it is not
|
|
55
|
+
possible to create a WRITER custom endpoint.
|
|
56
|
+
"""
|
|
57
|
+
ANY = "ANY"
|
|
58
|
+
READER = "READER"
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_string(cls, value):
|
|
62
|
+
return CustomEndpointRoleType(value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class CustomEndpointInfo:
|
|
66
|
+
def __init__(self,
|
|
67
|
+
endpoint_id: str,
|
|
68
|
+
cluster_id: str,
|
|
69
|
+
endpoint: str,
|
|
70
|
+
role_type: CustomEndpointRoleType,
|
|
71
|
+
static_members: Optional[Set[str]],
|
|
72
|
+
excluded_members: Optional[Set[str]]):
|
|
73
|
+
self.endpoint_id = endpoint_id
|
|
74
|
+
self.cluster_id = cluster_id
|
|
75
|
+
self.endpoint = endpoint
|
|
76
|
+
self.role_type = role_type
|
|
77
|
+
self.static_members = None if not static_members else static_members
|
|
78
|
+
self.excluded_members = None if not excluded_members else excluded_members
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_db_cluster_endpoint(cls, endpoint_response_info: Dict[str, Union[str, List[str]]]):
|
|
82
|
+
return CustomEndpointInfo(
|
|
83
|
+
str(endpoint_response_info.get("DBClusterEndpointIdentifier")),
|
|
84
|
+
str(endpoint_response_info.get("DBClusterIdentifier")),
|
|
85
|
+
str(endpoint_response_info.get("Endpoint")),
|
|
86
|
+
CustomEndpointRoleType.from_string(str(endpoint_response_info.get("CustomEndpointType"))),
|
|
87
|
+
set(cast('List[str]', endpoint_response_info.get("StaticMembers"))),
|
|
88
|
+
set(cast('List[str]', endpoint_response_info.get("ExcludedMembers")))
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def __eq__(self, other: object):
|
|
92
|
+
if self is object:
|
|
93
|
+
return True
|
|
94
|
+
if not isinstance(other, CustomEndpointInfo):
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
return self.endpoint_id == other.endpoint_id \
|
|
98
|
+
and self.cluster_id == other.cluster_id \
|
|
99
|
+
and self.endpoint == other.endpoint \
|
|
100
|
+
and self.role_type == other.role_type \
|
|
101
|
+
and self.static_members == other.static_members \
|
|
102
|
+
and self.excluded_members == other.excluded_members
|
|
103
|
+
|
|
104
|
+
def __hash__(self):
|
|
105
|
+
return hash((self.endpoint_id, self.cluster_id, self.endpoint, self.role_type))
|
|
106
|
+
|
|
107
|
+
def __str__(self):
|
|
108
|
+
return (f"CustomEndpointInfo[endpoint={self.endpoint}, cluster_id={self.cluster_id}, "
|
|
109
|
+
f"role_type={self.role_type}, endpoint_id={self.endpoint_id}, static_members={self.static_members}, "
|
|
110
|
+
f"excluded_members={self.excluded_members}]")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CustomEndpointMonitor:
|
|
114
|
+
"""
|
|
115
|
+
A custom endpoint monitor. This class uses a background thread to monitor a given custom endpoint for custom
|
|
116
|
+
endpoint information and future changes to the custom endpoint.
|
|
117
|
+
"""
|
|
118
|
+
_CUSTOM_ENDPOINT_INFO_EXPIRATION_NS: ClassVar[int] = 5 * 60_000_000_000 # 5 minutes
|
|
119
|
+
# Keys are custom endpoint URLs, values are information objects for the associated custom endpoint.
|
|
120
|
+
_custom_endpoint_info_cache: ClassVar[CacheMap[str, CustomEndpointInfo]] = CacheMap()
|
|
121
|
+
|
|
122
|
+
def __init__(self,
|
|
123
|
+
plugin_service: PluginService,
|
|
124
|
+
custom_endpoint_host_info: HostInfo,
|
|
125
|
+
endpoint_id: str,
|
|
126
|
+
region: str,
|
|
127
|
+
refresh_rate_ns: int,
|
|
128
|
+
session: Optional[Session] = None):
|
|
129
|
+
self._plugin_service = plugin_service
|
|
130
|
+
self._custom_endpoint_host_info = custom_endpoint_host_info
|
|
131
|
+
self._endpoint_id = endpoint_id
|
|
132
|
+
self._region = region
|
|
133
|
+
self._refresh_rate_ns = refresh_rate_ns
|
|
134
|
+
self._session = session if session else Session()
|
|
135
|
+
self._client = self._session.client('rds', region_name=region)
|
|
136
|
+
|
|
137
|
+
self._stop_event = Event()
|
|
138
|
+
telemetry_factory = self._plugin_service.get_telemetry_factory()
|
|
139
|
+
self._info_changed_counter = telemetry_factory.create_counter("customEndpoint.infoChanged.counter")
|
|
140
|
+
|
|
141
|
+
self._thread = Thread(daemon=True, name="CustomEndpointMonitorThread", target=self._run)
|
|
142
|
+
self._thread.start()
|
|
143
|
+
|
|
144
|
+
def _run(self):
|
|
145
|
+
logger.debug("CustomEndpointMonitor.StartingMonitor", self._custom_endpoint_host_info.host)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
while not self._stop_event.is_set():
|
|
149
|
+
try:
|
|
150
|
+
start_ns = perf_counter_ns()
|
|
151
|
+
|
|
152
|
+
response = self._client.describe_db_cluster_endpoints(
|
|
153
|
+
DBClusterEndpointIdentifier=self._endpoint_id,
|
|
154
|
+
Filters=[
|
|
155
|
+
{
|
|
156
|
+
"Name": "db-cluster-endpoint-type",
|
|
157
|
+
"Values": ["custom"]
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
endpoints = response["DBClusterEndpoints"]
|
|
163
|
+
if len(endpoints) != 1:
|
|
164
|
+
endpoint_hostnames = [endpoint["Endpoint"] for endpoint in endpoints]
|
|
165
|
+
logger.warning(
|
|
166
|
+
"CustomEndpointMonitor.UnexpectedNumberOfEndpoints",
|
|
167
|
+
self._endpoint_id,
|
|
168
|
+
self._region,
|
|
169
|
+
len(endpoints),
|
|
170
|
+
endpoint_hostnames)
|
|
171
|
+
|
|
172
|
+
sleep(self._refresh_rate_ns / 1_000_000_000)
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
endpoint_info = CustomEndpointInfo.from_db_cluster_endpoint(endpoints[0])
|
|
176
|
+
cached_info = \
|
|
177
|
+
CustomEndpointMonitor._custom_endpoint_info_cache.get(self._custom_endpoint_host_info.host)
|
|
178
|
+
if cached_info is not None and cached_info == endpoint_info:
|
|
179
|
+
elapsed_time = perf_counter_ns() - start_ns
|
|
180
|
+
sleep_duration = max(0, self._refresh_rate_ns - elapsed_time)
|
|
181
|
+
sleep(sleep_duration / 1_000_000_000)
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
logger.debug(
|
|
185
|
+
"CustomEndpointMonitor.DetectedChangeInCustomEndpointInfo",
|
|
186
|
+
self._custom_endpoint_host_info.host, endpoint_info)
|
|
187
|
+
|
|
188
|
+
# The custom endpoint info has changed, so we need to update the set of allowed/blocked hosts.
|
|
189
|
+
hosts = AllowedAndBlockedHosts(endpoint_info.static_members, endpoint_info.excluded_members)
|
|
190
|
+
self._plugin_service.allowed_and_blocked_hosts = hosts
|
|
191
|
+
CustomEndpointMonitor._custom_endpoint_info_cache.put(
|
|
192
|
+
self._custom_endpoint_host_info.host,
|
|
193
|
+
endpoint_info,
|
|
194
|
+
CustomEndpointMonitor._CUSTOM_ENDPOINT_INFO_EXPIRATION_NS)
|
|
195
|
+
self._info_changed_counter.inc()
|
|
196
|
+
|
|
197
|
+
elapsed_time = perf_counter_ns() - start_ns
|
|
198
|
+
sleep_duration = max(0, self._refresh_rate_ns - elapsed_time)
|
|
199
|
+
sleep(sleep_duration / 1_000_000_000)
|
|
200
|
+
continue
|
|
201
|
+
except InterruptedError as e:
|
|
202
|
+
raise e
|
|
203
|
+
except Exception as e:
|
|
204
|
+
# If the exception is not an InterruptedError, log it and continue monitoring.
|
|
205
|
+
logger.error("CustomEndpointMonitor.Exception", self._custom_endpoint_host_info.host, e)
|
|
206
|
+
except InterruptedError:
|
|
207
|
+
logger.info("CustomEndpointMonitor.Interrupted", self._custom_endpoint_host_info.host)
|
|
208
|
+
finally:
|
|
209
|
+
CustomEndpointMonitor._custom_endpoint_info_cache.remove(self._custom_endpoint_host_info.host)
|
|
210
|
+
self._stop_event.set()
|
|
211
|
+
self._client.close()
|
|
212
|
+
logger.debug("CustomEndpointMonitor.StoppedMonitor", self._custom_endpoint_host_info.host)
|
|
213
|
+
|
|
214
|
+
def has_custom_endpoint_info(self):
|
|
215
|
+
return CustomEndpointMonitor._custom_endpoint_info_cache.get(self._custom_endpoint_host_info.host) is not None
|
|
216
|
+
|
|
217
|
+
def close(self):
|
|
218
|
+
logger.debug("CustomEndpointMonitor.StoppingMonitor", self._custom_endpoint_host_info.host)
|
|
219
|
+
CustomEndpointMonitor._custom_endpoint_info_cache.remove(self._custom_endpoint_host_info.host)
|
|
220
|
+
self._stop_event.set()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class CustomEndpointPlugin(Plugin):
|
|
224
|
+
"""
|
|
225
|
+
A plugin that analyzes custom endpoints for custom endpoint information and custom endpoint changes, such as adding
|
|
226
|
+
or removing an instance in the custom endpoint.
|
|
227
|
+
"""
|
|
228
|
+
_SUBSCRIBED_METHODS: ClassVar[Set[str]] = {"connect"}
|
|
229
|
+
_CACHE_CLEANUP_RATE_NS: ClassVar[int] = 6 * 10 ^ 10 # 1 minute
|
|
230
|
+
_monitors: ClassVar[SlidingExpirationCacheWithCleanupThread[str, CustomEndpointMonitor]] = \
|
|
231
|
+
SlidingExpirationCacheWithCleanupThread(_CACHE_CLEANUP_RATE_NS,
|
|
232
|
+
should_dispose_func=lambda _: True,
|
|
233
|
+
item_disposal_func=lambda monitor: monitor.close())
|
|
234
|
+
|
|
235
|
+
def __init__(self, plugin_service: PluginService, props: Properties):
|
|
236
|
+
self._plugin_service = plugin_service
|
|
237
|
+
self._props = props
|
|
238
|
+
|
|
239
|
+
self._should_wait_for_info: bool = WrapperProperties.WAIT_FOR_CUSTOM_ENDPOINT_INFO.get_bool(self._props)
|
|
240
|
+
self._wait_for_info_timeout_ms: int = WrapperProperties.WAIT_FOR_CUSTOM_ENDPOINT_INFO_TIMEOUT_MS.get_int(self._props)
|
|
241
|
+
self._idle_monitor_expiration_ms: int = \
|
|
242
|
+
WrapperProperties.CUSTOM_ENDPOINT_IDLE_MONITOR_EXPIRATION_MS.get_int(self._props)
|
|
243
|
+
|
|
244
|
+
self._rds_utils = RdsUtils()
|
|
245
|
+
self._region_utils = RegionUtils()
|
|
246
|
+
self._region: Optional[str] = None
|
|
247
|
+
self._custom_endpoint_host_info: Optional[HostInfo] = None
|
|
248
|
+
self._custom_endpoint_id: Optional[str] = None
|
|
249
|
+
telemetry_factory: TelemetryFactory = self._plugin_service.get_telemetry_factory()
|
|
250
|
+
self._wait_for_info_counter: TelemetryCounter = telemetry_factory.create_counter("customEndpoint.waitForInfo.counter")
|
|
251
|
+
|
|
252
|
+
CustomEndpointPlugin._SUBSCRIBED_METHODS.update(self._plugin_service.network_bound_methods)
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def subscribed_methods(self) -> Set[str]:
|
|
256
|
+
return CustomEndpointPlugin._SUBSCRIBED_METHODS
|
|
257
|
+
|
|
258
|
+
def connect(
|
|
259
|
+
self,
|
|
260
|
+
target_driver_func: Callable,
|
|
261
|
+
driver_dialect: DriverDialect,
|
|
262
|
+
host_info: HostInfo,
|
|
263
|
+
props: Properties,
|
|
264
|
+
is_initial_connection: bool,
|
|
265
|
+
connect_func: Callable) -> Connection:
|
|
266
|
+
if not self._rds_utils.is_rds_custom_cluster_dns(host_info.host):
|
|
267
|
+
return connect_func()
|
|
268
|
+
|
|
269
|
+
self._custom_endpoint_host_info = host_info
|
|
270
|
+
logger.debug("CustomEndpointPlugin.ConnectionRequestToCustomEndpoint", host_info.host)
|
|
271
|
+
|
|
272
|
+
self._custom_endpoint_id = self._rds_utils.get_cluster_id(host_info.host)
|
|
273
|
+
if not self._custom_endpoint_id:
|
|
274
|
+
raise AwsWrapperError(
|
|
275
|
+
Messages.get_formatted(
|
|
276
|
+
"CustomEndpointPlugin.ErrorParsingEndpointIdentifier", self._custom_endpoint_host_info.host))
|
|
277
|
+
|
|
278
|
+
hostname = self._custom_endpoint_host_info.host
|
|
279
|
+
self._region = self._region_utils.get_region_from_hostname(hostname)
|
|
280
|
+
if not self._region:
|
|
281
|
+
error_message = "RdsUtils.UnsupportedHostname"
|
|
282
|
+
logger.debug(error_message, hostname)
|
|
283
|
+
raise AwsWrapperError(Messages.get_formatted(error_message, hostname))
|
|
284
|
+
|
|
285
|
+
monitor = self._create_monitor_if_absent(props)
|
|
286
|
+
if self._should_wait_for_info:
|
|
287
|
+
self._wait_for_info(monitor)
|
|
288
|
+
|
|
289
|
+
return connect_func()
|
|
290
|
+
|
|
291
|
+
def _create_monitor_if_absent(self, props: Properties) -> CustomEndpointMonitor:
|
|
292
|
+
host_info = cast('HostInfo', self._custom_endpoint_host_info)
|
|
293
|
+
endpoint_id = cast('str', self._custom_endpoint_id)
|
|
294
|
+
region = cast('str', self._region)
|
|
295
|
+
monitor = CustomEndpointPlugin._monitors.compute_if_absent(
|
|
296
|
+
host_info.host,
|
|
297
|
+
lambda key: CustomEndpointMonitor(
|
|
298
|
+
self._plugin_service,
|
|
299
|
+
host_info,
|
|
300
|
+
endpoint_id,
|
|
301
|
+
region,
|
|
302
|
+
WrapperProperties.CUSTOM_ENDPOINT_INFO_REFRESH_RATE_MS.get_int(props) * 1_000_000),
|
|
303
|
+
self._idle_monitor_expiration_ms * 1_000_000)
|
|
304
|
+
|
|
305
|
+
return cast('CustomEndpointMonitor', monitor)
|
|
306
|
+
|
|
307
|
+
def _wait_for_info(self, monitor: CustomEndpointMonitor):
|
|
308
|
+
has_info = monitor.has_custom_endpoint_info()
|
|
309
|
+
if has_info:
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
self._wait_for_info_counter.inc()
|
|
313
|
+
host_info = cast('HostInfo', self._custom_endpoint_host_info)
|
|
314
|
+
hostname = host_info.host
|
|
315
|
+
logger.debug("CustomEndpointPlugin.WaitingForCustomEndpointInfo", hostname, self._wait_for_info_timeout_ms)
|
|
316
|
+
wait_for_info_timeout_ns = perf_counter_ns() + self._wait_for_info_timeout_ms * 1_000_000
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
while not has_info and perf_counter_ns() < wait_for_info_timeout_ns:
|
|
320
|
+
sleep(0.1)
|
|
321
|
+
has_info = monitor.has_custom_endpoint_info()
|
|
322
|
+
except InterruptedError:
|
|
323
|
+
raise AwsWrapperError(Messages.get_formatted("CustomEndpointPlugin.InterruptedThread", hostname))
|
|
324
|
+
|
|
325
|
+
if not has_info:
|
|
326
|
+
raise AwsWrapperError(
|
|
327
|
+
Messages.get_formatted(
|
|
328
|
+
"CustomEndpointPlugin.TimedOutWaitingForCustomEndpointInfo",
|
|
329
|
+
self._wait_for_info_timeout_ms, hostname))
|
|
330
|
+
|
|
331
|
+
def execute(self, target: type, method_name: str, execute_func: Callable, *args: Any, **kwargs: Any) -> Any:
|
|
332
|
+
if self._custom_endpoint_host_info is None:
|
|
333
|
+
return execute_func()
|
|
334
|
+
|
|
335
|
+
monitor = self._create_monitor_if_absent(self._props)
|
|
336
|
+
if self._should_wait_for_info:
|
|
337
|
+
self._wait_for_info(monitor)
|
|
338
|
+
|
|
339
|
+
return execute_func()
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class CustomEndpointPluginFactory(PluginFactory):
|
|
343
|
+
def get_instance(self, plugin_service: PluginService, props: Properties) -> Plugin:
|
|
344
|
+
return CustomEndpointPlugin(plugin_service, props)
|
|
@@ -25,6 +25,7 @@ if TYPE_CHECKING:
|
|
|
25
25
|
|
|
26
26
|
from typing import Any, Callable, Dict, Optional, Set
|
|
27
27
|
|
|
28
|
+
from aws_advanced_python_wrapper import LogUtils
|
|
28
29
|
from aws_advanced_python_wrapper.errors import (
|
|
29
30
|
AwsWrapperError, FailoverFailedError, FailoverSuccessError,
|
|
30
31
|
TransactionResolutionUnknownError)
|
|
@@ -328,17 +329,23 @@ class FailoverPlugin(Plugin):
|
|
|
328
329
|
try:
|
|
329
330
|
logger.info("FailoverPlugin.StartWriterFailover")
|
|
330
331
|
|
|
331
|
-
result: WriterFailoverResult = self._writer_failover_handler.failover(self._plugin_service.
|
|
332
|
-
|
|
332
|
+
result: WriterFailoverResult = self._writer_failover_handler.failover(self._plugin_service.all_hosts)
|
|
333
333
|
if result is not None and result.exception is not None:
|
|
334
334
|
raise result.exception
|
|
335
335
|
elif result is None or not result.is_connected:
|
|
336
336
|
raise FailoverFailedError(Messages.get("FailoverPlugin.UnableToConnectToWriter"))
|
|
337
337
|
|
|
338
338
|
writer_host = self._get_writer(result.topology)
|
|
339
|
+
allowed_hosts = self._plugin_service.hosts
|
|
340
|
+
allowed_hostnames = [host.host for host in allowed_hosts]
|
|
341
|
+
if writer_host.host not in allowed_hostnames:
|
|
342
|
+
raise FailoverFailedError(
|
|
343
|
+
Messages.get_formatted(
|
|
344
|
+
"FailoverPlugin.NewWriterNotAllowed",
|
|
345
|
+
"<null>" if writer_host is None else writer_host.host,
|
|
346
|
+
LogUtils.log_topology(allowed_hosts)))
|
|
339
347
|
|
|
340
348
|
self._plugin_service.set_current_connection(result.new_connection, writer_host)
|
|
341
|
-
|
|
342
349
|
logger.info("FailoverPlugin.EstablishedConnection", self._plugin_service.current_host_info)
|
|
343
350
|
|
|
344
351
|
self._plugin_service.refresh_host_list()
|
|
@@ -438,11 +445,11 @@ class FailoverPlugin(Plugin):
|
|
|
438
445
|
def _is_failover_enabled(self) -> bool:
|
|
439
446
|
return self._enable_failover_setting and \
|
|
440
447
|
self._rds_url_type != RdsUrlType.RDS_PROXY and \
|
|
441
|
-
self._plugin_service.
|
|
442
|
-
len(self._plugin_service.
|
|
448
|
+
self._plugin_service.all_hosts is not None and \
|
|
449
|
+
len(self._plugin_service.all_hosts) > 0
|
|
443
450
|
|
|
444
451
|
def _get_current_writer(self) -> Optional[HostInfo]:
|
|
445
|
-
topology = self._plugin_service.
|
|
452
|
+
topology = self._plugin_service.all_hosts
|
|
446
453
|
if topology is None:
|
|
447
454
|
return None
|
|
448
455
|
|
|
@@ -50,6 +50,8 @@ MAX_VALUE = 2147483647
|
|
|
50
50
|
class FastestResponseStrategyPlugin(Plugin):
|
|
51
51
|
_FASTEST_RESPONSE_STRATEGY_NAME = "fastest_response"
|
|
52
52
|
_SUBSCRIBED_METHODS: Set[str] = {"accepts_strategy",
|
|
53
|
+
"connect",
|
|
54
|
+
"force_connect",
|
|
53
55
|
"get_host_info_by_strategy",
|
|
54
56
|
"notify_host_list_changed"}
|
|
55
57
|
|
|
@@ -111,7 +113,7 @@ class FastestResponseStrategyPlugin(Plugin):
|
|
|
111
113
|
fastest_response_host: Optional[HostInfo] = self._cached_fastest_response_host_by_role.get(role.name)
|
|
112
114
|
if fastest_response_host is not None:
|
|
113
115
|
|
|
114
|
-
# Found a fastest host. Let's find it in the
|
|
116
|
+
# Found a fastest host. Let's find it in the latest topology.
|
|
115
117
|
for host in self._plugin_service.hosts:
|
|
116
118
|
if host == fastest_response_host:
|
|
117
119
|
# found the fastest host in the topology
|
|
@@ -22,6 +22,7 @@ from urllib.parse import urlencode
|
|
|
22
22
|
from aws_advanced_python_wrapper.credentials_provider_factory import (
|
|
23
23
|
CredentialsProviderFactory, SamlCredentialsProviderFactory)
|
|
24
24
|
from aws_advanced_python_wrapper.utils.iam_utils import IamAuthUtils, TokenInfo
|
|
25
|
+
from aws_advanced_python_wrapper.utils.region_utils import RegionUtils
|
|
25
26
|
from aws_advanced_python_wrapper.utils.saml_utils import SamlUtils
|
|
26
27
|
|
|
27
28
|
if TYPE_CHECKING:
|
|
@@ -59,6 +60,7 @@ class FederatedAuthPlugin(Plugin):
|
|
|
59
60
|
self._credentials_provider_factory = credentials_provider_factory
|
|
60
61
|
self._session = session
|
|
61
62
|
|
|
63
|
+
self._region_utils = RegionUtils()
|
|
62
64
|
telemetry_factory = self._plugin_service.get_telemetry_factory()
|
|
63
65
|
self._fetch_token_counter = telemetry_factory.create_counter("federated.fetch_token.count")
|
|
64
66
|
self._cache_size_gauge = telemetry_factory.create_gauge("federated.token_cache.size", lambda: len(FederatedAuthPlugin._token_cache))
|
|
@@ -82,7 +84,11 @@ class FederatedAuthPlugin(Plugin):
|
|
|
82
84
|
|
|
83
85
|
host = IamAuthUtils.get_iam_host(props, host_info)
|
|
84
86
|
port = IamAuthUtils.get_port(props, host_info, self._plugin_service.database_dialect.default_port)
|
|
85
|
-
region
|
|
87
|
+
region = self._region_utils.get_region(props, WrapperProperties.IAM_REGION.name, host, self._session)
|
|
88
|
+
if not region:
|
|
89
|
+
error_message = "RdsUtils.UnsupportedHostname"
|
|
90
|
+
logger.debug(error_message, host)
|
|
91
|
+
raise AwsWrapperError(Messages.get_formatted(error_message, host))
|
|
86
92
|
|
|
87
93
|
user = WrapperProperties.DB_USER.get(props)
|
|
88
94
|
cache_key: str = IamAuthUtils.get_cache_key(
|