2022-06-04 11:30:16 +00:00
|
|
|
import random
|
|
|
|
import time
|
2023-11-30 15:55:51 +00:00
|
|
|
from threading import Thread
|
2023-12-14 21:35:36 +00:00
|
|
|
from typing import Any, List, Optional
|
2022-06-04 11:30:16 +00:00
|
|
|
|
2023-11-30 15:55:51 +00:00
|
|
|
import pytest
|
2022-08-13 09:49:43 +00:00
|
|
|
|
|
|
|
from moto.autoscaling.models import AutoScalingBackend
|
2024-01-17 10:05:37 +00:00
|
|
|
from moto.core import DEFAULT_ACCOUNT_ID
|
|
|
|
from moto.core.base_backend import AccountSpecificBackend, BackendDict, BaseBackend
|
2022-08-13 09:49:43 +00:00
|
|
|
from moto.ec2.models import EC2Backend
|
|
|
|
from moto.elbv2.models import ELBv2Backend
|
|
|
|
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
class ExampleBackend(BaseBackend):
|
2023-12-14 21:35:36 +00:00
|
|
|
def __init__(self, region_name: str, account_id: str):
|
2022-06-04 11:30:16 +00:00
|
|
|
super().__init__(region_name, account_id)
|
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_backend_dict_returns_nothing_by_default() -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
backend_dict = BackendDict(ExampleBackend, "ebs")
|
2023-07-10 21:04:31 +00:00
|
|
|
assert list(backend_dict.items()) == []
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_account_specific_dict_contains_known_regions() -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
backend_dict = BackendDict(ExampleBackend, "ec2")
|
2023-07-10 21:04:31 +00:00
|
|
|
assert isinstance(backend_dict["account"]["eu-north-1"], ExampleBackend)
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_backend_dict_does_not_contain_unknown_regions() -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
backend_dict = BackendDict(ExampleBackend, "ec2")
|
2023-07-10 21:04:31 +00:00
|
|
|
assert "mars-south-1" not in backend_dict["account"]
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_backend_dict_fails_when_retrieving_unknown_regions() -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
backend_dict = BackendDict(ExampleBackend, "ec2")
|
|
|
|
with pytest.raises(KeyError):
|
2022-08-13 09:49:43 +00:00
|
|
|
backend_dict["account"]["mars-south-1"] # pylint: disable=pointless-statement
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_backend_dict_can_retrieve_for_specific_account() -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
backend_dict = BackendDict(ExampleBackend, "ec2")
|
|
|
|
|
2022-08-13 09:49:43 +00:00
|
|
|
# Random account does not exist
|
2023-07-10 21:04:31 +00:00
|
|
|
assert "000000" not in backend_dict
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
# Retrieve AccountSpecificBackend by assuming it exists
|
|
|
|
backend = backend_dict["012345"]
|
2023-07-10 21:04:31 +00:00
|
|
|
assert isinstance(backend, AccountSpecificBackend)
|
2022-06-04 11:30:16 +00:00
|
|
|
|
2023-07-10 21:04:31 +00:00
|
|
|
assert "eu-north-1" in backend
|
2022-06-04 11:30:16 +00:00
|
|
|
regional_backend = backend["eu-north-1"]
|
2023-07-10 21:04:31 +00:00
|
|
|
assert isinstance(regional_backend, ExampleBackend)
|
|
|
|
assert regional_backend.region_name == "eu-north-1"
|
2022-06-04 11:30:16 +00:00
|
|
|
# We always return a fixed account_id for now, until we have proper multi-account support
|
2023-07-10 21:04:31 +00:00
|
|
|
assert regional_backend.account_id == "012345"
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_backend_dict_can_ignore_boto3_regions() -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
backend_dict = BackendDict(ExampleBackend, "ec2", use_boto3_regions=False)
|
2023-07-10 21:04:31 +00:00
|
|
|
assert backend_dict["account"].get("us-east-1") is None
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_backend_dict_can_specify_additional_regions() -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
backend_dict = BackendDict(
|
|
|
|
ExampleBackend, "ec2", additional_regions=["region1", "global"]
|
2022-08-13 09:49:43 +00:00
|
|
|
)["123456"]
|
2023-07-10 21:04:31 +00:00
|
|
|
assert isinstance(backend_dict["us-east-1"], ExampleBackend)
|
|
|
|
assert isinstance(backend_dict["region1"], ExampleBackend)
|
|
|
|
assert isinstance(backend_dict["global"], ExampleBackend)
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
# Unknown regions still do not exist
|
2023-07-10 21:04:31 +00:00
|
|
|
assert backend_dict.get("us-east-3") is None
|
2022-06-04 11:30:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestMultiThreadedAccess:
|
|
|
|
class SlowExampleBackend(BaseBackend):
|
2023-12-14 21:35:36 +00:00
|
|
|
def __init__(self, region_name: str, account_id: str):
|
2022-06-04 11:30:16 +00:00
|
|
|
super().__init__(region_name, account_id)
|
|
|
|
time.sleep(0.1)
|
2023-12-14 21:35:36 +00:00
|
|
|
self.data: List[int] = []
|
2022-06-04 11:30:16 +00:00
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def setup_method(self) -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
self.backend = BackendDict(TestMultiThreadedAccess.SlowExampleBackend, "ec2")
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_access_a_slow_backend_concurrently(self) -> None:
|
2022-08-13 09:49:43 +00:00
|
|
|
"""None
|
2022-06-04 11:30:16 +00:00
|
|
|
Usecase that we want to avoid:
|
|
|
|
|
|
|
|
Thread 1 comes in, and sees the backend does not exist for this region
|
|
|
|
Thread 1 starts creating the backend
|
|
|
|
Thread 2 comes in, and sees the backend does not exist for this region
|
|
|
|
Thread 2 starts creating the backend
|
|
|
|
Thread 1 finishes creating the backend, initializes the list and adds a new value to it
|
|
|
|
Thread 2 finishes creating the backend, re-initializes the list and adds a new value to it
|
|
|
|
|
|
|
|
Creating the Backend for a region should only be invoked once at a time, and the execution flow should look like:
|
|
|
|
|
|
|
|
Thread 1 comes in, and sees the backend does not exist for this region
|
|
|
|
Thread 1 starts creating the backend
|
|
|
|
Thread 2 comes in and is blocked
|
|
|
|
Thread 1 finishes creating the backend, initializes the list and adds a new value to it
|
|
|
|
Thread 2 gains access, and re-uses the created backend
|
|
|
|
Thread 2 adds a new value to the list
|
|
|
|
"""
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def access(random_number: int) -> None:
|
2022-06-04 11:30:16 +00:00
|
|
|
self.backend["123456789012"]["us-east-1"].data.append(random_number)
|
|
|
|
|
|
|
|
threads = []
|
|
|
|
|
|
|
|
for _ in range(0, 15):
|
|
|
|
x = Thread(target=access, args=(random.randint(100, 200),))
|
|
|
|
x.start()
|
|
|
|
threads.append(x)
|
|
|
|
|
|
|
|
for x in threads:
|
|
|
|
x.join()
|
|
|
|
|
2023-07-10 21:04:31 +00:00
|
|
|
assert len(self.backend["123456789012"]["us-east-1"].data) == 15
|
2022-08-13 09:49:43 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_backend_dict_can_be_hashed() -> None:
|
2022-08-13 09:49:43 +00:00
|
|
|
hashes = []
|
|
|
|
for backend in [ExampleBackend, set, list, BaseBackend]:
|
|
|
|
hashes.append(BackendDict(backend, "n/a").__hash__())
|
|
|
|
# Hash is different for different backends
|
2023-07-10 21:04:31 +00:00
|
|
|
assert len(set(hashes)) == 4
|
2022-08-13 09:49:43 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_account_specific_dict_can_be_hashed() -> None:
|
2022-08-13 09:49:43 +00:00
|
|
|
hashes = []
|
|
|
|
ids = ["01234567912", "01234567911", "01234567913", "000000000000", "0"]
|
|
|
|
for accnt_id in ids:
|
|
|
|
asb = _create_asb(accnt_id)
|
|
|
|
hashes.append(asb.__hash__())
|
|
|
|
# Hash is different for different accounts
|
2023-07-10 21:04:31 +00:00
|
|
|
assert len(set(hashes)) == 5
|
2022-08-13 09:49:43 +00:00
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def _create_asb(
|
|
|
|
account_id: str,
|
|
|
|
backend: Any = None,
|
|
|
|
use_boto3_regions: bool = False,
|
|
|
|
regions: Optional[List[str]] = None,
|
|
|
|
) -> Any:
|
2022-08-13 09:49:43 +00:00
|
|
|
return AccountSpecificBackend(
|
|
|
|
service_name="ec2",
|
|
|
|
account_id=account_id,
|
|
|
|
backend=backend or ExampleBackend,
|
|
|
|
use_boto3_regions=use_boto3_regions,
|
|
|
|
additional_regions=regions,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-12-14 21:35:36 +00:00
|
|
|
def test_multiple_backends_cache_behaviour() -> None:
|
2022-08-13 09:49:43 +00:00
|
|
|
ec2 = BackendDict(EC2Backend, "ec2")
|
|
|
|
ec2_useast1 = ec2[DEFAULT_ACCOUNT_ID]["us-east-1"]
|
|
|
|
assert type(ec2_useast1) == EC2Backend
|
|
|
|
|
|
|
|
autoscaling = BackendDict(AutoScalingBackend, "autoscaling")
|
|
|
|
as_1 = autoscaling[DEFAULT_ACCOUNT_ID]["us-east-1"]
|
|
|
|
assert type(as_1) == AutoScalingBackend
|
|
|
|
|
|
|
|
from moto.elbv2 import elbv2_backends
|
|
|
|
|
|
|
|
elbv2_useast = elbv2_backends["00000000"]["us-east-1"]
|
|
|
|
assert type(elbv2_useast) == ELBv2Backend
|
|
|
|
elbv2_useast2 = elbv2_backends[DEFAULT_ACCOUNT_ID]["us-east-2"]
|
|
|
|
assert type(elbv2_useast2) == ELBv2Backend
|
|
|
|
|
|
|
|
ec2_useast1 = ec2[DEFAULT_ACCOUNT_ID]["us-east-1"]
|
|
|
|
assert type(ec2_useast1) == EC2Backend
|
|
|
|
ec2_useast2 = ec2[DEFAULT_ACCOUNT_ID]["us-east-2"]
|
|
|
|
assert type(ec2_useast2) == EC2Backend
|
|
|
|
|
|
|
|
as_1 = autoscaling[DEFAULT_ACCOUNT_ID]["us-east-1"]
|
|
|
|
assert type(as_1) == AutoScalingBackend
|