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/models/placement/test_placement.py b/test/integration/models/placement/test_placement.py index db570aa9e..af853a2ea 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,52 @@ 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 = get_test_label(10) + + pg_outbound = client.placement.group_create( + label, + 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=create_placement_group.label, + 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/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)