Skip to content

Commit 874f95f

Browse files
Add the Networking section to SlurmQueues.ComputeResources including only PlacementGroup (aws#4330)
This change allows for creating configs that include placement groups for specific compute resources. It changes the way Queue level placement groups are configured as well. This aligns better with AWS EC2 placement group guidance to reduce insufficient capacity errors. Parallel cluster managed placement groups will no longer be created to include multiple compute resources. If enabled at the queue level and not assigned a name, every compute resource will get a separate parallel cluster managed placement group. If assigned a name at the queue level, there will be a single placement group for all compute resources in the queue. If enabled at the compute resource level and not assigned a name, this config will override the queue level config and that resource will get its own managed placement group. If assigned a name at the compute resource level, this config will override the queue level config and that resource will use the assigned placement group. Signed-off-by: Ryan Anderson <[email protected]>
1 parent 8f4b7e3 commit 874f95f

File tree

11 files changed

+535
-91
lines changed

11 files changed

+535
-91
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ CHANGELOG
1717
- Disable Multithreading through script executed by cloud-init and not through CpuOptions set into Launch Template.
1818
- Add support for multiple instance types in the same Compute Resource.
1919
- Add support for a Name field in PlacementGroup as the preferred naming method.
20+
- Add support for Networking.PlacementGroup in the SlurmQueues.ComputeResources section
2021

2122
**BUG FIXES**
2223
- Fix validation of parameter `SharedStorage/EfsSettings`: now validation fails when `FileSystemId` is specified

cli/src/pcluster/config/cluster_config.py

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -518,8 +518,8 @@ def __init__(self, http_proxy_address: str = None):
518518
class _BaseNetworking(Resource):
519519
"""Represent the networking configuration shared by head node and compute node."""
520520

521-
def __init__(self, security_groups: List[str] = None, additional_security_groups: List[str] = None):
522-
super().__init__()
521+
def __init__(self, security_groups: List[str] = None, additional_security_groups: List[str] = None, **kwargs):
522+
super().__init__(**kwargs)
523523
self.security_groups = Resource.init_param(security_groups)
524524
self.additional_security_groups = Resource.init_param(additional_security_groups)
525525

@@ -548,17 +548,35 @@ def availability_zone(self):
548548

549549

550550
class PlacementGroup(Resource):
551-
"""Represent the placement group for the Queue networking."""
551+
"""Represent the placement group for networking."""
552552

553-
def __init__(self, enabled: bool = None, name: str = None, id: str = None):
554-
super().__init__()
555-
self.enabled = Resource.init_param(enabled, default=False)
553+
def __init__(self, enabled: bool = None, name: str = None, id: str = None, **kwargs):
554+
super().__init__(**kwargs)
555+
self.enabled = Resource.init_param(enabled)
556556
self.name = Resource.init_param(name)
557557
self.id = Resource.init_param(id) # Duplicate of name
558558

559559
def _register_validators(self):
560560
self._register_validator(PlacementGroupNamingValidator, placement_group=self)
561561

562+
@property
563+
def is_enabled_and_unassigned(self) -> bool:
564+
"""Check if the PlacementGroup is enabled without a name or id."""
565+
return not (self.id or self.name) and self.enabled
566+
567+
@property
568+
def assignment(self) -> str:
569+
"""Check if the placement group has a name or id and get it, preferring the name if it exists."""
570+
return self.name or self.id
571+
572+
573+
class SlurmComputeResourceNetworking(Resource):
574+
"""Represent the networking configuration for the compute resource."""
575+
576+
def __init__(self, placement_group: PlacementGroup = None, **kwargs):
577+
super().__init__(**kwargs)
578+
self.placement_group = placement_group or PlacementGroup(implied=True)
579+
562580

563581
class _QueueNetworking(_BaseNetworking):
564582
"""Represent the networking configuration for the Queue."""
@@ -574,7 +592,7 @@ class SlurmQueueNetworking(_QueueNetworking):
574592

575593
def __init__(self, placement_group: PlacementGroup = None, proxy: Proxy = None, **kwargs):
576594
super().__init__(**kwargs)
577-
self.placement_group = placement_group
595+
self.placement_group = placement_group or PlacementGroup(implied=True)
578596
self.proxy = proxy
579597

580598

@@ -1623,6 +1641,7 @@ def __init__(
16231641
disable_simultaneous_multithreading: bool = None,
16241642
schedulable_memory: int = None,
16251643
capacity_reservation_target: CapacityReservationTarget = None,
1644+
networking: SlurmComputeResourceNetworking = None,
16261645
**kwargs,
16271646
):
16281647
super().__init__(**kwargs)
@@ -1637,6 +1656,7 @@ def __init__(
16371656
self.capacity_reservation_target = capacity_reservation_target
16381657
self._instance_types_with_instance_storage = []
16391658
self._instance_type_info_map = {}
1659+
self.networking = networking or SlurmComputeResourceNetworking(implied=True)
16401660

16411661
@staticmethod
16421662
def fetch_instance_type_info(instance_type) -> InstanceTypeInfo:
@@ -1792,11 +1812,25 @@ def disable_simultaneous_multithreading_manually(self) -> bool:
17921812
return self.disable_simultaneous_multithreading and self.instance_type_info.default_threads_per_core() > 1
17931813

17941814

1815+
class SchedulerPluginComputeResource(SlurmComputeResource):
1816+
"""Represent the Scheduler Plugin Compute Resource."""
1817+
1818+
def __init__(
1819+
self,
1820+
custom_settings: Dict = None,
1821+
**kwargs,
1822+
):
1823+
super().__init__(**kwargs)
1824+
self.custom_settings = custom_settings
1825+
1826+
17951827
class _CommonQueue(BaseQueue):
17961828
"""Represent the Common Queue resource between Slurm and Scheduler Plugin."""
17971829

17981830
def __init__(
17991831
self,
1832+
compute_resources: List[Union[_BaseSlurmComputeResource, SchedulerPluginComputeResource]],
1833+
networking: Union[SlurmQueueNetworking, SchedulerPluginQueueNetworking],
18001834
compute_settings: ComputeSettings = None,
18011835
custom_actions: CustomActions = None,
18021836
iam: Iam = None,
@@ -1810,6 +1844,8 @@ def __init__(
18101844
self.iam = iam or Iam(implied=True)
18111845
self.image = image
18121846
self.capacity_reservation_target = capacity_reservation_target
1847+
self.compute_resources = compute_resources
1848+
self.networking = networking
18131849

18141850
@property
18151851
def instance_role(self):
@@ -1829,6 +1865,43 @@ def queue_ami(self):
18291865
else:
18301866
return None
18311867

1868+
def get_managed_placement_group_keys(self) -> List[str]:
1869+
managed_placement_group_keys = []
1870+
for resource in self.compute_resources:
1871+
chosen_pg = (
1872+
resource.networking.placement_group
1873+
if not resource.networking.placement_group.implied
1874+
else self.networking.placement_group
1875+
)
1876+
if chosen_pg.is_enabled_and_unassigned:
1877+
managed_placement_group_keys.append(f"{self.name}-{resource.name}")
1878+
return managed_placement_group_keys
1879+
1880+
def get_placement_group_key_for_compute_resource(
1881+
self, compute_resource: Union[_BaseSlurmComputeResource, SchedulerPluginComputeResource]
1882+
) -> (str, bool):
1883+
# prefer compute level groups over queue level groups
1884+
placement_group_key, managed = None, None
1885+
cr_pg = compute_resource.networking.placement_group
1886+
if cr_pg.assignment:
1887+
placement_group_key, managed = cr_pg.assignment, False
1888+
elif cr_pg.enabled:
1889+
placement_group_key, managed = f"{self.name}-{compute_resource.name}", True
1890+
elif cr_pg.enabled is False:
1891+
placement_group_key, managed = None, False
1892+
elif self.networking.placement_group.assignment:
1893+
placement_group_key, managed = self.networking.placement_group.assignment, False
1894+
elif self.networking.placement_group.enabled:
1895+
placement_group_key, managed = f"{self.name}-{compute_resource.name}", True
1896+
return placement_group_key, managed
1897+
1898+
def is_placement_group_disabled_for_compute_resource(self, compute_resource_pg_enabled: bool) -> bool:
1899+
return (
1900+
compute_resource_pg_enabled is False
1901+
or self.networking.placement_group.enabled is False
1902+
and compute_resource_pg_enabled is None
1903+
)
1904+
18321905

18331906
class AllocationStrategy(Enum):
18341907
"""Define supported allocation strategies."""
@@ -1842,15 +1915,13 @@ class SlurmQueue(_CommonQueue):
18421915

18431916
def __init__(
18441917
self,
1845-
compute_resources: List[_BaseSlurmComputeResource],
1846-
networking: SlurmQueueNetworking,
18471918
allocation_strategy: str = None,
18481919
**kwargs,
18491920
):
18501921
super().__init__(**kwargs)
1851-
self.compute_resources = compute_resources
1852-
self.networking = networking
1853-
if any(isinstance(compute_resource, SlurmFlexibleComputeResource) for compute_resource in compute_resources):
1922+
if any(
1923+
isinstance(compute_resource, SlurmFlexibleComputeResource) for compute_resource in self.compute_resources
1924+
):
18541925
self.allocation_strategy = (
18551926
AllocationStrategy[to_snake_case(allocation_strategy).upper()]
18561927
if allocation_strategy
@@ -1896,7 +1967,10 @@ def _register_validators(self):
18961967
self._register_validator(
18971968
EfaPlacementGroupValidator,
18981969
efa_enabled=compute_resource.efa.enabled,
1899-
placement_group=self.networking.placement_group,
1970+
placement_group_key=self.get_placement_group_key_for_compute_resource(compute_resource)[0],
1971+
placement_group_disabled=self.is_placement_group_disabled_for_compute_resource(
1972+
compute_resource.networking.placement_group.enabled
1973+
),
19001974
)
19011975
for instance_type in compute_resource.instance_types:
19021976
self._register_validator(
@@ -1967,31 +2041,15 @@ def _register_validators(self):
19672041
)
19682042

19692043

1970-
class SchedulerPluginComputeResource(SlurmComputeResource):
1971-
"""Represent the Scheduler Plugin Compute Resource."""
1972-
1973-
def __init__(
1974-
self,
1975-
custom_settings: Dict = None,
1976-
**kwargs,
1977-
):
1978-
super().__init__(**kwargs)
1979-
self.custom_settings = custom_settings
1980-
1981-
19822044
class SchedulerPluginQueue(_CommonQueue):
19832045
"""Represent the Scheduler Plugin queue."""
19842046

19852047
def __init__(
19862048
self,
1987-
compute_resources: List[SchedulerPluginComputeResource],
1988-
networking: SchedulerPluginQueueNetworking,
19892049
custom_settings: Dict = None,
19902050
**kwargs,
19912051
):
19922052
super().__init__(**kwargs)
1993-
self.compute_resources = compute_resources
1994-
self.networking = networking
19952053
self.custom_settings = custom_settings
19962054

19972055
def _register_validators(self):
@@ -2014,7 +2072,10 @@ def _register_validators(self):
20142072
self._register_validator(
20152073
EfaPlacementGroupValidator,
20162074
efa_enabled=compute_resource.efa.enabled,
2017-
placement_group=self.networking.placement_group,
2075+
placement_group_key=self.get_placement_group_key_for_compute_resource(compute_resource)[0],
2076+
placement_group_disabled=self.is_placement_group_disabled_for_compute_resource(
2077+
compute_resource.networking.placement_group.enabled
2078+
),
20182079
)
20192080

20202081
@property

cli/src/pcluster/schemas/cluster_schema.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
SharedFsxLustre,
101101
SlurmClusterConfig,
102102
SlurmComputeResource,
103+
SlurmComputeResourceNetworking,
103104
SlurmFlexibleComputeResource,
104105
SlurmQueue,
105106
SlurmQueueNetworking,
@@ -1128,6 +1129,19 @@ class _ComputeResourceSchema(BaseSchema):
11281129
name = fields.Str(required=True, metadata={"update_policy": UpdatePolicy.UNSUPPORTED})
11291130

11301131

1132+
class SlurmComputeResourceNetworkingSchema(BaseSchema):
1133+
"""Represent the Networking schema of the Slurm ComputeResource."""
1134+
1135+
placement_group = fields.Nested(
1136+
PlacementGroupSchema, metadata={"update_policy": UpdatePolicy.QUEUE_UPDATE_STRATEGY}
1137+
)
1138+
1139+
@post_load
1140+
def make_resource(self, data, **kwargs):
1141+
"""Generate resource."""
1142+
return SlurmComputeResourceNetworking(**data)
1143+
1144+
11311145
class SlurmComputeResourceSchema(_ComputeResourceSchema):
11321146
"""Represent the schema of the Slurm ComputeResource."""
11331147

@@ -1148,6 +1162,9 @@ class SlurmComputeResourceSchema(_ComputeResourceSchema):
11481162
capacity_reservation_target = fields.Nested(
11491163
CapacityReservationTargetSchema, metadata={"update_policy": UpdatePolicy.QUEUE_UPDATE_STRATEGY}
11501164
)
1165+
networking = fields.Nested(
1166+
SlurmComputeResourceNetworkingSchema, metadata={"update_policy": UpdatePolicy.QUEUE_UPDATE_STRATEGY}
1167+
)
11511168

11521169
@validates_schema
11531170
def no_coexist_instance_type_flexibility(self, data, **kwargs):

cli/src/pcluster/schemas/common_schema.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class BaseSchema(Schema):
6464

6565
def on_bind_field(self, field_name, field_obj):
6666
"""
67-
Bind PascalCase in the config with with snake_case in Python.
67+
Bind PascalCase in the config with snake_case in Python.
6868
6969
For example, subnet_id in the code is automatically bind with SubnetId in the config file.
7070
The bind can be overwritten by specifying data_key.
@@ -76,11 +76,11 @@ def on_bind_field(self, field_name, field_obj):
7676
@staticmethod
7777
def fields_coexist(data, field_list, one_required=False, **kwargs):
7878
"""
79-
Check if at least two fields in the filed lists co-exist in the schema.
79+
Check if at least two fields in the field list co-exist in the schema.
8080
8181
:param data: data to be checked
8282
:param field_list: list including the name of the fields to check
83-
:param one_required: True if one of the field is required to be existed
83+
:param one_required: True if one of the fields is required to exist
8484
:return: True if one and only one field is not None
8585
"""
8686
if kwargs.get("partial"):

cli/src/pcluster/templates/cluster_stack.py

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,47 +1364,39 @@ def _add_policies_to_cleanup_resources_lambda_role(self):
13641364
def _add_placement_groups(self) -> Dict[str, ec2.CfnPlacementGroup]:
13651365
managed_placement_groups = {}
13661366
for queue in self._config.scheduling.queues:
1367-
if (
1368-
queue.networking.placement_group
1369-
and queue.networking.placement_group.enabled
1370-
and not (queue.networking.placement_group.id or queue.networking.placement_group.name)
1371-
):
1372-
managed_placement_groups[queue.name] = ec2.CfnPlacementGroup(
1373-
self, f"PlacementGroup{create_hash_suffix(queue.name)}", strategy="cluster"
1367+
for key in queue.get_managed_placement_group_keys():
1368+
managed_placement_groups[key] = ec2.CfnPlacementGroup(
1369+
self,
1370+
f"PlacementGroup{create_hash_suffix(key)}",
1371+
strategy="cluster",
13741372
)
13751373
return managed_placement_groups
13761374

1375+
@staticmethod
1376+
def _get_placement_group_for_compute_resource(queue, managed_placement_groups, compute_resource) -> str:
1377+
placement_group_key, managed = queue.get_placement_group_key_for_compute_resource(compute_resource)
1378+
return managed_placement_groups[placement_group_key].ref if managed else placement_group_key
1379+
13771380
def _add_launch_templates(self, managed_placement_groups, instance_profiles):
13781381
compute_launch_templates = {}
13791382
for queue in self._config.scheduling.queues:
13801383
compute_launch_templates[queue.name] = {}
13811384
queue_lt_security_groups = get_queue_security_groups_full(self._compute_security_group, queue)
1382-
1383-
queue_placement_group = None
1384-
if queue.networking.placement_group:
1385-
if (queue.networking.placement_group.id or queue.networking.placement_group.name) and (
1386-
queue.networking.placement_group.enabled or queue.networking.placement_group.is_implied("enabled")
1387-
):
1388-
queue_placement_group = queue.networking.placement_group.name or queue.networking.placement_group.id
1389-
elif queue.networking.placement_group.enabled:
1390-
queue_placement_group = managed_placement_groups[queue.name].ref
1391-
13921385
queue_pre_install_action, queue_post_install_action = (None, None)
13931386
if queue.custom_actions:
13941387
queue_pre_install_action = queue.custom_actions.on_node_start
13951388
queue_post_install_action = queue.custom_actions.on_node_configured
13961389

1397-
for compute_resource in queue.compute_resources:
1398-
launch_template = self._add_compute_resource_launch_template(
1390+
for resource in queue.compute_resources:
1391+
compute_launch_templates[queue.name][resource.name] = self._add_compute_resource_launch_template(
13991392
queue,
1400-
compute_resource,
1393+
resource,
14011394
queue_pre_install_action,
14021395
queue_post_install_action,
14031396
queue_lt_security_groups,
1404-
queue_placement_group,
1397+
self._get_placement_group_for_compute_resource(queue, managed_placement_groups, resource),
14051398
instance_profiles,
14061399
)
1407-
compute_launch_templates[queue.name][compute_resource.name] = launch_template
14081400
return compute_launch_templates
14091401

14101402
def _add_compute_resource_launch_template(
@@ -1414,7 +1406,7 @@ def _add_compute_resource_launch_template(
14141406
queue_pre_install_action,
14151407
queue_post_install_action,
14161408
queue_lt_security_groups,
1417-
queue_placement_group,
1409+
placement_group,
14181410
instance_profiles,
14191411
):
14201412
# LT network interfaces
@@ -1479,7 +1471,7 @@ def _add_compute_resource_launch_template(
14791471
),
14801472
# key_name=,
14811473
network_interfaces=compute_lt_nw_interfaces,
1482-
placement=ec2.CfnLaunchTemplate.PlacementProperty(group_name=queue_placement_group),
1474+
placement=ec2.CfnLaunchTemplate.PlacementProperty(group_name=placement_group),
14831475
image_id=self._config.image_dict[queue.name],
14841476
iam_instance_profile=ec2.CfnLaunchTemplate.IamInstanceProfileProperty(
14851477
name=instance_profiles[queue.name]

0 commit comments

Comments
 (0)