Skip to content

Commit e81495c

Browse files
committed
Added tests for EFS Access Points
1 parent 80ddc78 commit e81495c

File tree

7 files changed

+217
-10
lines changed

7 files changed

+217
-10
lines changed

cli/src/pcluster/validators/efs_validators.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@ class EfsMountOptionsValidator(Validator):
1818
IAM Authorization requires Encryption in Transit.
1919
"""
2020

21-
def _validate(self, encryption_in_transit: bool, iam_authorization: bool, accesspoint_id: str, name: str):
21+
def _validate(self, encryption_in_transit: bool, iam_authorization: bool, access_point_id: str, name: str):
2222
if iam_authorization and not encryption_in_transit:
2323
self._add_failure(
2424
"EFS IAM authorization cannot be enabled when encryption in-transit is disabled. "
2525
f"Please either disable IAM authorization or enable encryption in-transit for file system {name}",
2626
FailureLevel.ERROR,
2727
)
28+
if access_point_id and not name:
29+
self._add_failure(
30+
"An access point can only be specified when using an existing EFS file system. "
31+
f"Please either remove the access point id {access_point_id} or provide the file system id for the access point",
32+
FailureLevel.ERROR,
33+
)

cli/tests/pcluster/validators/test_efs_validators.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
False,
2828
True,
2929
"EFS IAM authorization cannot be enabled when encryption in-transit is disabled. "
30-
"Please either disable IAM authorization or enable encryption in-transit for file system "
31-
"<name-of-the-file-system>",
30+
"Please either disable IAM authorization or enable encryption in-transit "
31+
"for file system <name-of-the-file-system>",
3232
),
3333
(
3434
True,
@@ -42,8 +42,42 @@
4242
),
4343
],
4444
)
45-
def test_efs_mount_options_validator(encryption_in_transit, iam_authorization, expected_message):
45+
def test_efs_mount_options_validator(
46+
encryption_in_transit, iam_authorization, access_point_id, file_system_id, expected_message
47+
):
4648
actual_failures = EfsMountOptionsValidator().execute(
47-
encryption_in_transit, iam_authorization, "<name-of-the-file-system>"
49+
encryption_in_transit, iam_authorization, None, "<name-of-the-file-system>"
4850
)
4951
assert_failure_messages(actual_failures, expected_message)
52+
53+
54+
@pytest.mark.parametrize(
55+
"access_point_id, name_of_the_file_system, expected_message",
56+
[
57+
(
58+
None,
59+
None,
60+
None,
61+
),
62+
(
63+
"<access_point_id>",
64+
None,
65+
"An access point can only be specified when using an existing EFS file system. "
66+
"Please either remove the access point id <access_point_id> "
67+
"or provide the file system id for the access point",
68+
),
69+
(
70+
"<access_point_id>",
71+
"<name-of-the-file-system>",
72+
None,
73+
),
74+
(
75+
None,
76+
"<name-of-the-file-system>",
77+
None,
78+
),
79+
],
80+
)
81+
def test_efs_access_point_validator(access_point_id, name_of_the_file_system, expected_message):
82+
actual_failures = EfsMountOptionsValidator().execute(False, False, access_point_id, name_of_the_file_system)
83+
assert_failure_messages(actual_failures, expected_message)

tests/integration-tests/configs/develop.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,12 @@ test-suites:
635635
instances: {{ common.INSTANCES_DEFAULT_X86 }}
636636
oss: ["rhel8"]
637637
schedulers: ["slurm"]
638+
test_efs.py::test_efs_access_point:
639+
dimensions:
640+
- regions: ["us-east-2"]
641+
instances: {{ common.INSTANCES_DEFAULT_X86 }}
642+
oss: ["alinux2"]
643+
schedulers: ["slurm"]
638644
test_raid.py::test_raid_fault_tolerance_mode:
639645
dimensions:
640646
- regions: ["cn-northwest-1"]

tests/integration-tests/conftest.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from jinja2.sandbox import SandboxedEnvironment
5555
from troposphere import Ref, Sub, Template, ec2, resourcegroups
5656
from troposphere.ec2 import PlacementGroup
57-
from troposphere.efs import FileSystem as EFSFileSystem
57+
from troposphere.efs import FileSystem as EFSFileSystem, AccessPoint as EFSAccessPoint
5858
from troposphere.efs import MountTarget
5959
from troposphere.fsx import (
6060
ClientConfigurations,
@@ -1785,6 +1785,32 @@ def create_efs(num=1):
17851785
for stack in created_stacks:
17861786
cfn_stacks_factory.delete_stack(stack.name, region)
17871787

1788+
@pytest.fixture(scope="class")
1789+
def efs_access_point_stack_factory(cfn_stacks_factory, request, region, vpc_stack):
1790+
"""EFS stack contains a single efs and a single access point resource."""
1791+
created_stacks = []
1792+
1793+
def create_access_points(efs_fs_id, num=1):
1794+
ap_template = Template()
1795+
ap_template.set_version("2010-09-09")
1796+
ap_template.set_description("Access Point stack created for testing existing EFS wtith Access points")
1797+
access_point_resource_name = "AccessPointResourceResource"
1798+
for i in range(num):
1799+
access_point = EFSAccessPoint(f"{access_point_resource_name}{i}")
1800+
access_point.FileSystemId = efs_fs_id
1801+
ap_template.add_resource(access_point)
1802+
stack_name = generate_stack_name("integ-tests-efs-ap", request.config.getoption("stackname_suffix"))
1803+
stack = CfnStack(name=stack_name, region=region, template=ap_template.to_json())
1804+
cfn_stacks_factory.create_stack(stack)
1805+
created_stacks.append(stack)
1806+
return [stack.cfn_resources[f"{access_point_resource_name}{i}"] for i in range(num)]
1807+
1808+
yield create_access_points
1809+
1810+
if not request.config.getoption("no_delete"):
1811+
for stack in created_stacks:
1812+
cfn_stacks_factory.delete_stack(stack.name, region)
1813+
17881814

17891815
@pytest.fixture(scope="class")
17901816
def efs_mount_target_stack_factory(cfn_stacks_factory, request, region, vpc_stack):

tests/integration-tests/tests/storage/storage_common.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def test_raid_correctly_mounted(remote_command_executor, mount_dir, volume_size)
222222

223223

224224
def write_file_into_efs(
225-
region, vpc_stack: CfnVpcStack, efs_ids, request, key_name, cfn_stacks_factory, efs_mount_target_stack_factory
225+
region, vpc_stack: CfnVpcStack, efs_ids, request, key_name, cfn_stacks_factory, efs_mount_target_stack_factory, access_point_id=None
226226
):
227227
"""Write file stack contains an instance to write an empty file with random name into each of the efs in efs_ids."""
228228
write_file_template = Template()
@@ -236,7 +236,7 @@ def write_file_into_efs(
236236
write_file_user_data = ""
237237
for efs_id in efs_ids:
238238
random_file_name = random_alphanumeric()
239-
write_file_user_data += _write_user_data(efs_id, random_file_name)
239+
write_file_user_data += _write_user_data(efs_id, random_file_name, access_point_id=access_point_id)
240240
random_file_names.append(random_file_name)
241241
user_data = f"""
242242
#cloud-config
@@ -312,11 +312,12 @@ def write_file_into_efs(
312312
return random_file_names
313313

314314

315-
def _write_user_data(efs_id, random_file_name):
315+
def _write_user_data(efs_id, random_file_name, access_point_id=None):
316316
mount_dir = "/mnt/efs/fs"
317+
access_point_mount_parameter = f",accesspoint={access_point_id}" if access_point_id is not None else ""
317318
return f"""
318319
- mkdir -p {mount_dir}
319-
- mount -t efs -o tls,iam {efs_id}:/ {mount_dir}
320+
- mount -t efs -o tls,iam{access_point_mount_parameter} {efs_id}:/ {mount_dir}
320321
- touch {mount_dir}/{random_file_name}
321322
- umount {mount_dir}
322323
""" # noqa: E501

tests/integration-tests/tests/storage/test_efs.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,90 @@ def test_multiple_efs(
200200
encryption_in_transits,
201201
)
202202

203+
@pytest.mark.usefixtures("instance")
204+
def test_efs_access_point(
205+
os,
206+
region,
207+
scheduler,
208+
efs_stack_factory,
209+
efs_mount_target_stack_factory,
210+
efs_access_point_stack_factory,
211+
pcluster_config_reader,
212+
clusters_factory,
213+
vpc_stack,
214+
request,
215+
key_name,
216+
cfn_stacks_factory,
217+
scheduler_commands_factory,
218+
):
219+
"""
220+
Test when efs_fs_id is provided in the config file, the existing efs can be correctly mounted.
221+
222+
To verify the efs is the existing efs, the test expects a file with random ran inside the efs mounted
223+
"""
224+
# Names of files that will be written from separate instance. The test checks the cluster nodes can access them.
225+
efs_filenames = []
226+
iam_authorizations = [False, False, True] if scheduler != "awsbatch" else 3 * [False]
227+
encryption_in_transits = [False, True, True] if scheduler != "awsbatch" else 3 * [False]
228+
# create an additional EFS with file system policy to prevent anonymous access
229+
efs_filesystem_id = efs_stack_factory()[0]
230+
efs_mount_target_stack_factory([efs_filesystem_id])
231+
access_point_id = efs_access_point_stack_factory(efs_fs_id=efs_filesystem_id)[0]
232+
if scheduler != "awsbatch":
233+
account_id = (
234+
boto3.client("sts", region_name=region, endpoint_url=get_sts_endpoint(region))
235+
.get_caller_identity()
236+
.get("Account")
237+
)
238+
policy = {
239+
"Version": "2012-10-17",
240+
"Id": "efs-policy-denying-access-for-direct-efs-access",
241+
"Statement": [
242+
{
243+
"Sid": "efs-block-not-access-point-in-account",
244+
"Effect": "Deny",
245+
"Principal": {"AWS": "*"},
246+
"Action": [
247+
"elasticfilesystem:ClientMount",
248+
"elasticfilesystem:ClientRootAccess",
249+
"elasticfilesystem:ClientWrite",
250+
],
251+
"Resource": f"arn:{get_arn_partition(region)}:elasticfilesystem:{region}:{account_id}:"
252+
f"file-system/{efs_filesystem_id}",
253+
"Condition": {
254+
"StringNotLike": {"elasticfilesystem:AccessPointArn":
255+
f"arn:{get_arn_partition(region)}:elasticfilesystem:{region}:{account_id}:access-point/{access_point_id}"
256+
}
257+
},
258+
},
259+
{
260+
"Sid": "efs-allow-accesspoint-in-account",
261+
"Effect": "Allow",
262+
"Principal": {"AWS": "*"},
263+
"Action": [
264+
"elasticfilesystem:ClientMount",
265+
"elasticfilesystem:ClientRootAccess",
266+
"elasticfilesystem:ClientWrite",
267+
],
268+
"Resource": f"arn:{get_arn_partition(region)}:elasticfilesystem:{region}:{account_id}:"
269+
f"file-system/{efs_filesystem_id}"
270+
}
271+
]
272+
}
273+
boto3.client("efs").put_file_system_policy(FileSystemId=efs_filesystem_id, Policy=json.dumps(policy))
274+
275+
mount_dir = "efs_mount_dir"
276+
cluster_config = pcluster_config_reader(mount_dir=mount_dir, efs_filesystem_id=efs_filesystem_id, access_point_id=access_point_id)
277+
cluster = clusters_factory(cluster_config)
278+
remote_command_executor = RemoteCommandExecutor(cluster)
279+
280+
mount_dir = "/" + mount_dir
281+
scheduler_commands = scheduler_commands_factory(remote_command_executor)
282+
test_efs_correctly_mounted(remote_command_executor, mount_dir)
283+
_test_efs_correctly_shared(remote_command_executor, mount_dir, scheduler_commands)
284+
203285

286+
204287
def _check_efs_after_nodes_reboot(
205288
all_mount_dirs,
206289
cluster,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
Image:
2+
Os: {{ os }}
3+
HeadNode:
4+
InstanceType: {{ instance }}
5+
Networking:
6+
SubnetId: {{ public_subnet_ids[0] }}
7+
Ssh:
8+
KeyName: {{ key_name }}
9+
Imds:
10+
Secured: {{ imds_secured }}
11+
Scheduling:
12+
Scheduler: {{ scheduler }}
13+
{% if scheduler == "awsbatch" %}AwsBatchQueues:{% else %}SlurmQueues:{% endif %}
14+
- Name: queue-0
15+
ComputeResources:
16+
- Name: compute-resource-0
17+
{% if scheduler == "awsbatch" %}
18+
InstanceTypes:
19+
- {{ instance }}
20+
MinvCpus: 4
21+
DesiredvCpus: 4
22+
{% else %}
23+
Instances:
24+
- InstanceType: {{ instance }}
25+
MinCount: 1
26+
MaxCount: 1
27+
{% endif %}
28+
Networking:
29+
SubnetIds:
30+
- {{ private_subnet_ids[1] }}
31+
{% if scheduler == "slurm" %}
32+
- Name: queue-1
33+
ComputeResources:
34+
- Name: compute-resource-0
35+
Instances:
36+
- InstanceType: {{ instance }}
37+
MinCount: 1
38+
MaxCount: 1
39+
Networking:
40+
SubnetIds:
41+
- {% if private_subnet_ids|length >= 3 %} {{ private_subnet_ids[2] }} {% else %} {{ private_subnet_ids[1] }} {% endif %}
42+
{% endif %}
43+
# This compute subnet would be in a different AZ than head node for regions defined in AVAILABILITY_ZONE_OVERRIDES
44+
# See conftest for details
45+
SharedStorage:
46+
- MountDir: {{ mount_dir }}
47+
Name: efs
48+
StorageType: Efs
49+
EfsSettings:
50+
FileSystemId: {{ efs_filesystem_id }}
51+
AccessPointId: {{ access_point_id }}

0 commit comments

Comments
 (0)