diff --git a/e2e_scripts b/e2e_scripts index b56178520..6b71cb72e 160000 --- a/e2e_scripts +++ b/e2e_scripts @@ -1 +1 @@ -Subproject commit b56178520fae446a0a4f38df6259deb845efa667 +Subproject commit 6b71cb72eb20a18ace82f9e73a0f99fe1141d625 diff --git a/linode_api4/objects/image.py b/linode_api4/objects/image.py index 931ed4a31..1215c422c 100644 --- a/linode_api4/objects/image.py +++ b/linode_api4/objects/image.py @@ -64,12 +64,8 @@ class Image(Base): def replicate(self, regions: Union[List[str], List[Region]]): """ - NOTE: Image replication may not currently be available to all users. - Replicate the image to other regions. - Note: Image replication may not currently be available to all users. - API Documentation: https://techdocs.akamai.com/linode-api/reference/post-replicate-image :param regions: A list of regions that the customer wants to replicate this image in. diff --git a/linode_api4/objects/placement.py b/linode_api4/objects/placement.py index aa894af33..e436cf701 100644 --- a/linode_api4/objects/placement.py +++ b/linode_api4/objects/placement.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, Union +from typing import List, Optional, Union from linode_api4.objects.base import Base, Property from linode_api4.objects.linode import Instance @@ -34,6 +34,26 @@ class PlacementGroupMember(JSONObject): is_compliant: bool = False +@dataclass +class MigratedInstance(JSONObject): + """ + The ID for a compute instance being migrated into or out of the placement group. + """ + + linode_id: int = 0 + + +@dataclass +class PlacementGroupMigrations(JSONObject): + """ + Any compute instances that are being migrated to or from the placement group. + Returns an empty object if no migrations are taking place. + """ + + inbound: Optional[List[MigratedInstance]] = None + outbound: Optional[List[MigratedInstance]] = None + + class PlacementGroup(Base): """ NOTE: Placement Groups may not currently be available to all users. @@ -54,6 +74,7 @@ class PlacementGroup(Base): "placement_group_policy": Property(), "is_compliant": Property(), "members": Property(json_object=PlacementGroupMember), + "migrations": Property(json_object=PlacementGroupMigrations), } def assign( diff --git a/test/fixtures/placement_groups.json b/test/fixtures/placement_groups.json index 758fc8521..bf05f9936 100644 --- a/test/fixtures/placement_groups.json +++ b/test/fixtures/placement_groups.json @@ -12,7 +12,19 @@ "linode_id": 123, "is_compliant": true } - ] + ], + "migrations": { + "inbound": [ + { + "linode_id": 123 + } + ], + "outbound": [ + { + "linode_id": 456 + } + ] + } } ], "page": 1, diff --git a/test/fixtures/placement_groups_123.json b/test/fixtures/placement_groups_123.json index 453e9fd5f..c7a9cab27 100644 --- a/test/fixtures/placement_groups_123.json +++ b/test/fixtures/placement_groups_123.json @@ -10,5 +10,17 @@ "linode_id": 123, "is_compliant": true } - ] + ], + "migrations": { + "inbound": [ + { + "linode_id": 123 + } + ], + "outbound": [ + { + "linode_id": 456 + } + ] + } } \ No newline at end of file diff --git a/test/integration/conftest.py b/test/integration/conftest.py index cb1305d68..ba4a2ee14 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -2,13 +2,17 @@ import os import random import time +from test.integration.helpers import ( + get_test_label, + send_request_when_resource_available, +) from typing import Optional, Set import pytest import requests from requests.exceptions import ConnectionError, RequestException -from linode_api4 import ApiError, PlacementGroupPolicy, PlacementGroupType +from linode_api4 import PlacementGroupPolicy, PlacementGroupType from linode_api4.linode_client import LinodeClient from linode_api4.objects import Region @@ -27,13 +31,6 @@ def get_api_url(): return os.environ.get(ENV_API_URL_NAME, "https://api.linode.com/v4beta") -def get_random_label(): - timestamp = str(time.time_ns())[:-5] - label = "label_" + timestamp - - return label - - def get_regions( client: LinodeClient, capabilities: Optional[Set[str]] = None, @@ -59,7 +56,7 @@ def get_regions( def get_region( - client: LinodeClient, capabilities: Set[str] = None, site_type: str = None + client: LinodeClient, capabilities: Set[str] = None, site_type: str = "core" ): return random.choice(get_regions(client, capabilities, site_type)) @@ -161,14 +158,12 @@ def create_inbound_rule(ipv4_address, ipv6_address): def create_linode(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") + label = get_test_label(length=8) linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian12", label=label, firewall=e2e_test_firewall, @@ -183,14 +178,12 @@ def create_linode(test_linode_client, e2e_test_firewall): def create_linode_for_pass_reset(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") + label = get_test_label(length=8) linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian10", label=label, firewall=e2e_test_firewall, @@ -271,36 +264,21 @@ def test_domain(test_linode_client): @pytest.fixture(scope="session") def test_volume(test_linode_client): client = test_linode_client - timestamp = str(time.time_ns()) - region = client.regions()[4] - label = "TestSDK-" + timestamp + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") + label = get_test_label(length=8) volume = client.volume_create(label=label, region=region) yield volume - timeout = 100 # give 100s for volume to be detached before deletion - - start_time = time.time() - - while time.time() - start_time < timeout: - try: - res = volume.delete() - if res: - break - else: - time.sleep(3) - except ApiError as e: - if time.time() - start_time > timeout: - raise e + send_request_when_resource_available(timeout=100, func=volume.delete) @pytest.fixture(scope="session") def test_volume_with_encryption(test_linode_client): client = test_linode_client - timestamp = str(time.time_ns()) region = get_region(client, {"Block Storage Encryption"}) - label = "TestSDK-" + timestamp + label = get_test_label(length=8) volume = client.volume_create( label=label, region=region, encryption="enabled" @@ -308,28 +286,14 @@ def test_volume_with_encryption(test_linode_client): yield volume - timeout = 100 # give 100s for volume to be detached before deletion - - start_time = time.time() - - while time.time() - start_time < timeout: - try: - res = volume.delete() - if res: - break - else: - time.sleep(3) - except ApiError as e: - if time.time() - start_time > timeout: - raise e + send_request_when_resource_available(timeout=100, func=volume.delete) @pytest.fixture def test_tag(test_linode_client): client = test_linode_client - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + label = get_test_label(length=8) tag = client.tag_create(label=label) @@ -342,11 +306,10 @@ def test_tag(test_linode_client): def test_nodebalancer(test_linode_client): client = test_linode_client - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + label = get_test_label(length=8) nodebalancer = client.nodebalancer_create( - region=get_region(client), label=label + region=get_region(client, capabilities={"NodeBalancers"}), label=label ) yield nodebalancer @@ -357,8 +320,7 @@ def test_nodebalancer(test_linode_client): @pytest.fixture def test_longview_client(test_linode_client): client = test_linode_client - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + label = get_test_label(length=8) longview_client = client.longview.client_create(label=label) yield longview_client @@ -370,7 +332,8 @@ def test_longview_client(test_linode_client): def test_sshkey(test_linode_client, ssh_key_gen): pub_key = ssh_key_gen[0] client = test_linode_client - key = client.profile.ssh_key_upload(pub_key, "IntTestSDK-sshkey") + key_label = get_test_label(8) + "_key" + key = client.profile.ssh_key_upload(pub_key, key_label) yield key @@ -380,7 +343,7 @@ def test_sshkey(test_linode_client, ssh_key_gen): @pytest.fixture def access_keys_object_storage(test_linode_client): client = test_linode_client - label = "TestSDK-obj-storage-key" + label = get_test_label(length=8) key = client.object_storage.keys_create(label) yield key @@ -398,8 +361,7 @@ def test_firewall(test_linode_client): "inbound_policy": "ACCEPT", } - timestamp = str(time.time_ns()) - label = "firewall_" + timestamp + label = get_test_label(8) + "_firewall" firewall = client.networking.firewall_create( label=label, rules=rules, status="enabled" @@ -413,7 +375,7 @@ def test_firewall(test_linode_client): @pytest.fixture def test_oauth_client(test_linode_client): client = test_linode_client - label = get_random_label() + "_oauth" + label = get_test_label(length=8) + "_oauth" oauth_client = client.account.oauth_client_create( label, "https://localhost/oauth/callback" @@ -428,10 +390,10 @@ def test_oauth_client(test_linode_client): def create_vpc(test_linode_client): client = test_linode_client - timestamp = str(int(time.time())) + label = get_test_label(length=10) vpc = client.vpcs.create( - "pythonsdk-" + timestamp, + label, get_region(test_linode_client, {"VPCs"}), description="test description", ) @@ -455,8 +417,7 @@ def create_vpc_with_subnet_and_linode( ): vpc, subnet = create_vpc_with_subnet - timestamp = str(int(time.time())) - label = "TestSDK-" + timestamp + label = get_test_label(length=8) instance, password = test_linode_client.linode.instance_create( "g6-standard-1", @@ -475,18 +436,18 @@ def create_vpc_with_subnet_and_linode( def create_multiple_vpcs(test_linode_client): client = test_linode_client - timestamp = str(int(time.time_ns() % 10**10)) + label = get_test_label(length=10) - timestamp_2 = str(int(time.time_ns() % 10**10)) + label_2 = get_test_label(length=10) vpc_1 = client.vpcs.create( - "pythonsdk-" + timestamp, + label, get_region(test_linode_client, {"VPCs"}), description="test description", ) vpc_2 = client.vpcs.create( - "pythonsdk-" + timestamp_2, + label_2, get_region(test_linode_client, {"VPCs"}), description="test description", ) @@ -502,10 +463,10 @@ def create_multiple_vpcs(test_linode_client): def create_placement_group(test_linode_client): client = test_linode_client - timestamp = str(int(time.time())) + label = get_test_label(10) pg = client.placement.group_create( - "pythonsdk-" + timestamp, + label, get_region(test_linode_client, {"Placement Group"}), PlacementGroupType.anti_affinity_local, PlacementGroupPolicy.flexible, diff --git a/test/integration/helpers.py b/test/integration/helpers.py index e874ea7e2..0ee9810a8 100644 --- a/test/integration/helpers.py +++ b/test/integration/helpers.py @@ -1,116 +1,59 @@ +import random import time +from string import ascii_lowercase from typing import Callable -from linode_api4 import PaginatedList from linode_api4.errors import ApiError -from linode_api4.linode_client import LinodeClient -def get_test_label(): - unique_timestamp = str(time.time_ns())[:-3] - label = "test_" + unique_timestamp - return label - - -def delete_instance_with_test_kw(paginated_list: PaginatedList): - for i in paginated_list: - try: - if hasattr(i, "label"): - label = getattr(i, "label") - if "IntTestSDK" in str(label): - i.delete() - elif "lke" in str(label): - iso_created_date = getattr(i, "created") - created_time = int( - time.mktime(iso_created_date.timetuple()) - ) - timestamp = int(time.time()) - if (timestamp - created_time) < 86400: - i.delete() - elif hasattr(i, "domain"): - domain = getattr(i, "domain") - if "IntTestSDK" in domain: - i.delete() - except AttributeError as e: - if "IntTestSDK" in str(i.__dict__): - i.delete() - - -def delete_all_test_instances(client: LinodeClient): - tags = client.tags() - linodes = client.linode.instances() - images = client.images() - volumes = client.volumes() - nodebalancers = client.nodebalancers() - domains = client.domains() - longview_clients = client.longview.clients() - clusters = client.lke.clusters() - firewalls = client.networking.firewalls() - - delete_instance_with_test_kw(tags) - delete_instance_with_test_kw(linodes) - delete_instance_with_test_kw(images) - delete_instance_with_test_kw(volumes) - delete_instance_with_test_kw(nodebalancers) - delete_instance_with_test_kw(domains) - delete_instance_with_test_kw(longview_clients) - delete_instance_with_test_kw(clusters) - delete_instance_with_test_kw(firewalls) +def get_test_label(length: int = 8): + return "".join(random.choice(ascii_lowercase) for i in range(length)) def wait_for_condition( interval: int, timeout: int, condition: Callable, *args ) -> object: - start_time = time.time() - while True: - if condition(*args): - break - - if time.time() - start_time > timeout: - raise TimeoutError("Wait for condition timeout error") - + end_time = time.time() + timeout + while time.time() < end_time: + result = condition(*args) + if result: + return result time.sleep(interval) + raise TimeoutError( + f"Timeout Error: resource not available in {timeout} seconds" + ) # Retry function to help in case of requests sending too quickly before instance is ready def retry_sending_request( - retries: int, condition: Callable, *args, **kwargs + retries: int, condition: Callable, *args, backoff: int = 5, **kwargs ) -> object: - curr_t = 0 - while curr_t < retries: + for attempt in range(1, retries + 1): try: - curr_t += 1 - res = condition(*args, **kwargs) - return res + return condition(*args, **kwargs) except ApiError: - if curr_t >= retries: - raise ApiError - time.sleep(5) + if attempt == retries: + raise ApiError( + "Api Error: Failed after all retry attempts" + ) from None + time.sleep(backoff) def send_request_when_resource_available( timeout: int, func: Callable, *args, **kwargs ) -> object: start_time = time.time() + retry_statuses = {400, 500} while True: try: - res = func(*args, **kwargs) - return res + return func(*args, **kwargs) except ApiError as e: - if ( - e.status == 400 - or e.status == 500 - or "Please try again later" in str(e.__dict__) - ): + if e.status in retry_statuses or "Please try again later" in str(e): if time.time() - start_time > timeout: raise TimeoutError( - "Timeout Error: resource is not available in " - + str(timeout) - + " seconds" + f"Timeout Error: resource not available in {timeout} seconds" ) time.sleep(10) else: raise e - - return res diff --git a/test/integration/linode_client/test_linode_client.py b/test/integration/linode_client/test_linode_client.py index 105535211..2802c90f9 100644 --- a/test/integration/linode_client/test_linode_client.py +++ b/test/integration/linode_client/test_linode_client.py @@ -12,15 +12,13 @@ @pytest.fixture(scope="session") def setup_client_and_linode(test_linode_client, e2e_test_firewall): client = test_linode_client - chosen_region = get_region( - client, {"Kubernetes", "NodeBalancers"}, "core" - ).id + region = get_region(client, {"Kubernetes", "NodeBalancers"}, "core").id label = get_test_label() linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian10", label=label, firewall=e2e_test_firewall, @@ -233,11 +231,11 @@ def test_get_account_settings(test_linode_client): # LinodeGroupTests def test_create_linode_instance_without_image(test_linode_client): client = test_linode_client - chosen_region = get_region(client, {"Linodes"}, "core").id + region = get_region(client, {"Linodes"}, "core").id label = get_test_label() linode_instance = client.linode.instance_create( - "g6-nanode-1", chosen_region, label=label + "g6-nanode-1", region, label=label ) assert linode_instance.label == label @@ -257,12 +255,12 @@ def test_create_linode_instance_with_image(setup_client_and_linode): def test_create_linode_with_interfaces(test_linode_client): client = test_linode_client - chosen_region = get_region(client, {"Vlans", "Linodes"}).id + region = get_region(client, {"Vlans", "Linodes"}, site_type="core").id label = get_test_label() linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, label=label, image="linode/debian10", interfaces=[ diff --git a/test/integration/models/account/test_account.py b/test/integration/models/account/test_account.py index ab20ee079..8bdc8c60e 100644 --- a/test/integration/models/account/test_account.py +++ b/test/integration/models/account/test_account.py @@ -1,5 +1,6 @@ import time from datetime import datetime +from test.integration.conftest import get_region from test.integration.helpers import get_test_label import pytest @@ -64,13 +65,12 @@ def test_get_account_settings(test_linode_client): def test_latest_get_event(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") label = get_test_label() linode, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian10", label=label, firewall=e2e_test_firewall, diff --git a/test/integration/models/domain/test_domain.py b/test/integration/models/domain/test_domain.py index cf5a54710..36ecbb0dc 100644 --- a/test/integration/models/domain/test_domain.py +++ b/test/integration/models/domain/test_domain.py @@ -42,7 +42,7 @@ def get_zone_file_view(): def test_clone(test_linode_client, test_domain): domain = test_linode_client.load(Domain, test_domain.id) timestamp = str(time.time_ns()) - dom = "example.clone-" + timestamp + "-IntTestSDK.org" + dom = "example.clone-" + timestamp + "-inttestsdk.org" domain.clone(dom) ds = test_linode_client.domains() diff --git a/test/integration/models/firewall/test_firewall.py b/test/integration/models/firewall/test_firewall.py index 7f907cc2f..6a9f6f079 100644 --- a/test/integration/models/firewall/test_firewall.py +++ b/test/integration/models/firewall/test_firewall.py @@ -1,4 +1,5 @@ import time +from test.integration.conftest import get_region from test.integration.helpers import get_test_label import pytest @@ -9,12 +10,11 @@ @pytest.fixture(scope="session") def linode_fw(test_linode_client): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") label = get_test_label() linode_instance, password = client.linode.instance_create( - "g6-nanode-1", chosen_region, image="linode/debian10", label=label + "g6-nanode-1", region, image="linode/debian10", label=label ) yield linode_instance @@ -80,6 +80,5 @@ def test_get_device(test_linode_client, test_firewall, linode_fw): FirewallDevice, firewall.devices.first().id, firewall.id ) - assert "test_" in firewall_device.entity.label assert firewall_device.entity.type == "linode" assert "/v4/linode/instances/" in firewall_device.entity.url diff --git a/test/integration/models/image/test_image.py b/test/integration/models/image/test_image.py index 4c2aa77d2..94c819709 100644 --- a/test/integration/models/image/test_image.py +++ b/test/integration/models/image/test_image.py @@ -1,9 +1,6 @@ from io import BytesIO from test.integration.conftest import get_region, get_regions -from test.integration.helpers import ( - delete_instance_with_test_kw, - get_test_label, -) +from test.integration.helpers import get_test_label import polling import pytest @@ -15,7 +12,11 @@ def image_upload_url(test_linode_client): label = get_test_label() + "_image" - region = get_region(test_linode_client, site_type="core") + region = get_region( + test_linode_client, + capabilities={"Linodes", "Object Storage"}, + site_type="core", + ) test_linode_client.image_create_upload( label, region.id, "integration test image upload" @@ -26,8 +27,6 @@ def image_upload_url(test_linode_client): yield image image.delete() - images = test_linode_client.images() - delete_instance_with_test_kw(images) @pytest.fixture(scope="session") diff --git a/test/integration/models/linode/test_linode.py b/test/integration/models/linode/test_linode.py index 998a0c89a..a8ba2b21e 100644 --- a/test/integration/models/linode/test_linode.py +++ b/test/integration/models/linode/test_linode.py @@ -25,8 +25,7 @@ @pytest.fixture(scope="session") def linode_with_volume_firewall(test_linode_client): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") label = get_test_label() rules = { @@ -38,7 +37,7 @@ def linode_with_volume_firewall(test_linode_client): linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian10", label=label + "_modlinode", ) @@ -71,14 +70,12 @@ def linode_with_volume_firewall(test_linode_client): @pytest.fixture(scope="session") def linode_for_network_interface_tests(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") + label = get_test_label(length=8) linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian10", label=label, firewall=e2e_test_firewall, @@ -92,13 +89,12 @@ def linode_for_network_interface_tests(test_linode_client, e2e_test_firewall): @pytest.fixture def linode_for_disk_tests(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") label = get_test_label() linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/alpine3.19", label=label + "_long_tests", firewall=e2e_test_firewall, @@ -126,12 +122,12 @@ def linode_for_disk_tests(test_linode_client, e2e_test_firewall): @pytest.fixture def linode_with_block_storage_encryption(test_linode_client, e2e_test_firewall): client = test_linode_client - chosen_region = get_region(client, {"Linodes", "Block Storage Encryption"}) + region = get_region(client, {"Linodes", "Block Storage Encryption"}) label = get_test_label() linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/alpine3.19", label=label + "block-storage-encryption", firewall=e2e_test_firewall, @@ -145,13 +141,12 @@ def linode_with_block_storage_encryption(test_linode_client, e2e_test_firewall): @pytest.fixture def create_linode_for_long_running_tests(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") label = get_test_label() linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian10", label=label + "_long_tests", firewall=e2e_test_firewall, @@ -167,15 +162,14 @@ def linode_with_disk_encryption(test_linode_client, request): client = test_linode_client target_region = get_region(client, {"Disk Encryption"}) - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + label = get_test_label(length=8) disk_encryption = request.param linode_instance, password = client.linode.instance_create( "g6-nanode-1", target_region, - image="linode/ubuntu23.04", + image="linode/ubuntu23.10", label=label, booted=False, disk_encryption=disk_encryption, @@ -215,14 +209,12 @@ def test_linode_transfer(test_linode_client, linode_with_volume_firewall): def test_linode_rebuild(test_linode_client): client = test_linode_client - # TODO(LDE): Uncomment once LDE is available - # chosen_region = get_region(client, {"Disk Encryption"}) - chosen_region = get_region(client) + region = get_region(client, {"Disk Encryption"}) label = get_test_label() + "_rebuild" linode, password = client.linode.instance_create( - "g6-nanode-1", chosen_region, image="linode/debian10", label=label + "g6-nanode-1", region, image="linode/debian10", label=label ) wait_for_condition(10, 100, get_status, linode, "running") @@ -231,8 +223,7 @@ def test_linode_rebuild(test_linode_client): 3, linode.rebuild, "linode/debian10", - # TODO(LDE): Uncomment once LDE is available - # disk_encryption=InstanceDiskEncryptionType.disabled, + disk_encryption=InstanceDiskEncryptionType.disabled, ) wait_for_condition(10, 100, get_status, linode, "rebuilding") @@ -240,8 +231,7 @@ def test_linode_rebuild(test_linode_client): assert linode.status == "rebuilding" assert linode.image.id == "linode/debian10" - # TODO(LDE): Uncomment once LDE is available - # assert linode.disk_encryption == InstanceDiskEncryptionType.disabled + assert linode.disk_encryption == InstanceDiskEncryptionType.disabled wait_for_condition(10, 300, get_status, linode, "running") @@ -276,13 +266,12 @@ def test_update_linode(create_linode): def test_delete_linode(test_linode_client): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") label = get_test_label() linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian10", label=label + "_linode", ) @@ -372,6 +361,7 @@ def test_linode_resize_with_class( assert linode.status == "running" +@pytest.mark.flaky(reruns=3, reruns_delay=2) def test_linode_resize_with_migration_type( test_linode_client, create_linode_for_long_running_tests, @@ -433,7 +423,7 @@ def test_linode_firewalls(linode_with_volume_firewall): firewalls = linode.firewalls() assert len(firewalls) > 0 - assert "test" in firewalls[0].label + assert "firewall" in firewalls[0].label def test_linode_volumes(linode_with_volume_firewall): @@ -442,11 +432,9 @@ def test_linode_volumes(linode_with_volume_firewall): volumes = linode.volumes() assert len(volumes) > 0 - assert "test" in volumes[0].label + assert "_volume" in volumes[0].label -# TODO(LDE): Remove skip once LDE is available -@pytest.mark.skip("LDE is not currently enabled") @pytest.mark.parametrize( "linode_with_disk_encryption", ["disabled"], indirect=True ) @@ -541,13 +529,12 @@ def test_linode_ips(create_linode): def test_linode_initate_migration(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") label = get_test_label() + "_migration" linode, _ = client.linode.instance_create( "g6-nanode-1", - chosen_region, + region, image="linode/debian12", label=label, firewall=e2e_test_firewall, diff --git a/test/integration/models/lke/test_lke.py b/test/integration/models/lke/test_lke.py index bd0692dcc..4a3ba6c7e 100644 --- a/test/integration/models/lke/test_lke.py +++ b/test/integration/models/lke/test_lke.py @@ -23,6 +23,7 @@ LKENodePoolTaint, LKEType, ) +from linode_api4.objects.linode import InstanceDiskEncryptionType @pytest.fixture(scope="session") @@ -30,9 +31,7 @@ def lke_cluster(test_linode_client): node_type = test_linode_client.linode.types()[1] # g6-standard-1 version = test_linode_client.lke.versions()[0] - # TODO(LDE): Uncomment once LDE is available - # region = get_region(test_linode_client, {"Kubernetes", "Disk Encryption"}) - region = get_region(test_linode_client, {"Kubernetes"}) + region = get_region(test_linode_client, {"Kubernetes", "Disk Encryption"}) node_pools = test_linode_client.lke.node_pool(node_type, 3) label = get_test_label() + "_cluster" @@ -146,8 +145,7 @@ def _to_comparable(p: LKENodePool) -> Dict[str, Any]: assert _to_comparable(cluster.pools[0]) == _to_comparable(pool) - # TODO(LDE): Uncomment once LDE is available - # assert pool.disk_encryption == InstanceDiskEncryptionType.enabled + assert pool.disk_encryption == InstanceDiskEncryptionType.enabled def test_cluster_dashboard_url_view(lke_cluster): diff --git a/test/integration/models/longview/test_longview.py b/test/integration/models/longview/test_longview.py index f04875e63..6a6855460 100644 --- a/test/integration/models/longview/test_longview.py +++ b/test/integration/models/longview/test_longview.py @@ -1,5 +1,6 @@ import re import time +from test.integration.helpers import get_test_label import pytest @@ -22,7 +23,7 @@ def test_update_longview_label(test_linode_client, test_longview_client): longview = test_linode_client.load(LongviewClient, test_longview_client.id) old_label = longview.label - label = "updated_longview_label" + label = get_test_label(10) longview.label = label @@ -33,7 +34,7 @@ def test_update_longview_label(test_linode_client, test_longview_client): def test_delete_client(test_linode_client, test_longview_client): client = test_linode_client - label = "TestSDK-longview" + label = get_test_label(length=8) longview_client = client.longview.client_create(label=label) time.sleep(5) diff --git a/test/integration/models/networking/test_networking.py b/test/integration/models/networking/test_networking.py index a52f38ef2..430bd94b9 100644 --- a/test/integration/models/networking/test_networking.py +++ b/test/integration/models/networking/test_networking.py @@ -1,32 +1,36 @@ +from test.integration.conftest import ( + get_api_ca_file, + get_api_url, + get_region, + get_token, +) from test.integration.helpers import get_test_label import pytest +from linode_api4 import LinodeClient from linode_api4.objects import Config, ConfigInterfaceIPv4, Firewall, IPAddress from linode_api4.objects.networking import NetworkTransferPrice, Price - -@pytest.mark.smoke -def test_get_networking_rules(test_linode_client, test_firewall): - firewall = test_linode_client.load(Firewall, test_firewall.id) - - rules = firewall.get_rules() - - assert "inbound" in str(rules) - assert "inbound_policy" in str(rules) - assert "outbound" in str(rules) - assert "outbound_policy" in str(rules) +TEST_REGION = get_region( + LinodeClient( + token=get_token(), + base_url=get_api_url(), + ca_path=get_api_ca_file(), + ), + {"Linodes", "Cloud Firewall"}, + site_type="core", +) def create_linode(test_linode_client): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] + label = get_test_label() linode_instance, _ = client.linode.instance_create( "g6-nanode-1", - chosen_region, + TEST_REGION, image="linode/debian12", label=label, ) @@ -52,6 +56,18 @@ def create_linode_to_be_shared_with_ips(test_linode_client): linode.delete() +@pytest.mark.smoke +def test_get_networking_rules(test_linode_client, test_firewall): + firewall = test_linode_client.load(Firewall, test_firewall.id) + + rules = firewall.get_rules() + + assert "inbound" in str(rules) + assert "inbound_policy" in str(rules) + assert "outbound" in str(rules) + assert "outbound_policy" in str(rules) + + @pytest.mark.smoke def test_ip_addresses_share( test_linode_client, diff --git a/test/integration/models/nodebalancer/test_nodebalancer.py b/test/integration/models/nodebalancer/test_nodebalancer.py index 5581c9029..d8a8a53b1 100644 --- a/test/integration/models/nodebalancer/test_nodebalancer.py +++ b/test/integration/models/nodebalancer/test_nodebalancer.py @@ -1,8 +1,15 @@ import re +from test.integration.conftest import ( + get_api_ca_file, + get_api_url, + get_region, + get_token, +) +from test.integration.helpers import get_test_label import pytest -from linode_api4 import ApiError +from linode_api4 import ApiError, LinodeClient from linode_api4.objects import ( NodeBalancerConfig, NodeBalancerNode, @@ -10,17 +17,25 @@ RegionPrice, ) +TEST_REGION = get_region( + LinodeClient( + token=get_token(), + base_url=get_api_url(), + ca_path=get_api_ca_file(), + ), + {"Linodes", "Cloud Firewall", "NodeBalancers"}, + site_type="core", +) + @pytest.fixture(scope="session") def linode_with_private_ip(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] - label = "linode_with_privateip" + label = get_test_label(8) linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + TEST_REGION, image="linode/debian10", label=label, private_ip=True, @@ -35,12 +50,10 @@ def linode_with_private_ip(test_linode_client, e2e_test_firewall): @pytest.fixture(scope="session") def create_nb_config(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] - label = "nodebalancer_test" + label = get_test_label(8) nb = client.nodebalancer_create( - region=chosen_region, label=label, firewall=e2e_test_firewall.id + region=TEST_REGION, label=label, firewall=e2e_test_firewall.id ) config = nb.config_create() diff --git a/test/integration/models/placement/test_placement.py b/test/integration/models/placement/test_placement.py index db570aa9e..21c6519f5 100644 --- a/test/integration/models/placement/test_placement.py +++ b/test/integration/models/placement/test_placement.py @@ -1,6 +1,18 @@ +from test.integration.conftest import get_region +from test.integration.helpers import ( + get_test_label, + send_request_when_resource_available, +) + import pytest -from linode_api4 import PlacementGroup +from linode_api4 import ( + MigratedInstance, + MigrationType, + PlacementGroup, + PlacementGroupPolicy, + PlacementGroupType, +) @pytest.mark.smoke @@ -48,3 +60,54 @@ def test_pg_assignment(test_linode_client, create_placement_group_with_linode): assert pg.members[0].linode_id == inst.id assert inst.placement_group.id == pg.id + + +def test_pg_migration( + test_linode_client, e2e_test_firewall, create_placement_group +): + """ + Tests that an instance can be migrated into and our of PGs successfully. + """ + client = test_linode_client + + label_pg = get_test_label(10) + + label_instance = get_test_label(10) + + pg_outbound = client.placement.group_create( + label_pg, + get_region(test_linode_client, {"Placement Group"}), + PlacementGroupType.anti_affinity_local, + PlacementGroupPolicy.flexible, + ) + + linode = client.linode.instance_create( + "g6-nanode-1", + pg_outbound.region, + label=label_instance, + placement_group=pg_outbound, + ) + + pg_inbound = create_placement_group + + # Says it could take up to ~6 hrs for migration to fully complete + send_request_when_resource_available( + 300, + linode.initiate_migration, + placement_group=pg_inbound.id, + migration_type=MigrationType.COLD, + region=pg_inbound.region, + ) + + pg_inbound = test_linode_client.load(PlacementGroup, pg_inbound.id) + pg_outbound = test_linode_client.load(PlacementGroup, pg_outbound.id) + + assert pg_inbound.migrations.inbound[0] == MigratedInstance( + linode_id=linode.id + ) + assert pg_outbound.migrations.outbound[0] == MigratedInstance( + linode_id=linode.id + ) + + linode.delete() + pg_outbound.delete() diff --git a/test/integration/models/volume/test_volume.py b/test/integration/models/volume/test_volume.py index 19bc55c26..6588d92a7 100644 --- a/test/integration/models/volume/test_volume.py +++ b/test/integration/models/volume/test_volume.py @@ -1,28 +1,54 @@ import time -from test.integration.conftest import get_token +from test.integration.conftest import ( + get_api_ca_file, + get_api_url, + get_region, + get_token, +) from test.integration.helpers import ( get_test_label, retry_sending_request, + send_request_when_resource_available, wait_for_condition, ) import pytest -from linode_api4 import ApiError, LinodeClient +from linode_api4 import LinodeClient from linode_api4.objects import RegionPrice, Volume, VolumeType +TEST_REGION = get_region( + LinodeClient( + token=get_token(), + base_url=get_api_url(), + ca_path=get_api_ca_file(), + ), + {"Linodes", "Cloud Firewall"}, + site_type="core", +) + + +@pytest.fixture(scope="session") +def test_volume(test_linode_client): + client = test_linode_client + label = get_test_label(length=8) + + volume = client.volume_create(label=label, region=TEST_REGION) + + yield volume + + send_request_when_resource_available(timeout=100, func=volume.delete) + @pytest.fixture(scope="session") def linode_for_volume(test_linode_client, e2e_test_firewall): client = test_linode_client - available_regions = client.regions() - chosen_region = available_regions[4] - timestamp = str(time.time_ns()) - label = "TestSDK-" + timestamp + + label = get_test_label(length=8) linode_instance, password = client.linode.instance_create( "g6-nanode-1", - chosen_region, + TEST_REGION, image="linode/debian10", label=label, firewall=e2e_test_firewall, @@ -30,25 +56,17 @@ def linode_for_volume(test_linode_client, e2e_test_firewall): yield linode_instance - timeout = 100 # give 100s for volume to be detached before deletion - - start_time = time.time() - - while time.time() - start_time < timeout: - try: - res = linode_instance.delete() - - if res: - break - else: - time.sleep(3) - except ApiError as e: - if time.time() - start_time > timeout: - raise e + send_request_when_resource_available( + timeout=100, func=linode_instance.delete + ) def get_status(volume: Volume, status: str): - client = LinodeClient(token=get_token()) + client = LinodeClient( + token=get_token(), + base_url=get_api_url(), + ca_path=get_api_ca_file(), + ) volume = client.load(Volume, volume.id) return volume.status == status @@ -71,15 +89,15 @@ def test_get_volume_with_encryption( def test_update_volume_tag(test_linode_client, test_volume): volume = test_volume - tag_1 = "volume_test_tag1" - tag_2 = "volume_test_tag2" + tag_1 = get_test_label(10) + tag_2 = get_test_label(10) volume.tags = [tag_1, tag_2] volume.save() volume = test_linode_client.load(Volume, test_volume.id) - assert [tag_1, tag_2] == volume.tags + assert all(tag in volume.tags for tag in [tag_1, tag_2]) def test_volume_resize(test_linode_client, test_volume): @@ -113,7 +131,7 @@ def test_attach_volume_to_linode( volume = test_volume linode = linode_for_volume - res = retry_sending_request(5, volume.attach, linode.id) + res = retry_sending_request(5, volume.attach, linode.id, backoff=30) assert res diff --git a/test/unit/objects/placement_test.py b/test/unit/objects/placement_test.py index 71d171644..4e5960e7b 100644 --- a/test/unit/objects/placement_test.py +++ b/test/unit/objects/placement_test.py @@ -2,6 +2,7 @@ from linode_api4 import PlacementGroupPolicy from linode_api4.objects import ( + MigratedInstance, PlacementGroup, PlacementGroupMember, PlacementGroupType, @@ -116,3 +117,5 @@ def validate_pg_123(self, pg: PlacementGroup): assert pg.members[0] == PlacementGroupMember( linode_id=123, is_compliant=True ) + assert pg.migrations.inbound[0] == MigratedInstance(linode_id=123) + assert pg.migrations.outbound[0] == MigratedInstance(linode_id=456)