Skip to content

Commit 37f52f2

Browse files
Extend MultiNetworkInterfacesInstancesValidator to cover single-card EFA instances (#7457)
Since 3.15.0, single-network-card instances with EFA enabled (e.g. hpc6a, c5n) are launched with two network interfaces (a primary interface plus a dedicated efa-only interface), so AWS does not auto-assign them a public IP. Previously MultiNetworkInterfacesInstancesValidator only checked multi-network-card instances, so these queues passed validation and their compute nodes silently failed to bootstrap on public subnets without a NAT gateway. Broaden the validator to also flag single-card EFA compute resources (mirroring the launch-template logic in queues_stack.add_network_interfaces, including the EfaInterfaceType: efa opt-out), and rework the failure messages to name the responsible instance types, explain why they launch multiple interfaces, and give a branch-specific workaround. Co-authored-by: hanwen-cluster <hanwenli@amazon.com>
1 parent 59ab253 commit 37f52f2

4 files changed

Lines changed: 276 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
**CHANGES**
1212
- The validator `ClusterNameValidator` now enforces cluster names to be limited to 40 characters when using `ExternalSlurmdbd`,
1313
consistent with the existing limit for `Database`. This prevents runtime failures caused by MySQL's table name length limit.
14+
- The validator `MultiNetworkInterfacesInstancesValidator` now also covers single-network-card instances with EFA enabled, which are launched with multiple network interfaces and therefore cannot be auto-assigned a public IP.
1415
- The CLI now requires the additional permission `tag:GetResources`.
1516
- Add support for Python 3.11, 3.12 in pcluster CLI
1617

cli/src/pcluster/config/cluster_config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
Feature,
6868
)
6969
from pcluster.utils import (
70+
get_attr,
7071
get_partition,
7172
get_resource_name_from_resource_arn,
7273
to_snake_case,
@@ -3015,7 +3016,11 @@ def _register_validators(self, context: ValidatorContext = None): # noqa: C901
30153016
)
30163017

30173018
instance_types_data = self.get_instance_types_data()
3018-
self._register_validator(MultiNetworkInterfacesInstancesValidator, queues=self.scheduling.queues)
3019+
self._register_validator(
3020+
MultiNetworkInterfacesInstancesValidator,
3021+
queues=self.scheduling.queues,
3022+
efa_interface_type=get_attr(self, "dev_settings.efa_interface_type"),
3023+
)
30193024
checked_images = []
30203025
capacity_reservation_id_max_count_map = {}
30213026
total_max_compute_nodes = 0

cli/src/pcluster/validators/cluster_validators.py

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,14 +1538,73 @@ def _validate(self, encryption_settings: list):
15381538

15391539

15401540
class MultiNetworkInterfacesInstancesValidator(Validator):
1541-
"""Verify that queues with multi nic compute resources don't auto-assign public IPs or contain subnets that do."""
1541+
"""Verify that queues launching multiple network interfaces don't auto-assign public IPs or use subnets that do."""
15421542

1543-
def _validate(self, queues):
1543+
# Shared statement of the underlying AWS limitation.
1544+
_PUBLIC_IP_LIMITATION = (
1545+
"AWS can't auto-assign a public IP to instances launched with more than one network interface."
1546+
)
1547+
1548+
@staticmethod
1549+
def _multiple_network_interfaces_reason(compute_resource, efa_interface_type):
1550+
"""Return why the compute resource is launched with more than one network interface, or None.
1551+
1552+
Two cases produce more than one network interface, which must be kept in sync with
1553+
templates/queues_stack.py::add_network_interfaces:
1554+
- "multi_card": multi-network-card instances (e.g. p4d, hpc6id) attach a network interface per network card.
1555+
- "efa": single-network-card instances with EFA enabled (e.g. hpc6a, c5n), for which ParallelCluster attaches
1556+
a primary interface plus a dedicated efa-only interface (unless the EfaInterfaceType: efa opt-out is set or
1557+
the primary network card cannot hold 2 network interfaces).
1558+
"""
1559+
if compute_resource.max_network_cards > 1:
1560+
return "multi_card"
1561+
1562+
efa_enabled = compute_resource.efa and compute_resource.efa.enabled
1563+
use_legacy_efa = efa_interface_type == "efa"
1564+
nci0_supports_efa = compute_resource.max_efa_interfaces == compute_resource.max_network_cards
1565+
nci0_max_enis = (
1566+
compute_resource.network_cards_list[0].maximum_network_interfaces()
1567+
if compute_resource.network_cards_list
1568+
else 1
1569+
)
1570+
if efa_enabled and not use_legacy_efa and nci0_supports_efa and nci0_max_enis >= 2:
1571+
return "efa"
1572+
return None
1573+
1574+
def _reason_clause(self, queue, efa_interface_type):
1575+
"""Return a queue-level clause explaining why the queue's instances launch multiple network interfaces.
1576+
1577+
A queue may contain both kinds of compute resources, so include every applicable reason along with the
1578+
instance types responsible for it.
1579+
"""
1580+
instance_types_by_reason = {"multi_card": [], "efa": []}
1581+
for compute_resource in queue.compute_resources:
1582+
reason = self._multiple_network_interfaces_reason(compute_resource, efa_interface_type)
1583+
if reason:
1584+
instance_types_by_reason[reason].extend(compute_resource.instance_types)
1585+
1586+
clauses = []
1587+
if instance_types_by_reason["multi_card"]:
1588+
# Instance types with multiple network cards are launched with one network interface per card.
1589+
types = ", ".join(sorted(set(instance_types_by_reason["multi_card"])))
1590+
clauses.append(f"contains the instance types {types} with multiple network interfaces")
1591+
if instance_types_by_reason["efa"]:
1592+
# Single-network-card instances launched with a primary interface plus an efa-only interface (EFA enabled).
1593+
types = ", ".join(sorted(set(instance_types_by_reason["efa"])))
1594+
clauses.append(
1595+
f"has EFA enabled on the single-network-card instance types {types}, so its compute nodes are "
1596+
f"launched with multiple network interfaces (a primary interface plus a dedicated efa-only interface)"
1597+
)
1598+
return " and ".join(clauses)
1599+
1600+
def _validate(self, queues, efa_interface_type=None):
15441601
multi_nic_queues = [
15451602
queue
15461603
for queue in queues
1547-
for compute_resource in queue.compute_resources
1548-
if compute_resource.max_network_cards > 1
1604+
if any(
1605+
self._multiple_network_interfaces_reason(compute_resource, efa_interface_type)
1606+
for compute_resource in queue.compute_resources
1607+
)
15491608
]
15501609

15511610
all_subnets_with_public_ips = {
@@ -1557,11 +1616,12 @@ def _validate(self, queues):
15571616
}
15581617

15591618
for queue in multi_nic_queues:
1619+
reason_clause = self._reason_clause(queue, efa_interface_type)
15601620
if queue.networking.assign_public_ip:
15611621
self._add_failure(
1562-
f"The queue {queue.name} contains an instance type with multiple network interfaces however the "
1563-
f"AssignPublicIp value is set to true. AWS public IPs can only be assigned to instances launched "
1564-
f"with a single network interface.",
1622+
f"The queue {queue.name} {reason_clause}, but AssignPublicIp is set to true. "
1623+
f"{self._PUBLIC_IP_LIMITATION} Set AssignPublicIp to false and use a private subnet with a NAT "
1624+
f"gateway to provide internet access to the compute nodes.",
15651625
FailureLevel.ERROR,
15661626
)
15671627

@@ -1570,8 +1630,8 @@ def _validate(self, queues):
15701630
)
15711631
if queue_subnets_with_public_ips:
15721632
self._add_failure(
1573-
f"The queue {queue.name} contains an instance type with multiple network interfaces however the "
1574-
f"subnets {queue_subnets_with_public_ips} is configured to automatically assign public IPs. AWS "
1575-
f"public IPs can only be assigned to instances launched with a single network interface.",
1633+
f"The queue {queue.name} {reason_clause}, but the subnets {queue_subnets_with_public_ips} "
1634+
f"auto-assign public IPs. {self._PUBLIC_IP_LIMITATION} Use a private subnet with a NAT gateway to "
1635+
f"provide internet access to the compute nodes.",
15761636
FailureLevel.ERROR,
15771637
)

0 commit comments

Comments
 (0)