Skip to content

Commit 19f59c7

Browse files
committed
feat: limitless plugin implementation
1 parent f5f5f71 commit 19f59c7

File tree

5 files changed

+88
-8
lines changed

5 files changed

+88
-8
lines changed

aws_advanced_python_wrapper/connection_provider.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626

2727
from aws_advanced_python_wrapper.errors import AwsWrapperError
2828
from aws_advanced_python_wrapper.host_selector import (
29-
HostSelector, RandomHostSelector, RoundRobinHostSelector,
30-
WeightedRandomHostSelector)
29+
HighestWeightHostSelector, HostSelector, RandomHostSelector,
30+
RoundRobinHostSelector, WeightedRandomHostSelector)
3131
from aws_advanced_python_wrapper.plugin import CanReleaseResources
3232
from aws_advanced_python_wrapper.utils.log import Logger
3333
from aws_advanced_python_wrapper.utils.messages import Messages
@@ -98,7 +98,8 @@ def connect(
9898
class DriverConnectionProvider(ConnectionProvider):
9999
_accepted_strategies: Dict[str, HostSelector] = {"random": RandomHostSelector(),
100100
"round_robin": RoundRobinHostSelector(),
101-
"weighted_random": WeightedRandomHostSelector()}
101+
"weighted_random": WeightedRandomHostSelector(),
102+
"highest_weight": HighestWeightHostSelector()}
102103

103104
def accepts_host_info(self, host_info: HostInfo, props: Properties) -> bool:
104105
return True

aws_advanced_python_wrapper/host_selector.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,15 @@ def _update_host_weight_map_from_string(self, props: Optional[Properties] = None
260260
except ValueError:
261261
logger.error(message, pair)
262262
raise AwsWrapperError(Messages.get_formatted(message, pair))
263+
264+
265+
class HighestWeightHostSelector(HostSelector):
266+
267+
def get_host(self, hosts: Tuple[HostInfo, ...], role: HostRole, props: Optional[Properties] = None) -> HostInfo:
268+
eligible_hosts: List[HostInfo] = [host for host in hosts if
269+
host.role == role and host.get_availability() == HostAvailability.AVAILABLE]
270+
271+
if len(eligible_hosts) == 0:
272+
raise AwsWrapperError(Messages.get_formatted("HostSelector.NoHostsMatchingRole", role))
273+
274+
return max(eligible_hosts, key=lambda host: host.weight)

aws_advanced_python_wrapper/limitless_connection_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ def establish_connection(self, context: LimitlessConnectionContext) -> None:
363363

364364
self._retry_connection_with_least_loaded_routers(context)
365365
return
366-
366+
selected_host_info = None
367367
if selected_host_info is None:
368368
self._retry_connection_with_least_loaded_routers(context)
369369
return
@@ -417,7 +417,7 @@ def _retry_connection_with_least_loaded_routers(self, context: LimitlessConnecti
417417

418418
try:
419419
selected_host_info = self._plugin_service.get_host_info_by_strategy(
420-
HostRole.WRITER, "weighted_random", context.get_limitless_routers())
420+
HostRole.WRITER, "highest_weight", context.get_limitless_routers())
421421
logger.debug("LimitlessRouterServiceImpl.SelectedHostForRetry",
422422
"None" if selected_host_info is None else selected_host_info.host)
423423
if selected_host_info is None:

aws_advanced_python_wrapper/sql_alchemy_connection_provider.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
from aws_advanced_python_wrapper.connection_provider import ConnectionProvider
2727
from aws_advanced_python_wrapper.errors import AwsWrapperError
2828
from aws_advanced_python_wrapper.host_selector import (
29-
HostSelector, RandomHostSelector, RoundRobinHostSelector,
30-
WeightedRandomHostSelector)
29+
HighestWeightHostSelector, HostSelector, RandomHostSelector,
30+
RoundRobinHostSelector, WeightedRandomHostSelector)
3131
from aws_advanced_python_wrapper.plugin import CanReleaseResources
3232
from aws_advanced_python_wrapper.utils.messages import Messages
3333
from aws_advanced_python_wrapper.utils.properties import (Properties,
@@ -48,7 +48,8 @@ class SqlAlchemyPooledConnectionProvider(ConnectionProvider, CanReleaseResources
4848
_LEAST_CONNECTIONS: ClassVar[str] = "least_connections"
4949
_accepted_strategies: Dict[str, HostSelector] = {"random": RandomHostSelector(),
5050
"round_robin": RoundRobinHostSelector(),
51-
"weighted_random": WeightedRandomHostSelector()}
51+
"weighted_random": WeightedRandomHostSelector(),
52+
"highest_weight": HighestWeightHostSelector()}
5253
_rds_utils: ClassVar[RdsUtils] = RdsUtils()
5354
_database_pools: ClassVar[SlidingExpirationCache[PoolKey, QueuePool]] = SlidingExpirationCache(
5455
should_dispose_func=lambda queue_pool: queue_pool.checkedout() == 0,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
import pytest
16+
17+
from aws_advanced_python_wrapper.host_availability import HostAvailability
18+
from aws_advanced_python_wrapper.host_selector import HighestWeightHostSelector
19+
from aws_advanced_python_wrapper.hostinfo import HostInfo, HostRole
20+
from aws_advanced_python_wrapper.utils.properties import Properties
21+
22+
HOST_ROLE = HostRole.READER
23+
24+
25+
@pytest.mark.parametrize("execution_number", range(50))
26+
def test_get_host_given_unavailable_host(execution_number):
27+
unavailable_host: HostInfo = HostInfo(host="some_unavailable_host", role=HOST_ROLE, availability=HostAvailability.UNAVAILABLE)
28+
available_host: HostInfo = HostInfo(host="some_available_host", role=HOST_ROLE, availability=HostAvailability.AVAILABLE)
29+
30+
host_selector = HighestWeightHostSelector()
31+
actual_host = host_selector.get_host((unavailable_host, available_host), HOST_ROLE, Properties())
32+
33+
assert available_host == actual_host
34+
35+
36+
@pytest.mark.parametrize("execution_number", range(50))
37+
def test_get_host_given_multiple_unavailable_hosts(execution_number):
38+
hosts = (
39+
HostInfo(host="some_unavailable_host", role=HOST_ROLE, availability=HostAvailability.UNAVAILABLE),
40+
HostInfo(host="some_unavailable_host", role=HOST_ROLE, availability=HostAvailability.UNAVAILABLE),
41+
HostInfo(host="some_available_host", role=HOST_ROLE, availability=HostAvailability.AVAILABLE)
42+
)
43+
44+
host_selector = HighestWeightHostSelector()
45+
actual_host = host_selector.get_host(hosts, HOST_ROLE, Properties())
46+
47+
assert HostAvailability.AVAILABLE == actual_host.get_availability()
48+
49+
50+
@pytest.mark.parametrize("execution_number", range(50))
51+
def test_get_host_given_different_weights(execution_number):
52+
53+
highest_weight_host = HostInfo(host="some_available_host", role=HOST_ROLE, availability=HostAvailability.AVAILABLE, weight=3)
54+
55+
hosts = (
56+
HostInfo(host="some_unavailable_host", role=HOST_ROLE, availability=HostAvailability.UNAVAILABLE),
57+
HostInfo(host="some_unavailable_host", role=HOST_ROLE, availability=HostAvailability.UNAVAILABLE),
58+
HostInfo(host="some_available_host", role=HOST_ROLE, availability=HostAvailability.AVAILABLE, weight=1),
59+
HostInfo(host="some_available_host", role=HOST_ROLE, availability=HostAvailability.AVAILABLE, weight=2),
60+
highest_weight_host
61+
)
62+
63+
host_selector = HighestWeightHostSelector()
64+
actual_host = host_selector.get_host(hosts, HOST_ROLE, Properties())
65+
66+
assert actual_host == highest_weight_host

0 commit comments

Comments
 (0)