Skip to content

[Efs encryption][Draft] Support HeadNode Efs SharedStorage Encryption #6914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from

Conversation

Allenz5
Copy link
Contributor

@Allenz5 Allenz5 commented Jul 14, 2025

Description of changes

  • Add a SharedStorageEfsSettings section under the HeadNode section to include all settings related to Efs shared storage
  • Add an Encrypted parameter under the SharedStorageEfsSettings section to allow users to specify encryption for EFS shared storage
  • Add a SharedStorageEfsSettingsValidator to verify that SharedStorageEfsSettings should only be used when SharedStorageType is Efs

Tests

  • Add test_shared_storage_efs_settings_validator to test the behavior of SharedStorageEfsSettingsValidator
  • [TODO] Update the integration tests test_internal_efs and test_shared_home to verify EFS encryption

References

Checklist

  • Make sure you are pointing to the right branch.
  • If you're creating a patch for a branch other than develop add the branch name as prefix in the PR title (e.g. [release-3.6]).
  • Check all commits' messages are clear, describing what and why vs how.
  • Make sure to have added unit tests or integration tests to cover the new/modified code.
  • Check if documentation is impacted by this change.

Please review the guidelines for contributing and Pull Request Instructions.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@Allenz5 Allenz5 requested review from a team as code owners July 14, 2025 18:43
@himani2411
Copy link
Contributor

Please mention that Integ tests that you will modify is ONGOING

@himani2411
Copy link
Contributor

himani2411 commented Jul 15, 2025

LGTM! As discussed will wait for the Integ tests changes before we merge the PR

@@ -22,7 +22,8 @@
from aws_cdk.aws_ec2 import CfnSecurityGroup
from aws_cdk.core import CfnOutput, CfnResource, Construct, Fn, Stack

from pcluster.config.cluster_config import AwsBatchClusterConfig, CapacityType, SharedStorageType
from pcluster.config.cluster_config import AwsBatchClusterConfig, CapacityType
from pcluster.config.common import SharedStorageType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in other part of the PR.
This change is motivated by a refactoring, unrelated to the main goal of this PR.
A best practice is to keep the PR minimal and avoid refactoring on components that are out of scope for the main goal. The refactoring you're suggesting here is correct, but I suggest to do it in a follow up PR.

if self.config.head_node.shared_storage_efs_settings:
encrypted = self.config.head_node.shared_storage_efs_settings.encrypted
else:
encrypted = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Single Responsibility / Maintainability] The resource SharedStorageEfsSettings should be the only responsible to determine the default value of the encryption. To this aim, you should define the default value into a constant SharedStorageEfsSettings .DEFAULT_ENCRYPTION and reference the constant here. In this way we do not risk to scatter default values in different places. This prevent scattering responsibilities in different places, makes the code easier to maintain as it avoids code duplications and risk of misalignments.

else:
encrypted = False
internal_efs_storage_shared = SharedEfs(
mount_dir="/opt/parallelcluster/init_shared",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why init_shared and not, for example, shared? I know this was already in the code, but do we have an understanding about it?

def _validate(self, shared_storage_type: str, shared_storage_efs_settings):
if shared_storage_efs_settings and shared_storage_type.lower() != SharedStorageType.EFS.value:
self._add_failure(
"SharedStorageEfsSettings is specified "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message can be simplified to SharedStorageEfsSettings can only be used when SharedStorageType is {SharedStorageType.EFS.value.capitalize()}.

I suggest to use the capitalize because, even if we are able to handle both Efs and efs as values, in our public doc we always use capitalized strings.

@@ -392,6 +395,10 @@ def test_slurm_validators_are_called_with_correct_argument(test_datadir, mocker)
],
any_order=True,
)
shared_storage_efs_settings_validator.assert_has_calls(
[call(shared_storage_type="Ebs", shared_storage_efs_settings=None)],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Test] This unit test would make mnore sense if we use a configuration that inject both shared_storage_type and shared_storage_efs_settings into the validators, otherwise you could have the doubt that there exists cases where shared_storage_efs_settings is not injected.

My suggestion is to cover two scenarios:

  1. default case (the one you did): shared_storage_type=Ebs and shared_storage_efs_settings is not set
  2. case where shared_storage_efs_settings is injected: shared_storage_type=Efs and shared_storage_efs_settings is set.

@@ -2075,6 +2076,50 @@ def test_mixed_security_group_overwrite_validator(head_node_security_groups, que
assert_failure_messages(actual_failures, expected_message)


@pytest.mark.parametrize(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Test] It's a best practice to define test cases with a self-explanatory id. This improves readability and troubleshooting.
See example:

pytest.param(
{"clusterName": "cluster", "clusterConfiguration": CONFIG},
[ValidationResult("message", FailureLevel.WARNING, "type")],
None,
None,
"us-east-1",
None,
id="test with all errors",
),

mount_dir="/opt/parallelcluster/init_shared",
name="internal_pcluster_shared",
throughput_mode="elastic",
encrypted=encrypted,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Test] We should capture this property in a unit test. the unit test should verify that when encrypted is specified in the cluster config, then the cluster template has the EFS resource with that property set to the expected value.

See example:

def test_add_alarms(mocker, config_file_name):
mock_aws_api(mocker)
# mock bucket initialization parameters
mock_bucket(mocker)
mock_bucket_object_utils(mocker)
input_yaml, cluster = load_cluster_model_from_yaml(config_file_name)
generated_template, _ = CDKTemplateBuilder().build_cluster_template(
cluster_config=cluster, bucket=dummy_cluster_bucket(), stack_name="clustername"
)
simple_type = "AWS::CloudWatch::Alarm"
composite_type = "AWS::CloudWatch::CompositeAlarm"
head_node_alarms = [
{"name": "clustername-HeadNode", "type": composite_type},
{"name": "clustername-HeadNode-Health", "type": simple_type},
{"name": "clustername-HeadNode-Cpu", "type": simple_type},
{"name": "clustername-HeadNode-Mem", "type": simple_type},
{"name": "clustername-HeadNode-Disk", "type": simple_type},
]
if cluster.are_alarms_enabled:
for alarm in head_node_alarms:
matched_resources = get_resources(
generated_template, type=alarm["type"], properties={"AlarmName": alarm["name"]}
)
assert_that(matched_resources).is_length(1)
else:
matched_simple_alarms = get_resources(generated_template, type=simple_type)
matched_composite_alarms = get_resources(generated_template, type=composite_type)
assert_that(matched_simple_alarms).is_empty()
assert_that(matched_composite_alarms).is_empty()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants