@@ -335,6 +335,18 @@ def subnet(self) -> VPCSubnet:
335335 return VPCSubnet (self ._client , self .subnet_id , self .vpc_id )
336336
337337
338+ @dataclass
339+ class InstancePlacementGroupAssignment (JSONObject ):
340+ """
341+ Represents an assignment between an instance and a Placement Group.
342+ This is intended to be used when creating, cloning, and migrating
343+ instances.
344+ """
345+
346+ id : int
347+ compliant_only : bool = False
348+
349+
338350@dataclass
339351class ConfigInterface (JSONObject ):
340352 """
@@ -870,6 +882,37 @@ def transfer(self):
870882
871883 return self ._transfer
872884
885+ @property
886+ def placement_group (self ) -> Optional ["PlacementGroup" ]:
887+ """
888+ Returns the PlacementGroup object for the Instance.
889+
890+ :returns: The Placement Group this instance is under.
891+ :rtype: Optional[PlacementGroup]
892+ """
893+ # Workaround to avoid circular import
894+ from linode_api4 .objects .placement import ( # pylint: disable=import-outside-toplevel
895+ PlacementGroup ,
896+ )
897+
898+ if not hasattr (self , "_placement_group" ):
899+ # Refresh the instance if necessary
900+ if not self ._populated :
901+ self ._api_get ()
902+
903+ pg_data = self ._raw_json .get ("placement_group" , None )
904+
905+ if pg_data is None :
906+ return None
907+
908+ setattr (
909+ self ,
910+ "_placement_group" ,
911+ PlacementGroup (self ._client , pg_data .get ("id" ), json = pg_data ),
912+ )
913+
914+ return self ._placement_group
915+
873916 def _populate (self , json ):
874917 if json is not None :
875918 # fixes ipv4 and ipv6 attribute of json to make base._populate work
@@ -885,11 +928,16 @@ def invalidate(self):
885928 """Clear out cached properties"""
886929 if hasattr (self , "_avail_backups" ):
887930 del self ._avail_backups
931+
888932 if hasattr (self , "_ips" ):
889933 del self ._ips
934+
890935 if hasattr (self , "_transfer" ):
891936 del self ._transfer
892937
938+ if hasattr (self , "_placement_group" ):
939+ del self ._placement_group
940+
893941 Base .invalidate (self )
894942
895943 def boot (self , config = None ):
@@ -1471,6 +1519,9 @@ def initiate_migration(
14711519 region = None ,
14721520 upgrade = None ,
14731521 migration_type : MigrationType = MigrationType .COLD ,
1522+ placement_group : Union [
1523+ InstancePlacementGroupAssignment , Dict [str , Any ], int
1524+ ] = None ,
14741525 ):
14751526 """
14761527 Initiates a pending migration that is already scheduled for this Linode
@@ -1496,12 +1547,19 @@ def initiate_migration(
14961547 :param migration_type: The type of migration that will be used for this Linode migration.
14971548 Customers can only use this param when activating a support-created migration.
14981549 Customers can choose between a cold and warm migration, cold is the default type.
1499- :type: mirgation_type: str
1550+ :type: migration_type: str
1551+
1552+ :param placement_group: Information about the placement group to create this instance under.
1553+ :type placement_group: Union[InstancePlacementGroupAssignment, Dict[str, Any], int]
15001554 """
1555+
15011556 params = {
15021557 "region" : region .id if issubclass (type (region ), Base ) else region ,
15031558 "upgrade" : upgrade ,
15041559 "type" : migration_type ,
1560+ "placement_group" : _expand_placement_group_assignment (
1561+ placement_group
1562+ ),
15051563 }
15061564
15071565 util .drop_null_keys (params )
@@ -1583,6 +1641,12 @@ def clone(
15831641 label = None ,
15841642 group = None ,
15851643 with_backups = None ,
1644+ placement_group : Union [
1645+ InstancePlacementGroupAssignment ,
1646+ "PlacementGroup" ,
1647+ Dict [str , Any ],
1648+ int ,
1649+ ] = None ,
15861650 ):
15871651 """
15881652 Clones this linode into a new linode or into a new linode in the given region
@@ -1618,6 +1682,9 @@ def clone(
16181682 enrolled in the Linode Backup service. This will incur an additional charge.
16191683 :type: with_backups: bool
16201684
1685+ :param placement_group: Information about the placement group to create this instance under.
1686+ :type placement_group: Union[InstancePlacementGroupAssignment, PlacementGroup, Dict[str, Any], int]
1687+
16211688 :returns: The cloned Instance.
16221689 :rtype: Instance
16231690 """
@@ -1654,8 +1721,13 @@ def clone(
16541721 "label" : label ,
16551722 "group" : group ,
16561723 "with_backups" : with_backups ,
1724+ "placement_group" : _expand_placement_group_assignment (
1725+ placement_group
1726+ ),
16571727 }
16581728
1729+ util .drop_null_keys (params )
1730+
16591731 result = self ._client .post (
16601732 "{}/clone" .format (Instance .api_endpoint ), model = self , data = params
16611733 )
@@ -1790,3 +1862,40 @@ def _serialize(self):
17901862 dct = Base ._serialize (self )
17911863 dct ["images" ] = [d .id for d in self .images ]
17921864 return dct
1865+
1866+
1867+ def _expand_placement_group_assignment (
1868+ pg : Union [
1869+ InstancePlacementGroupAssignment , "PlacementGroup" , Dict [str , Any ], int
1870+ ]
1871+ ) -> Optional [Dict [str , Any ]]:
1872+ """
1873+ Expands the placement group argument into a dict for use in an API request body.
1874+
1875+ :param pg: The placement group argument to be expanded.
1876+ :type pg: Union[InstancePlacementGroupAssignment, PlacementGroup, Dict[str, Any], int]
1877+
1878+ :returns: The expanded placement group.
1879+ :rtype: Optional[Dict[str, Any]]
1880+ """
1881+ # Workaround to avoid circular import
1882+ from linode_api4 .objects .placement import ( # pylint: disable=import-outside-toplevel
1883+ PlacementGroup ,
1884+ )
1885+
1886+ if pg is None :
1887+ return None
1888+
1889+ if isinstance (pg , dict ):
1890+ return pg
1891+
1892+ if isinstance (pg , InstancePlacementGroupAssignment ):
1893+ return pg .dict
1894+
1895+ if isinstance (pg , PlacementGroup ):
1896+ return {"id" : pg .id }
1897+
1898+ if isinstance (pg , int ):
1899+ return {"id" : pg }
1900+
1901+ raise TypeError (f"Invalid type for Placement Group: { type (pg )} " )
0 commit comments