diff --git a/cli/tests/pcluster/aws/dummy_aws_api.py b/cli/tests/pcluster/aws/dummy_aws_api.py index f8880d5b50..797fedb995 100644 --- a/cli/tests/pcluster/aws/dummy_aws_api.py +++ b/cli/tests/pcluster/aws/dummy_aws_api.py @@ -337,13 +337,13 @@ def get_instance_profile(self, instance_profile_name): "CreateDate": "2024-01-10T06:33:26Z", "RoleName": "Mocked-RoleName", "Path": "/", - "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/Mocked-RoleName" + "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/Mocked-RoleName", } ], "CreateDate": "2024-01-10T06:33:26Z", "InstanceProfileName": instance_profile_name, "Path": "/", - "Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/instance_profile_name" + "Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/instance_profile_name", } } diff --git a/cli/tests/pcluster/templates/test_cluster_stack.py b/cli/tests/pcluster/templates/test_cluster_stack.py index 99f91c04cc..0bcfba3c73 100644 --- a/cli/tests/pcluster/templates/test_cluster_stack.py +++ b/cli/tests/pcluster/templates/test_cluster_stack.py @@ -28,7 +28,6 @@ MAX_EXISTING_STORAGE_COUNT, MAX_NEW_STORAGE_COUNT, MAX_NUMBER_OF_COMPUTE_RESOURCES_PER_CLUSTER, - MAX_NUMBER_OF_QUEUES, ) from pcluster.models.s3_bucket import S3FileFormat, format_content from pcluster.schemas.cluster_schema import ClusterSchema @@ -130,7 +129,7 @@ def test_cluster_config_limits(mocker, capsys, tmpdir, pcluster_config_reader, t mock_bucket(mocker) mock_bucket_object_utils(mocker) - #TODO We must restore the actual maximum defined in constants.MAX_NUMBER_OF_QUEUES, which is 50. + # TODO We must restore the actual maximum defined in constants.MAX_NUMBER_OF_QUEUES, which is 50. max_number_of_queues = 46 # The max number of queues cannot be used with the max number of compute resources @@ -407,8 +406,9 @@ def test_compute_launch_template_properties( lt_assertion.assert_lt_properties(asset_content, launch_template_logical_id) # Checking user data variables - user_data_variables = asset_content["Resources"][launch_template_logical_id]["Properties"][ - "LaunchTemplateData"]["UserData"]["Fn::Base64"]["Fn::Sub"][1] + user_data_variables = asset_content["Resources"][launch_template_logical_id]["Properties"]["LaunchTemplateData"][ + "UserData" + ]["Fn::Base64"]["Fn::Sub"][1] expected_user_data_variables = { "CloudFormationUrl": "https://cloudformation.us-east-1.amazonaws.com", "LaunchTemplateResourceId": launch_template_logical_id, diff --git a/cli/tests/pcluster/templates/test_queues_stack.py b/cli/tests/pcluster/templates/test_queues_stack.py new file mode 100644 index 0000000000..7fafbb9560 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack.py @@ -0,0 +1,181 @@ +import json + +import pytest +from assertpy import assert_that +from freezegun import freeze_time + +from pcluster.schemas.cluster_schema import ClusterSchema +from pcluster.templates.cdk_builder import CDKTemplateBuilder +from pcluster.utils import load_json_dict, load_yaml_dict +from tests.pcluster.aws.dummy_aws_api import mock_aws_api +from tests.pcluster.models.dummy_s3_bucket import dummy_cluster_bucket, mock_bucket_object_utils +from tests.pcluster.templates.test_cluster_stack import IamPolicyAssertion, get_generated_template_and_cdk_assets +from tests.pcluster.utils import get_asset_content_with_resource_name + + +@pytest.mark.parametrize( + "config_file_name, iam_policy_assertions", + [ + ( + "config.yaml", + [ + IamPolicyAssertion( + expected_statements=[ + { + "Action": "ec2:DescribeInstanceAttribute", + "Effect": "Allow", + "Resource": "*", + "Sid": "Ec2", + }, + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + {"Ref": "AWS::Partition"}, + ":s3:::", + {"Ref": "AWS::Region"}, + "-aws-parallelcluster/*", + ], + ] + }, + "Sid": "S3GetObj", + }, + { + "Action": "cloudformation:DescribeStackResource", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + {"Ref": "AWS::Partition"}, + ":cloudformation:", + {"Ref": "AWS::Region"}, + ":", + {"Ref": "AWS::AccountId"}, + ":stack/clustername-*/*", + ], + ] + }, + "Sid": "CloudFormation", + }, + { + "Action": [ + "dynamodb:UpdateItem", + "dynamodb:PutItem", + "dynamodb:GetItem", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + {"Ref": "AWS::Partition"}, + ":dynamodb:", + {"Ref": "AWS::Region"}, + ":", + {"Ref": "AWS::AccountId"}, + ":table/parallelcluster-clustername", + ], + ] + }, + "Sid": "DynamoDBTable", + }, + ] + ), + ], + ), + ], +) +def test_compute_nodes_iam_permissions( + mocker, + config_file_name, + iam_policy_assertions, + test_datadir, +): + generated_template, cdk_assets = get_generated_template_and_cdk_assets( + mocker, + config_file_name, + test_datadir, + ) + + asset_content_iam_policies = get_asset_content_with_resource_name( + cdk_assets, + "ParallelClusterPolicies15b342af42246b70", + ) + for iam_policy_assertion in iam_policy_assertions: + iam_policy_assertion.assert_iam_policy_properties( + asset_content_iam_policies, "ParallelClusterPolicies15b342af42246b70" + ) + + +@freeze_time("2024-01-15T15:30:45") +@pytest.mark.parametrize( + "config_file_name, expected_compute_node_dna_json_file_name, expected_compute_node_extra_json_file_name", + [ + ("config-1.yaml", "dna-1.json", "extra-1.json"), + ("config-2.yaml", "dna-2.json", "extra-2.json"), + ], +) +def test_compute_nodes_dna_json( + mocker, + test_datadir, + config_file_name, + expected_compute_node_dna_json_file_name, + expected_compute_node_extra_json_file_name, +): + mock_aws_api(mocker) + mock_bucket_object_utils(mocker) + + # Read yaml and render CF Template + input_yaml = load_yaml_dict(test_datadir / config_file_name) + cluster_config = ClusterSchema(cluster_name="clustername").load(input_yaml) + _, cdk_assets = CDKTemplateBuilder().build_cluster_template( + cluster_config=cluster_config, bucket=dummy_cluster_bucket(), stack_name="clustername" + ) + + # Generated dna.json and extra.json + compute_node_lt_asset = get_asset_content_with_resource_name(cdk_assets, "LaunchTemplateA7211c84b953696f") + compute_node_lt = compute_node_lt_asset["Resources"]["LaunchTemplateA7211c84b953696f"] + compute_node_cfn_init_files = compute_node_lt["Metadata"]["AWS::CloudFormation::Init"]["deployConfigFiles"]["files"] + compute_node_dna_json = compute_node_cfn_init_files["/tmp/dna.json"] + compute_node_extra_json = compute_node_cfn_init_files["/tmp/extra.json"] + + # Expected dna.json and extra.json + expected_compute_node_dna_json = load_json_dict(test_datadir / expected_compute_node_dna_json_file_name) + expected_compute_node_extra_json = load_json_dict(test_datadir / expected_compute_node_extra_json_file_name) + expected_owner = expected_group = "root" + expected_mode = "000644" + + # Assertions on dna.json + rendered_dna_json_content = render_join(compute_node_dna_json["content"]["Fn::Join"]) + rendered_dna_json_content_as_json = json.loads(rendered_dna_json_content) + assert_that(compute_node_dna_json["owner"]).is_equal_to(expected_owner) + assert_that(compute_node_dna_json["group"]).is_equal_to(expected_group) + assert_that(compute_node_dna_json["mode"]).is_equal_to(expected_mode) + assert_that(rendered_dna_json_content_as_json).is_equal_to(expected_compute_node_dna_json) + + # Assertions on extra.json + assert_that(compute_node_extra_json["owner"]).is_equal_to(expected_owner) + assert_that(compute_node_extra_json["group"]).is_equal_to(expected_group) + assert_that(compute_node_extra_json["mode"]).is_equal_to(expected_mode) + assert_that(json.loads(compute_node_extra_json["content"])).is_equal_to(expected_compute_node_extra_json) + + +def render_join(elem: dict): + sep = str(elem[0]) + body = elem[1] + rendered_body = [] + for item in body: + if isinstance(item, str): + rendered_body.append(str(item).strip()) + elif isinstance(item, dict): + rendered_body.append(str(json.dumps(item).replace('"', '\\"')).strip()) + else: + raise ValueError("Found unsupported item type while rendering Fn::Join") + return sep.join(rendered_body) diff --git a/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/config-1.yaml b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/config-1.yaml new file mode 100644 index 0000000000..091793b698 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/config-1.yaml @@ -0,0 +1,21 @@ +Region: eu-west-1 +Image: + Os: alinux2 +HeadNode: + InstanceType: t2.micro + Networking: + SubnetId: subnet-12345678 + Ssh: + KeyName: ec2-key-name +Scheduling: + Scheduler: slurm + SlurmQueues: + - Name: queue1 + ComputeResources: + - Name: cr1 + InstanceType: c4.xlarge + MinCount: 0 + MaxCount: 10 + Networking: + SubnetIds: + - subnet-12345678 diff --git a/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/config-2.yaml b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/config-2.yaml new file mode 100644 index 0000000000..e1f61bf3e2 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/config-2.yaml @@ -0,0 +1,28 @@ +Region: eu-west-1 +Image: + Os: alinux2 +HeadNode: + InstanceType: t2.micro + Networking: + SubnetId: subnet-12345678 + Ssh: + KeyName: ec2-key-name +Scheduling: + Scheduler: slurm + SlurmQueues: + - Name: queue1 + ComputeResources: + - Name: cr1 + InstanceType: c4.xlarge + MinCount: 0 + MaxCount: 10 + Networking: + SubnetIds: + - subnet-12345678 +DirectoryService: + DomainName: corp.pcluster.com + DomainAddr: ldaps://corp.pcluster.com + PasswordSecretArn: arn:aws:secretsmanager:eu-west-1:XXXXXXXXXXXX:secret:XXXXXXXXXX + DomainReadOnlyUser: cn=ReadOnlyUser,ou=Users,ou=CORP,dc=corp,dc=pcluster,dc=com + LdapTlsReqCert: never + GenerateSshKeysForUsers: true \ No newline at end of file diff --git a/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/dna-1.json b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/dna-1.json new file mode 100644 index 0000000000..07f7863b65 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/dna-1.json @@ -0,0 +1,50 @@ +{ + "cluster": { + "cluster_name": "clustername", + "stack_name": "clustername", + "stack_arn": "{\"Ref\": \"AWS::StackId\"}", + "cluster_s3_bucket": "parallelcluster-a69601b5ee1fc2f2-v1-do-not-delete", + "cluster_config_s3_key": "parallelcluster/clusters/dummy-cluster-randomstring123/configs/cluster-config-with-implied-values.yaml", + "cluster_config_version": "", + "enable_efa": "NONE", + "raid_shared_dir": "", + "raid_type": "", + "base_os": "alinux2", + "region": "us-east-1", + "shared_storage_type": "ebs", + "efs_fs_ids": "", + "efs_shared_dirs": "", + "efs_encryption_in_transits": "", + "efs_iam_authorizations": "", + "fsx_fs_ids": "", + "fsx_mount_names": "", + "fsx_dns_names": "", + "fsx_volume_junction_paths": "", + "fsx_fs_types": "", + "fsx_shared_dirs": "", + "scheduler": "slurm", + "ephemeral_dir": "/scratch", + "ebs_shared_dirs": "", + "proxy": "NONE", + "slurm_ddb_table": "{\"Ref\": \"referencetoclusternameSlurmDynamoDBTable99119DBERef\"}", + "log_group_name": "/aws/parallelcluster/clustername-202401151530", + "dns_domain": "{\"Ref\": \"referencetoclusternameClusterDNSDomain8D0872E1Ref\"}", + "hosted_zone": "{\"Ref\": \"referencetoclusternameRoute53HostedZone2388733DRef\"}", + "node_type": "ComputeFleet", + "cluster_user": "ec2-user", + "enable_intel_hpc_platform": "false", + "cw_logging_enabled": "true", + "log_rotation_enabled": "true", + "scheduler_queue_name": "queue1", + "scheduler_compute_resource_name": "cr1", + "enable_efa_gdr": "NONE", + "custom_node_package": "", + "custom_awsbatchcli_package": "", + "use_private_hostname": "false", + "head_node_private_ip": "{\"Ref\": \"referencetoclusternameHeadNodeENI6497A502PrimaryPrivateIpAddress\"}", + "directory_service": { + "enabled": "false" + }, + "disable_sudo_access_for_default_user": "false" + } +} \ No newline at end of file diff --git a/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/dna-2.json b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/dna-2.json new file mode 100644 index 0000000000..d27ffbdbd9 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/dna-2.json @@ -0,0 +1,50 @@ +{ + "cluster": { + "cluster_name": "clustername", + "stack_name": "clustername", + "stack_arn": "{\"Ref\": \"AWS::StackId\"}", + "cluster_s3_bucket": "parallelcluster-a69601b5ee1fc2f2-v1-do-not-delete", + "cluster_config_s3_key": "parallelcluster/clusters/dummy-cluster-randomstring123/configs/cluster-config-with-implied-values.yaml", + "cluster_config_version": "", + "enable_efa": "NONE", + "raid_shared_dir": "", + "raid_type": "", + "base_os": "alinux2", + "region": "us-east-1", + "shared_storage_type": "ebs", + "efs_fs_ids": "", + "efs_shared_dirs": "", + "efs_encryption_in_transits": "", + "efs_iam_authorizations": "", + "fsx_fs_ids": "", + "fsx_mount_names": "", + "fsx_dns_names": "", + "fsx_volume_junction_paths": "", + "fsx_fs_types": "", + "fsx_shared_dirs": "", + "scheduler": "slurm", + "ephemeral_dir": "/scratch", + "ebs_shared_dirs": "", + "proxy": "NONE", + "slurm_ddb_table": "{\"Ref\": \"referencetoclusternameSlurmDynamoDBTable99119DBERef\"}", + "log_group_name": "/aws/parallelcluster/clustername-202401151530", + "dns_domain": "{\"Ref\": \"referencetoclusternameClusterDNSDomain8D0872E1Ref\"}", + "hosted_zone": "{\"Ref\": \"referencetoclusternameRoute53HostedZone2388733DRef\"}", + "node_type": "ComputeFleet", + "cluster_user": "ec2-user", + "enable_intel_hpc_platform": "false", + "cw_logging_enabled": "true", + "log_rotation_enabled": "true", + "scheduler_queue_name": "queue1", + "scheduler_compute_resource_name": "cr1", + "enable_efa_gdr": "NONE", + "custom_node_package": "", + "custom_awsbatchcli_package": "", + "use_private_hostname": "false", + "head_node_private_ip": "{\"Ref\": \"referencetoclusternameHeadNodeENI6497A502PrimaryPrivateIpAddress\"}", + "directory_service": { + "enabled": "true" + }, + "disable_sudo_access_for_default_user": "false" + } +} \ No newline at end of file diff --git a/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/extra-1.json b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/extra-1.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/extra-1.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/extra-2.json b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/extra-2.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_dna_json/extra-2.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_iam_permissions/config.yaml b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_iam_permissions/config.yaml new file mode 100644 index 0000000000..091793b698 --- /dev/null +++ b/cli/tests/pcluster/templates/test_queues_stack/test_compute_nodes_iam_permissions/config.yaml @@ -0,0 +1,21 @@ +Region: eu-west-1 +Image: + Os: alinux2 +HeadNode: + InstanceType: t2.micro + Networking: + SubnetId: subnet-12345678 + Ssh: + KeyName: ec2-key-name +Scheduling: + Scheduler: slurm + SlurmQueues: + - Name: queue1 + ComputeResources: + - Name: cr1 + InstanceType: c4.xlarge + MinCount: 0 + MaxCount: 10 + Networking: + SubnetIds: + - subnet-12345678