Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ Support for Blue/Green deployments using the AWS Advanced Python Wrapper require

Please note that Aurora Global Database and RDS Multi-AZ clusters with Blue/Green deployments is currently not supported. For detailed information on supported database versions, refer to the [Blue/Green Deployment Plugin Documentation](docs/using-the-python-wrapper/using-plugins/UsingTheBlueGreenPlugin.md).

#### Enhanced Failure Monitoring with MySQL

Enhanced Failure Monitoring (both the `host_monitoring` and `host_monitoring_v2` plugins) is not supported with the MySQL Connector/Python driver. Both plugins rely on being able to abort an active connection from a separate monitoring thread when a host is determined to be unhealthy, and the MySQL Connector/Python driver does not support aborting connections from a separate thread. For more information, see the [Host Monitoring Plugin Documentation](docs/using-the-python-wrapper/using-plugins/UsingTheHostMonitoringPlugin.md).

#### MySQL Connector/Python C Extension

When connecting to Aurora MySQL clusters, it is recommended to use the Python implementation of the MySQL Connector/Python driver by setting the `use_pure` connection argument to `True`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def _get_reader(self, props: Properties) -> Optional[HostInfo]:

def init_host_provider(self, props: Properties, host_list_provider_service: HostListProviderService, init_host_provider_func: Callable):
self._host_list_provider_service = host_list_provider_service
init_host_provider_func(props)
init_host_provider_func()

def _has_no_readers(self) -> bool:
if len(self._plugin_service.all_hosts) == 0:
Expand Down
3 changes: 2 additions & 1 deletion aws_advanced_python_wrapper/blue_green_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,8 @@ def __init__(self, plugin_service: PluginService, props: Properties):
self._telemetry_factory = plugin_service.get_telemetry_factory()
self._provider_supplier: Callable[[PluginService, Properties, str], BlueGreenStatusProvider] = \
lambda _plugin_service, _props, bg_id: BlueGreenStatusProvider(_plugin_service, _props, bg_id)
self._bg_id = WrapperProperties.BG_ID.get_or_default(props).strip().lower()
bg_id = WrapperProperties.BG_ID.get(props)
self._bg_id = bg_id.strip().lower() if bg_id is not None else "1"
self._rds_utils = RdsUtils()
self._bg_status: Optional[BlueGreenStatus] = None
self._is_iam_in_use = False
Expand Down
7 changes: 5 additions & 2 deletions aws_advanced_python_wrapper/plugin_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,9 +914,12 @@ def get_plugins(self) -> List[Plugin]:
Messages.get_formatted("PluginManager.ConfigurationProfileNotFound", profile_name))
plugin_factories = DriverConfigurationProfiles.get_plugin_factories(profile_name)
else:
plugin_codes = WrapperProperties.PLUGINS.get(self._props)
plugin_codes = WrapperProperties.PLUGINS.get(self._props, False)
if plugin_codes is None:
plugin_codes = WrapperProperties.DEFAULT_PLUGINS
driver_dialect = self._container.plugin_service.driver_dialect
plugin_codes = WrapperProperties.MYSQL_CONNECTOR_DEFAULT_PLUGINS \
if driver_dialect.dialect_code == "mysql-connector-python" \
else WrapperProperties.DEFAULT_PLUGINS

if plugin_codes != "":
plugin_factories = self.create_plugin_factories_from_list(plugin_codes.split(","))
Expand Down
22 changes: 14 additions & 8 deletions aws_advanced_python_wrapper/utils/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,18 @@ def __init__(
def __str__(self):
return f"WrapperProperty(name={self.name}, default_value={self.default_value})"

def get(self, props: Properties) -> Optional[str]:
if self.default_value:
def get(self, props: Properties, return_default: bool = True) -> Optional[str]:
"""Retrieve this property's value from the given properties.

:param props: the :class:`Properties` collection to read the value from.
:param return_default: if ``True``, fall back to this property's
``default_value`` when the property is not present in ``props``.
If ``False``, the default value is ignored.
:return: the property's value, the default value when missing and
``return_default`` is ``True``, or ``None`` if the property is
absent and no default applies.
"""
if self.default_value and return_default:
return props.get(self.name, self.default_value)
return props.get(self.name)

Expand All @@ -62,11 +72,6 @@ def get_type(self, props: Properties, type_class: Type[T]) -> T:
return value.lower() == "true" if isinstance(value, str) else bool(value) # type: ignore
return type_class(value) # type: ignore

def get_or_default(self, props: Properties) -> str:
if not self.default_value:
raise ValueError(f"No default value found for property {self}")
return props.get(self.name, self.default_value)

def get_int(self, props: Properties) -> int:
return self.get_type(props, int)

Expand All @@ -81,7 +86,8 @@ def set(self, props: Properties, value: Any):


class WrapperProperties:
DEFAULT_PLUGINS = "aurora_connection_tracker,failover,host_monitoring_v2"
DEFAULT_PLUGINS = "initial_connection,aurora_connection_tracker,failover_v2,host_monitoring_v2"
MYSQL_CONNECTOR_DEFAULT_PLUGINS = "initial_connection,aurora_connection_tracker,failover_v2"
_DEFAULT_TOKEN_EXPIRATION_SEC = 15 * 60

PROFILE_NAME = WrapperProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ One use case is to pair EFM with the [Failover Connection Plugin](./UsingTheFail

The Host Monitoring Connection Plugin will be loaded by default if the [`plugins`](../UsingThePythonWrapper.md#connection-plugin-manager-parameters) parameter is not specified. The Host Monitoring Connection Plugin can also be explicitly loaded by adding the plugin code `host_monitoring` to the [`plugins`](../UsingThePythonWrapper.md#aws-advanced-python-wrapper-parameters) parameter. Enhanced Failure Monitoring is enabled by default when the Host Monitoring Connection Plugin is loaded, but it can be disabled by setting the `failure_detection_enabled` parameter to `False`.

This plugin only works with drivers that support aborting connections from a separate thread. At this moment, this plugin is incompatible with the MySQL Connector/Python driver.
This plugin only works with drivers that support aborting connections from a separate thread. At this moment, this plugin is incompatible with the MySQL Connector/Python driver because the driver does not support aborting connections from a separate thread.

> [IMPORTANT]\
> The Host Monitoring Plugin creates monitoring threads in the background to monitor all connections established to each cluster instance. The monitoring threads can be cleaned up in two ways:
Expand Down Expand Up @@ -88,6 +88,9 @@ finally:
Host Monitoring Plugin v2, also known as `host_monitoring_v2`, is an alternative implementation of enhanced failure monitoring and it is functionally equivalent to the Host Monitoring Plugin described above. Both plugins share the same set of [configuration parameters](#enhanced-failure-monitoring-parameters). The `host_monitoring_v2` plugin is designed to be a drop-in replacement for the `host_monitoring` plugin.
The `host_monitoring_v2` plugin can be used in any scenario where the `host_monitoring` plugin is mentioned. This plugin is enabled by default. The original EFM plugin can still be used by specifying `host_monitoring` in the `plugins` parameter.

> [!NOTE]\
> Like the `host_monitoring` plugin, the `host_monitoring_v2` plugin only works with drivers that support aborting connections from a separate thread. At this moment, it is incompatible with the MySQL Connector/Python driver because the driver does not support aborting connections from a separate thread.

> [!NOTE]\
> Since these two plugins are separate plugins, users may decide to use them together with a single connection. While this should not have any negative side effects, it is not recommended. It is recommended to use either the `host_monitoring_v2` plugin, or the `host_monitoring` plugin where it's needed.

Expand Down
3 changes: 2 additions & 1 deletion tests/unit/test_iam_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ def test_connect_with_specified_host(iam_host: str, mocker, mock_plugin_service,

def test_aws_supported_regions_url_exists():
url = "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html"
assert 200 == urllib.request.urlopen(url).getcode()
request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
assert 200 == urllib.request.urlopen(request).getcode()


@pytest.mark.parametrize("host", [
Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_limitless_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def mock_plugin_service(mocker, mock_driver_dialect, mock_conn, host_info):
service_mock.current_host_info = host_info
# Use a real AuroraPgDialect to pass isinstance checks in Python 3.12+
service_mock.database_dialect = AuroraPgDialect()
service_mock.props = Properties()

type(service_mock).driver_dialect = mocker.PropertyMock(return_value=mock_driver_dialect)
return service_mock
Expand Down