# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
# with the License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
# limitations under the License.
import datetime
import json
import os
import re

import configparser
import pytest
from assertpy import assert_that

import tests.pcluster.config.utils as utils
from pcluster.config.cfn_param_types import CfnParam, CfnSection
from pcluster.config.mappings import ALLOWED_VALUES, FSX
from pcluster.config.validators import (
    DCV_MESSAGES,
    EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS,
    FSX_MESSAGES,
    FSX_SUPPORTED_ARCHITECTURES_OSES,
    LOGFILE_LOGGER,
    architecture_os_validator,
    check_usage_class,
    cluster_type_validator,
    compute_resource_validator,
    disable_hyperthreading_architecture_validator,
    efa_gdr_validator,
    efa_os_arch_validator,
    fsx_ignored_parameters_validator,
    instances_architecture_compatibility_validator,
    intel_hpc_architecture_validator,
    queue_compute_type_validator,
    queue_validator,
    region_validator,
    s3_bucket_region_validator,
    settings_validator,
)
from pcluster.constants import FSX_HDD_THROUGHPUT, FSX_SSD_THROUGHPUT
from tests.common import MockedBoto3Request
from tests.pcluster.config.defaults import DefaultDict


@pytest.fixture()
def boto3_stubber_path():
    return "pcluster.config.validators.boto3"


@pytest.mark.parametrize(
    "section_dict, expected_message, expected_warning",
    [
        # traditional scheduler
        ({"scheduler": "sge", "initial_queue_size": 1, "max_queue_size": 2, "maintain_initial_size": True}, None, None),
        (
            {"scheduler": "sge", "initial_queue_size": 3, "max_queue_size": 2, "maintain_initial_size": True},
            "initial_queue_size must be fewer than or equal to max_queue_size",
            None,
        ),
        (
            {"scheduler": "sge", "initial_queue_size": 3, "max_queue_size": 2, "maintain_initial_size": False},
            "initial_queue_size must be fewer than or equal to max_queue_size",
            None,
        ),
        # awsbatch
        ({"scheduler": "awsbatch", "min_vcpus": 1, "desired_vcpus": 2, "max_vcpus": 3}, None, None),
        (
            {"scheduler": "awsbatch", "min_vcpus": 3, "desired_vcpus": 2, "max_vcpus": 3},
            "desired_vcpus must be greater than or equal to min_vcpus",
            None,
        ),
        (
            {"scheduler": "awsbatch", "min_vcpus": 1, "desired_vcpus": 4, "max_vcpus": 3},
            "desired_vcpus must be fewer than or equal to max_vcpus",
            None,
        ),
        (
            {"scheduler": "awsbatch", "min_vcpus": 4, "desired_vcpus": 4, "max_vcpus": 3},
            "max_vcpus must be greater than or equal to min_vcpus",
            None,
        ),
        # key pair not provided
        ({"scheduler": "awsbatch"}, None, "If you do not specify a key pair"),
    ],
)
def test_cluster_validator(mocker, capsys, section_dict, expected_message, expected_warning):
    config_parser_dict = {"cluster default": section_dict}
    utils.assert_param_validator(
        mocker, config_parser_dict, expected_message, capsys, expected_warning=expected_warning
    )


@pytest.mark.parametrize(
    "instance_type, expected_message", [("t2.micro", None), ("c4.xlarge", None), ("c5.xlarge", "is not supported")]
)
def test_ec2_instance_type_validator(mocker, instance_type, expected_message):
    config_parser_dict = {"cluster default": {"compute_instance_type": instance_type}}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize("instance_type, expected_message", [("t2.micro", None), ("c4.xlarge", None)])
def test_head_node_instance_type_validator(mocker, instance_type, expected_message):
    config_parser_dict = {"cluster default": {"master_instance_type": instance_type}}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "scheduler, instance_type, expected_message, expected_warnings",
    [
        ("sge", "t2.micro", None, None),
        ("sge", "c4.xlarge", None, None),
        ("sge", "c5.xlarge", "is not supported", None),
        # NOTE: compute_instance_type_validator calls ec2_instance_type_validator only if the scheduler is not awsbatch
        ("awsbatch", "t2.micro", None, None),
        ("awsbatch", "c4.xlarge", "is not supported", None),
        ("awsbatch", "t2", None, None),  # t2 family
        ("awsbatch", "optimal", None, None),
        ("sge", "p4d.24xlarge", None, "has 4 Network Interfaces."),
        ("slurm", "p4d.24xlarge", None, None),
    ],
)
def test_compute_instance_type_validator(mocker, scheduler, instance_type, expected_message, expected_warnings):
    config_parser_dict = {"cluster default": {"scheduler": scheduler, "compute_instance_type": instance_type}}
    extra_patches = {
        "pcluster.config.validators.InstanceTypeInfo.max_network_interface_count": 4
        if instance_type == "p4d.24xlarge"
        else 1,
    }
    utils.assert_param_validator(
        mocker, config_parser_dict, expected_message, expected_warnings, extra_patches=extra_patches
    )


def test_ec2_key_pair_validator(mocker, boto3_stubber):
    describe_key_pairs_response = {
        "KeyPairs": [
            {"KeyFingerprint": "12:bf:7c:56:6c:dd:4f:8c:24:45:75:f1:1b:16:54:89:82:09:a4:26", "KeyName": "key1"}
        ]
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_key_pairs", response=describe_key_pairs_response, expected_params={"KeyNames": ["key1"]}
        )
    ]
    boto3_stubber("ec2", mocked_requests)

    # TODO test with invalid key
    config_parser_dict = {"cluster default": {"key_name": "key1"}}
    utils.assert_param_validator(mocker, config_parser_dict)


@pytest.mark.parametrize(
    "image_architecture, bad_ami_message, bad_architecture_message",
    [
        ("x86_64", None, None),
        (
            "arm64",
            None,
            "incompatible with the architecture supported by the instance type chosen for the head node",
        ),
        (
            "arm64",
            "Unable to get information for AMI",
            "incompatible with the architecture supported by the instance type chosen for the head node",
        ),
    ],
)
def test_ec2_ami_validator(mocker, boto3_stubber, image_architecture, bad_ami_message, bad_architecture_message):
    describe_images_response = {
        "Images": [
            {
                "VirtualizationType": "paravirtual",
                "Name": "My server",
                "Hypervisor": "xen",
                "ImageId": "ami-12345678",
                "RootDeviceType": "ebs",
                "State": "available",
                "BlockDeviceMappings": [
                    {
                        "DeviceName": "/dev/sda1",
                        "Ebs": {
                            "DeleteOnTermination": True,
                            "SnapshotId": "snap-1234567890abcdef0",
                            "VolumeSize": 8,
                            "VolumeType": "standard",
                        },
                    }
                ],
                "Architecture": image_architecture,
                "ImageLocation": "123456789012/My server",
                "KernelId": "aki-88aa75e1",
                "OwnerId": "123456789012",
                "RootDeviceName": "/dev/sda1",
                "Public": False,
                "ImageType": "machine",
                "Description": "An AMI for my server",
            }
        ]
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_images",
            response=describe_images_response,
            expected_params={"ImageIds": ["ami-12345678"]},
            generate_error=bad_ami_message,
        )
    ]
    boto3_stubber("ec2", mocked_requests)

    # TODO test with invalid key
    config_parser_dict = {"cluster default": {"custom_ami": "ami-12345678"}}
    expected_message = bad_ami_message or bad_architecture_message
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        ({"tags": {"key": "value", "key2": "value2"}}, None),
        (
            {"tags": {"key": "value", "Version": "value2"}},
            r"Version.*reserved",
        ),
    ],
)
def test_tags_validator(mocker, capsys, section_dict, expected_message):
    config_parser_dict = {"cluster default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_error=expected_message)


def test_ec2_volume_validator(mocker, boto3_stubber):
    describe_volumes_response = {
        "Volumes": [
            {
                "AvailabilityZone": "us-east-1a",
                "Attachments": [
                    {
                        "AttachTime": "2013-12-18T22:35:00.000Z",
                        "InstanceId": "i-1234567890abcdef0",
                        "VolumeId": "vol-12345678",
                        "State": "attached",
                        "DeleteOnTermination": True,
                        "Device": "/dev/sda1",
                    }
                ],
                "Encrypted": False,
                "VolumeType": "gp2",
                "VolumeId": "vol-049df61146c4d7901",
                "State": "available",  # TODO add test with "in-use"
                "SnapshotId": "snap-1234567890abcdef0",
                "CreateTime": "2013-12-18T22:35:00.084Z",
                "Size": 8,
            }
        ]
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_volumes",
            response=describe_volumes_response,
            expected_params={"VolumeIds": ["vol-12345678"]},
        )
    ]
    boto3_stubber("ec2", mocked_requests)

    # TODO test with invalid key
    config_parser_dict = {
        "cluster default": {"ebs_settings": "default"},
        "ebs default": {"shared_dir": "test", "ebs_volume_id": "vol-12345678"},
    }
    utils.assert_param_validator(mocker, config_parser_dict)


@pytest.mark.parametrize(
    "region, base_os, scheduler, expected_message",
    [
        # verify awsbatch supported regions
        (
            "ap-northeast-3",
            "alinux2",
            "awsbatch",
            "Region 'ap-northeast-3' is not yet officially supported by ParallelCluster",
        ),
        ("us-gov-east-1", "alinux2", "awsbatch", None),
        ("us-gov-west-1", "alinux2", "awsbatch", None),
        ("eu-west-1", "alinux2", "awsbatch", None),
        ("us-east-1", "alinux2", "awsbatch", None),
        ("eu-north-1", "alinux2", "awsbatch", None),
        ("cn-north-1", "alinux2", "awsbatch", None),
        ("cn-northwest-1", "alinux2", "awsbatch", None),
        # verify traditional schedulers are supported in all the regions but ap-northeast-3
        ("cn-northwest-1", "alinux2", "sge", None),
        ("us-gov-east-1", "alinux2", "sge", None),
        ("cn-northwest-1", "alinux2", "slurm", None),
        ("us-gov-east-1", "alinux2", "slurm", None),
        ("cn-northwest-1", "alinux2", "torque", None),
        ("us-gov-east-1", "alinux2", "torque", None),
        (
            "ap-northeast-3",
            "alinux2",
            "sge",
            "Region 'ap-northeast-3' is not yet officially supported by ParallelCluster",
        ),
        # verify awsbatch supported OSes
        ("eu-west-1", "centos7", "awsbatch", "scheduler supports the following Operating Systems"),
        ("eu-west-1", "centos8", "awsbatch", "scheduler supports the following Operating Systems"),
        ("eu-west-1", "ubuntu1804", "awsbatch", "scheduler supports the following Operating Systems"),
        ("eu-west-1", "alinux2", "awsbatch", None),
        # verify sge supports all the OSes
        ("eu-west-1", "centos7", "sge", None),
        ("eu-west-1", "centos8", "sge", None),
        ("eu-west-1", "ubuntu1804", "sge", None),
        ("eu-west-1", "alinux2", "sge", None),
        # verify slurm supports all the OSes
        ("eu-west-1", "centos7", "slurm", None),
        ("eu-west-1", "centos8", "slurm", None),
        ("eu-west-1", "ubuntu1804", "slurm", None),
        ("eu-west-1", "alinux2", "slurm", None),
        # verify torque supports all the OSes
        ("eu-west-1", "centos7", "torque", None),
        ("eu-west-1", "centos8", "torque", None),
        ("eu-west-1", "ubuntu1804", "torque", None),
        ("eu-west-1", "alinux2", "torque", None),
    ],
)
def test_scheduler_validator(mocker, capsys, region, base_os, scheduler, expected_message):
    # we need to set the region in the environment because it takes precedence respect of the config file
    os.environ["AWS_DEFAULT_REGION"] = region
    config_parser_dict = {"cluster default": {"base_os": base_os, "scheduler": scheduler}}
    # Deprecation warning should be printed for sge and torque
    expected_warning = None
    wiki_url = "https://github.com/aws/aws-parallelcluster/wiki/Deprecation-of-SGE-and-Torque-in-ParallelCluster"
    if scheduler in ["sge", "torque"]:
        expected_warning = ".{0}. is scheduled to be deprecated.*{1}".format(scheduler, wiki_url)
    utils.assert_param_validator(mocker, config_parser_dict, expected_message, capsys, expected_warning)


def test_placement_group_validator(mocker, boto3_stubber):
    describe_placement_groups_response = {
        "PlacementGroups": [{"GroupName": "my-cluster", "State": "available", "Strategy": "cluster"}]
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_placement_groups",
            response=describe_placement_groups_response,
            expected_params={"GroupNames": ["my-cluster"]},
        )
    ]
    boto3_stubber("ec2", mocked_requests)

    # TODO test with invalid group name
    config_parser_dict = {"cluster default": {"placement_group": "my-cluster"}}
    utils.assert_param_validator(mocker, config_parser_dict)


def test_url_validator(mocker, boto3_stubber, capsys):
    head_object_response = {
        "AcceptRanges": "bytes",
        "ContentType": "text/html",
        "LastModified": "Thu, 16 Apr 2015 18:19:14 GMT",
        "ContentLength": 77,
        "VersionId": "null",
        "ETag": '"30a6ec7e1a9ad79c203d05a589c8b400"',
        "Metadata": {},
    }
    mocked_requests = [
        MockedBoto3Request(
            method="head_object", response=head_object_response, expected_params={"Bucket": "test", "Key": "test.json"}
        )
    ]
    boto3_stubber("s3", mocked_requests)

    mocker.patch("pcluster.config.validators.urllib.request.urlopen")
    tests = [("s3://test/test.json", None), ("http://test/test.json", None)]
    for template_url, expected_message in tests:
        config_parser_dict = {"cluster default": {"template_url": template_url}}
        utils.assert_param_validator(mocker, config_parser_dict, expected_message)

    # Test S3 URI in custom_chef_cookbook.
    tests = [
        (
            "s3://test/cookbook.tgz",
            None,
            MockedBoto3Request(
                method="head_object",
                response=head_object_response,
                expected_params={"Bucket": "test", "Key": "cookbook.tgz"},
            ),
        ),
        (
            "s3://failure/cookbook.tgz",
            (
                "WARNING: The configuration parameter 'custom_chef_cookbook' generated the following warnings:\n"
                "The S3 object does not exist or you do not have access to it.\n"
                "Please make sure the cluster nodes have access to it."
            ),
            MockedBoto3Request(
                method="head_object",
                response=head_object_response,
                expected_params={"Bucket": "failure", "Key": "cookbook.tgz"},
                generate_error=True,
                error_code=404,
            ),
        ),
    ]

    for custom_chef_cookbook_url, expected_message, mocked_request in tests:
        boto3_stubber("s3", mocked_request)
        mocker.patch("pcluster.config.validators.urllib.request.urlopen")
        config_parser_dict = {
            "cluster default": {
                "scheduler": "slurm",
                "s3_read_resource": "arn:aws:s3:::test*",
                "custom_chef_cookbook": custom_chef_cookbook_url,
            }
        }
        utils.assert_param_validator(mocker, config_parser_dict, capsys=capsys, expected_warning=expected_message)


@pytest.mark.parametrize(
    "config, num_calls, error_code, bucket, expected_message",
    [
        (
            {
                "cluster default": {"fsx_settings": "fsx"},
                "fsx fsx": {
                    "storage_capacity": 1200,
                    "import_path": "s3://test/test1/test2",
                    "export_path": "s3://test/test1/test2",
                    "auto_import_policy": "NEW",
                },
            },
            2,
            None,
            {"Bucket": "test"},
            "AutoImport is not supported for cross-region buckets.",
        ),
        (
            {
                "cluster default": {"fsx_settings": "fsx"},
                "fsx fsx": {
                    "storage_capacity": 1200,
                    "import_path": "s3://test/test1/test2",
                    "export_path": "s3://test/test1/test2",
                    "auto_import_policy": "NEW",
                },
            },
            2,
            "NoSuchBucket",
            {"Bucket": "test"},
            "The S3 bucket 'test' does not appear to exist.",
        ),
        (
            {
                "cluster default": {"fsx_settings": "fsx"},
                "fsx fsx": {
                    "storage_capacity": 1200,
                    "import_path": "s3://test/test1/test2",
                    "export_path": "s3://test/test1/test2",
                    "auto_import_policy": "NEW",
                },
            },
            2,
            "AccessDenied",
            {"Bucket": "test"},
            "You do not have access to the S3 bucket",
        ),
    ],
)
def test_auto_import_policy_validator(mocker, boto3_stubber, config, num_calls, error_code, bucket, expected_message):
    os.environ["AWS_DEFAULT_REGION"] = "eu-west-1"
    head_bucket_response = {
        "ResponseMetadata": {
            "AcceptRanges": "bytes",
            "ContentType": "text/html",
            "LastModified": "Thu, 16 Apr 2015 18:19:14 GMT",
            "ContentLength": 77,
            "VersionId": "null",
            "ETag": '"30a6ec7e1a9ad79c203d05a589c8b400"',
            "Metadata": {},
        }
    }
    get_bucket_location_response = {
        "ResponseMetadata": {
            "LocationConstraint": "af-south1",
        }
    }
    mocked_requests = []
    for _ in range(num_calls):
        mocked_requests.append(
            MockedBoto3Request(method="head_bucket", response=head_bucket_response, expected_params=bucket)
        )
    if error_code is None:
        mocked_requests.append(
            MockedBoto3Request(
                method="get_bucket_location", response=get_bucket_location_response, expected_params=bucket
            )
        )
    else:
        mocked_requests.append(
            MockedBoto3Request(
                method="get_bucket_location",
                response=get_bucket_location_response,
                expected_params=bucket,
                generate_error=error_code is not None,
                error_code=error_code,
            )
        )

    boto3_stubber("s3", mocked_requests)

    utils.assert_param_validator(mocker, config, expected_message)


@pytest.mark.parametrize(
    "config, num_calls, bucket, expected_message",
    [
        (
            {
                "cluster default": {"fsx_settings": "fsx"},
                "fsx fsx": {
                    "storage_capacity": 1200,
                    "import_path": "s3://test/test1/test2",
                    "export_path": "s3://test/test1/test2",
                },
            },
            2,
            {"Bucket": "test"},
            None,
        ),
        (
            {
                "cluster default": {"fsx_settings": "fsx"},
                "fsx fsx": {
                    "storage_capacity": 1200,
                    "import_path": "http://test/test.json",
                    "export_path": "s3://test/test1/test2",
                },
            },
            1,
            {"Bucket": "test"},
            "The value 'http://test/test.json' used for the parameter 'import_path' is not a valid S3 URI.",
        ),
    ],
)
def test_s3_validator(mocker, boto3_stubber, config, num_calls, bucket, expected_message):
    if bucket:
        _head_bucket_stubber(mocker, boto3_stubber, bucket, num_calls)
    utils.assert_param_validator(mocker, config, expected_message)


@pytest.mark.parametrize(
    "bucket, region, error_code, expected_message, client_error",
    [
        (
            "bucket",
            "us-east-1",
            None,
            None,
            False,
        ),
        (
            "bucket",
            "us-west-1",
            None,
            None,
            False,
        ),
        (
            "bucket",
            "eu-west-1",
            None,
            "cluster_resource_bucket must be in the same region of the cluster.",
            False,
        ),
        (
            "not_existed_bucket",
            "af-south-1",
            "NoSuchBucket",
            "The S3 bucket 'not_existed_bucket' does not appear to exist",
            True,
        ),
        (
            "access_denied_bucket",
            "af-south-1",
            "AccessDenied",
            "You do not have access to the S3 bucket 'access_denied_bucket'",
            True,
        ),
        (
            "unexpected_error_bucket",
            "af-south-1",
            None,
            "Unexpected error for S3 bucket",
            True,
        ),
    ],
)
def test_s3_bucket_region_validator(mocker, boto3_stubber, error_code, bucket, region, client_error, expected_message):
    os.environ["AWS_DEFAULT_REGION"] = "us-west-1" if region == "us-west-1" else "us-east-1"
    if region == "us-east-1":
        # The actual response when region is us-east-1 is
        # {'ResponseMetadata': {...}, 'LocationConstraint': None}
        # But botocore doesn't support mock None response. we mock the return as following
        get_bucket_location_response = {
            "ResponseMetadata": {},
        }
    else:
        get_bucket_location_response = {
            "ResponseMetadata": {},
            "LocationConstraint": region,
        }
    mocked_requests = []

    if error_code is None:
        mocked_requests.append(
            MockedBoto3Request(
                method="get_bucket_location",
                response=get_bucket_location_response,
                expected_params={"Bucket": bucket},
                generate_error=client_error is True,
            )
        )
    else:
        mocked_requests.append(
            MockedBoto3Request(
                method="get_bucket_location",
                response=get_bucket_location_response,
                expected_params={"Bucket": bucket},
                generate_error=error_code is not None,
                error_code=error_code,
            )
        )

    boto3_stubber("s3", mocked_requests)
    config = {
        "cluster default": {"cluster_resource_bucket": bucket},
    }
    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config)

    pcluster_config = utils.init_pcluster_config_from_configparser(config_parser, False, auto_refresh=False)
    errors, warnings = s3_bucket_region_validator("cluster_resource_bucket", bucket, pcluster_config)

    if expected_message:
        assert_that(errors[0]).contains(expected_message)
    else:
        assert_that(errors).is_empty()


def test_ec2_vpc_id_validator(mocker, boto3_stubber):
    mocked_requests = []

    # mock describe_vpc boto3 call
    describe_vpc_response = {
        "Vpcs": [
            {
                "VpcId": "vpc-12345678",
                "InstanceTenancy": "default",
                "Tags": [{"Value": "Default VPC", "Key": "Name"}],
                "State": "available",
                "DhcpOptionsId": "dopt-4ef69c2a",
                "CidrBlock": "172.31.0.0/16",
                "IsDefault": True,
            }
        ]
    }
    mocked_requests.append(
        MockedBoto3Request(
            method="describe_vpcs", response=describe_vpc_response, expected_params={"VpcIds": ["vpc-12345678"]}
        )
    )

    # mock describe_vpc_attribute boto3 call
    describe_vpc_attribute_response = {
        "VpcId": "vpc-12345678",
        "EnableDnsSupport": {"Value": True},
        "EnableDnsHostnames": {"Value": True},
    }
    mocked_requests.append(
        MockedBoto3Request(
            method="describe_vpc_attribute",
            response=describe_vpc_attribute_response,
            expected_params={"VpcId": "vpc-12345678", "Attribute": "enableDnsSupport"},
        )
    )
    mocked_requests.append(
        MockedBoto3Request(
            method="describe_vpc_attribute",
            response=describe_vpc_attribute_response,
            expected_params={"VpcId": "vpc-12345678", "Attribute": "enableDnsHostnames"},
        )
    )
    boto3_stubber("ec2", mocked_requests)

    # TODO mock and test invalid vpc-id
    for vpc_id, expected_message in [("vpc-12345678", None)]:
        config_parser_dict = {"cluster default": {"vpc_settings": "default"}, "vpc default": {"vpc_id": vpc_id}}
        utils.assert_param_validator(mocker, config_parser_dict, expected_message)


def test_ec2_subnet_id_validator(mocker, boto3_stubber):
    describe_subnets_response = {
        "Subnets": [
            {
                "AvailabilityZone": "us-east-2c",
                "AvailabilityZoneId": "use2-az3",
                "AvailableIpAddressCount": 248,
                "CidrBlock": "10.0.1.0/24",
                "DefaultForAz": False,
                "MapPublicIpOnLaunch": False,
                "State": "available",
                "SubnetId": "subnet-12345678",
                "VpcId": "vpc-06e4ab6c6cEXAMPLE",
                "OwnerId": "111122223333",
                "AssignIpv6AddressOnCreation": False,
                "Ipv6CidrBlockAssociationSet": [],
                "Tags": [{"Key": "Name", "Value": "MySubnet"}],
                "SubnetArn": "arn:aws:ec2:us-east-2:111122223333:subnet/subnet-12345678",
            }
        ]
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_subnets",
            response=describe_subnets_response,
            expected_params={"SubnetIds": ["subnet-12345678"]},
        )
    ]
    boto3_stubber("ec2", mocked_requests)

    # TODO test with invalid key
    config_parser_dict = {
        "cluster default": {"vpc_settings": "default"},
        "vpc default": {"master_subnet_id": "subnet-12345678"},
    }
    utils.assert_param_validator(mocker, config_parser_dict)


def test_ec2_security_group_validator(mocker, boto3_stubber):
    describe_security_groups_response = {
        "SecurityGroups": [
            {
                "IpPermissionsEgress": [],
                "Description": "My security group",
                "IpPermissions": [
                    {
                        "PrefixListIds": [],
                        "FromPort": 22,
                        "IpRanges": [{"CidrIp": "203.0.113.0/24"}],
                        "ToPort": 22,
                        "IpProtocol": "tcp",
                        "UserIdGroupPairs": [],
                    }
                ],
                "GroupName": "MySecurityGroup",
                "OwnerId": "123456789012",
                "GroupId": "sg-12345678",
            }
        ]
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_security_groups",
            response=describe_security_groups_response,
            expected_params={"GroupIds": ["sg-12345678"]},
        )
    ]
    boto3_stubber("ec2", mocked_requests)

    # TODO test with invalid key
    config_parser_dict = {
        "cluster default": {"vpc_settings": "default"},
        "vpc default": {"vpc_security_group_id": "sg-12345678"},
    }
    utils.assert_param_validator(mocker, config_parser_dict)


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        (
            {"throughput_mode": "bursting", "provisioned_throughput": 1024},
            "When specifying 'provisioned_throughput', the 'throughput_mode' must be set to 'provisioned'",
        ),
        ({"throughput_mode": "provisioned", "provisioned_throughput": 1024}, None),
        ({"shared_dir": "NONE"}, "NONE cannot be used as a shared directory"),
        ({"shared_dir": "/NONE"}, "/NONE cannot be used as a shared directory"),
        ({"shared_dir": "/efs"}, None),
    ],
)
def test_efs_validator(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": {"efs_settings": "default"}, "efs default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        ({"volume_type": "io1", "volume_size": 20, "volume_iops": 120}, None),
        (
            {"volume_type": "io1", "volume_size": 20, "volume_iops": 90},
            "IOPS rate must be between 100 and 64000 when provisioning io1 volumes.",
        ),
        (
            {"volume_type": "io1", "volume_size": 20, "volume_iops": 64001},
            "IOPS rate must be between 100 and 64000 when provisioning io1 volumes.",
        ),
        ({"volume_type": "io1", "volume_size": 20, "volume_iops": 1001}, "IOPS to volume size ratio of .* is too high"),
        ({"volume_type": "io2", "volume_size": 20, "volume_iops": 120}, None),
        (
            {"volume_type": "io2", "volume_size": 20, "volume_iops": 90},
            "IOPS rate must be between 100 and 256000 when provisioning io2 volumes.",
        ),
        (
            {"volume_type": "io2", "volume_size": 20, "volume_iops": 256001},
            "IOPS rate must be between 100 and 256000 when provisioning io2 volumes.",
        ),
        (
            {"volume_type": "io2", "volume_size": 20, "volume_iops": 20001},
            "IOPS to volume size ratio of .* is too high",
        ),
        ({"volume_type": "gp3", "volume_size": 20, "volume_iops": 3000}, None),
        (
            {"volume_type": "gp3", "volume_size": 20, "volume_iops": 2900},
            "IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.",
        ),
        (
            {"volume_type": "gp3", "volume_size": 20, "volume_iops": 16001},
            "IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.",
        ),
        (
            {"volume_type": "gp3", "volume_size": 20, "volume_iops": 10001},
            "IOPS to volume size ratio of .* is too high",
        ),
    ],
)
def test_raid_validators(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": {"raid_settings": "default"}, "raid default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "kms_key_id, expected_message",
    [
        ("9e8a129be-0e46-459d-865b-3a5bf974a22k", None),
        (
            "9e7a129be-0e46-459d-865b-3a5bf974a22k",
            "Key 'arn:aws:kms:us-east-1:12345678:key/9e7a129be-0e46-459d-865b-3a5bf974a22k' does not exist",
        ),
    ],
)
def test_kms_key_validator(mocker, boto3_stubber, kms_key_id, expected_message):
    _kms_key_stubber(mocker, boto3_stubber, kms_key_id, expected_message, 1)

    config_parser_dict = {
        "cluster default": {"fsx_settings": "fsx"},
        "fsx fsx": {
            "storage_capacity": 1200,
            "fsx_kms_key_id": kms_key_id,
            "deployment_type": "PERSISTENT_1",
            "per_unit_storage_throughput": 50,
        },
    }
    utils.assert_param_validator(
        mocker, config_parser_dict, expected_error=expected_message if expected_message else None
    )


def _kms_key_stubber(mocker, boto3_stubber, kms_key_id, expected_message, num_calls):
    describe_key_response = {
        "KeyMetadata": {
            "AWSAccountId": "1234567890",
            "Arn": "arn:aws:kms:us-east-1:1234567890:key/{0}".format(kms_key_id),
            "CreationDate": datetime.datetime(2019, 1, 10, 11, 25, 59, 128000),
            "Description": "",
            "Enabled": True,
            "KeyId": kms_key_id,
            "KeyManager": "CUSTOMER",
            "KeyState": "Enabled",
            "KeyUsage": "ENCRYPT_DECRYPT",
            "Origin": "AWS_KMS",
        }
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_key",
            response=expected_message if expected_message else describe_key_response,
            expected_params={"KeyId": kms_key_id},
            generate_error=True if expected_message else False,
        )
    ] * num_calls
    boto3_stubber("kms", mocked_requests)


@pytest.mark.parametrize(
    "section_dict, bucket, expected_error, num_calls",
    [
        (
            {"imported_file_chunk_size": 1024, "import_path": "s3://test", "storage_capacity": 1200},
            {"Bucket": "test"},
            None,
            1,
        ),
        (
            {"imported_file_chunk_size": 1024, "storage_capacity": 1200},
            None,
            "When specifying 'imported_file_chunk_size', the 'import_path' option must be specified",
            0,
        ),
        (
            {"export_path": "s3://test", "import_path": "s3://test", "storage_capacity": 1200},
            {"Bucket": "test"},
            None,
            2,
        ),
        (
            {"export_path": "s3://test", "storage_capacity": 1200},
            {"Bucket": "test"},
            "When specifying 'export_path', the 'import_path' option must be specified",
            0,
        ),
        ({"shared_dir": "NONE", "storage_capacity": 1200}, None, "NONE cannot be used as a shared directory", 0),
        ({"shared_dir": "/NONE", "storage_capacity": 1200}, None, "/NONE cannot be used as a shared directory", 0),
        ({"shared_dir": "/fsx"}, None, "the 'storage_capacity' option must be specified", 0),
        ({"shared_dir": "/fsx", "storage_capacity": 1200}, None, None, 0),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "fsx_kms_key_id": "9e8a129be-0e46-459d-865b-3a5bf974a22k",
                "storage_capacity": 1200,
                "per_unit_storage_throughput": 50,
            },
            None,
            None,
            0,
        ),
        (
            {"deployment_type": "PERSISTENT_1", "per_unit_storage_throughput": 200, "storage_capacity": 1200},
            None,
            None,
            0,
        ),
        (
            {
                "deployment_type": "SCRATCH_2",
                "fsx_kms_key_id": "9e8a129be-0e46-459d-865b-3a5bf974a22k",
                "storage_capacity": 1200,
            },
            None,
            "'fsx_kms_key_id' can only be used when 'deployment_type = PERSISTENT_1'",
            1,
        ),
        (
            {"deployment_type": "SCRATCH_1", "per_unit_storage_throughput": 200, "storage_capacity": 1200},
            None,
            "'per_unit_storage_throughput' can only be used when 'deployment_type = PERSISTENT_1'",
            0,
        ),
        (
            {"deployment_type": "PERSISTENT_1", "storage_capacity": 1200},
            None,
            "'per_unit_storage_throughput' must be specified when 'deployment_type = PERSISTENT_1'",
            0,
        ),
        (
            {
                "storage_capacity": 1200,
                "per_unit_storage_throughput": "50",
                "deployment_type": "PERSISTENT_1",
                "automatic_backup_retention_days": 2,
            },
            None,
            None,
            0,
        ),
        (
            {
                "storage_capacity": 1200,
                "deployment_type": "PERSISTENT_1",
                "per_unit_storage_throughput": "50",
                "automatic_backup_retention_days": 2,
                "daily_automatic_backup_start_time": "03:00",
                "copy_tags_to_backups": True,
            },
            None,
            None,
            0,
        ),
        (
            {"automatic_backup_retention_days": 2, "deployment_type": "SCRATCH_1"},
            None,
            "FSx automatic backup features can be used only with 'PERSISTENT_1' file systems",
            0,
        ),
        (
            {"daily_automatic_backup_start_time": "03:00"},
            None,
            "When specifying 'daily_automatic_backup_start_time', "
            "the 'automatic_backup_retention_days' option must be specified",
            0,
        ),
        (
            {"storage_capacity": 1200, "deployment_type": "PERSISTENT_1", "copy_tags_to_backups": True},
            None,
            "When specifying 'copy_tags_to_backups', the 'automatic_backup_retention_days' option must be specified",
            0,
        ),
        (
            {"storage_capacity": 1200, "deployment_type": "PERSISTENT_1", "copy_tags_to_backups": False},
            None,
            "When specifying 'copy_tags_to_backups', the 'automatic_backup_retention_days' option must be specified",
            0,
        ),
        (
            {"daily_automatic_backup_start_time": "03:00", "copy_tags_to_backups": True},
            None,
            "When specifying 'daily_automatic_backup_start_time', "
            "the 'automatic_backup_retention_days' option must be specified",
            0,
        ),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "automatic_backup_retention_days": 2,
                "imported_file_chunk_size": 1024,
                "export_path": "s3://test",
                "import_path": "s3://test",
                "storage_capacity": 1200,
            },
            {"Bucket": "test"},
            "Backups cannot be created on S3-linked file systems",
            0,
        ),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "automatic_backup_retention_days": 2,
                "export_path": "s3://test",
                "import_path": "s3://test",
                "storage_capacity": 1200,
            },
            {"Bucket": "test"},
            "Backups cannot be created on S3-linked file systems",
            0,
        ),
        (
            {
                "deployment_type": "SCRATCH_1",
                "storage_type": "HDD",
                "per_unit_storage_throughput": 12,
                "storage_capacity": 1200,
                "drive_cache_type": "READ",
            },
            None,
            "For HDD filesystems, 'deployment_type' must be 'PERSISTENT_1'",
            0,
        ),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "storage_type": "HDD",
                "per_unit_storage_throughput": 50,
                "storage_capacity": 1200,
                "drive_cache_type": "READ",
            },
            None,
            "For HDD filesystems, 'per_unit_storage_throughput' can only have the following values: {0}".format(
                FSX_HDD_THROUGHPUT
            ),
            0,
        ),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "storage_type": "SSD",
                "per_unit_storage_throughput": 12,
                "storage_capacity": 1200,
            },
            None,
            "For SSD filesystems, 'per_unit_storage_throughput' can only have the following values: {0}".format(
                FSX_SSD_THROUGHPUT
            ),
            0,
        ),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "storage_type": "SSD",
                "per_unit_storage_throughput": 50,
                "storage_capacity": 1200,
                "drive_cache_type": "NONE",
            },
            None,
            "The configuration parameter 'drive_cache_type' has an invalid value 'NONE'",
            0,
        ),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "storage_type": "SSD",
                "per_unit_storage_throughput": 50,
                "storage_capacity": 1200,
            },
            None,
            None,
            0,
        ),
        (
            {
                "deployment_type": "PERSISTENT_1",
                "per_unit_storage_throughput": 50,
                "storage_capacity": 1200,
                "drive_cache_type": "READ",
            },
            None,
            "'drive_cache_type' features can be used only with HDD filesystems",
            0,
        ),
        (
            {
                "data_compression_type": "LZ4",
                "fsx_backup_id": "backup-12345678",
            },
            None,
            "FSx data compression option (LZ4) cannot be specified when creating a filesystem from backup",
            0,
        ),
        (
            {
                "data_compression_type": "NONE",
                "fsx_backup_id": "backup-12345678",
            },
            None,
            "The configuration parameter 'data_compression_type' has an invalid value 'NONE'",
            0,
        ),
        (
            {
                "data_compression_type": "LZ4",
                "storage_capacity": 1200,
            },
            None,
            None,
            0,
        ),
    ],
)
def test_fsx_validator(mocker, boto3_stubber, section_dict, bucket, expected_error, num_calls):
    if bucket:
        _head_bucket_stubber(mocker, boto3_stubber, bucket, num_calls)
    if "fsx_kms_key_id" in section_dict:
        _kms_key_stubber(mocker, boto3_stubber, section_dict.get("fsx_kms_key_id"), None, 0 if expected_error else 1)
    config_parser_dict = {"cluster default": {"fsx_settings": "default"}, "fsx default": section_dict}
    if expected_error:
        expected_error = re.escape(expected_error)
    utils.assert_param_validator(mocker, config_parser_dict, expected_error=expected_error)


@pytest.mark.parametrize(
    "section_dict, expected_error, expected_warning",
    [
        (
            {"storage_capacity": 1, "deployment_type": "SCRATCH_1"},
            "Capacity for FSx SCRATCH_1 filesystem is 1,200 GB, 2,400 GB or increments of 3,600 GB",
            None,
        ),
        ({"storage_capacity": 1200, "deployment_type": "SCRATCH_1"}, None, None),
        ({"storage_capacity": 2400, "deployment_type": "SCRATCH_1"}, None, None),
        ({"storage_capacity": 3600, "deployment_type": "SCRATCH_1"}, None, None),
        (
            {"storage_capacity": 3600, "deployment_type": "SCRATCH_2"},
            "Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB",
            None,
        ),
        (
            {"storage_capacity": 3600, "deployment_type": "PERSISTENT_1", "per_unit_storage_throughput": 50},
            "Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB",
            None,
        ),
        (
            {"storage_capacity": 3601, "deployment_type": "PERSISTENT_1", "per_unit_storage_throughput": 50},
            "Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB",
            None,
        ),
        ({"storage_capacity": 7200}, None, None),
        (
            {"deployment_type": "SCRATCH_1"},
            "When specifying 'fsx' section, the 'storage_capacity' option must be specified",
            None,
        ),
        (
            {
                "storage_type": "HDD",
                "deployment_type": "PERSISTENT_1",
                "storage_capacity": 1801,
                "per_unit_storage_throughput": 40,
            },
            "Capacity for FSx PERSISTENT HDD 40 MB/s/TiB file systems is increments of 1,800 GiB",
            None,
        ),
        (
            {
                "storage_type": "HDD",
                "deployment_type": "PERSISTENT_1",
                "storage_capacity": 6001,
                "per_unit_storage_throughput": 12,
            },
            "Capacity for FSx PERSISTENT HDD 12 MB/s/TiB file systems is increments of 6,000 GiB",
            None,
        ),
        (
            {
                "storage_type": "HDD",
                "deployment_type": "PERSISTENT_1",
                "storage_capacity": 1800,
                "per_unit_storage_throughput": 40,
            },
            None,
            None,
        ),
        (
            {
                "storage_type": "HDD",
                "deployment_type": "PERSISTENT_1",
                "storage_capacity": 6000,
                "per_unit_storage_throughput": 12,
            },
            None,
            None,
        ),
    ],
)
def test_fsx_storage_capacity_validator(mocker, boto3_stubber, capsys, section_dict, expected_error, expected_warning):
    config_parser_dict = {"cluster default": {"fsx_settings": "default"}, "fsx default": section_dict}
    utils.assert_param_validator(
        mocker, config_parser_dict, capsys=capsys, expected_error=expected_error, expected_warning=expected_warning
    )


def _head_bucket_stubber(mocker, boto3_stubber, bucket, num_calls):
    head_bucket_response = {
        "ResponseMetadata": {
            "AcceptRanges": "bytes",
            "ContentType": "text/html",
            "LastModified": "Thu, 16 Apr 2015 18:19:14 GMT",
            "ContentLength": 77,
            "VersionId": "null",
            "ETag": '"30a6ec7e1a9ad79c203d05a589c8b400"',
            "Metadata": {},
        }
    }
    mocked_requests = [
        MockedBoto3Request(method="head_bucket", response=head_bucket_response, expected_params=bucket)
    ] * num_calls
    boto3_stubber("s3", mocked_requests)
    mocker.patch("pcluster.config.validators.urllib.request.urlopen")


@pytest.mark.parametrize(
    "fsx_vpc, ip_permissions, network_interfaces, expected_message",
    [
        (  # working case, right vpc and sg, multiple network interfaces
            "vpc-06e4ab6c6cEXAMPLE",
            [{"IpProtocol": "-1", "UserIdGroupPairs": [{"UserId": "123456789012", "GroupId": "sg-12345678"}]}],
            ["eni-09b9460295ddd4e5f", "eni-001b3cef7c78b45c4"],
            None,
        ),
        (  # working case, right vpc and sg, single network interface
            "vpc-06e4ab6c6cEXAMPLE",
            [{"IpProtocol": "-1", "UserIdGroupPairs": [{"UserId": "123456789012", "GroupId": "sg-12345678"}]}],
            ["eni-09b9460295ddd4e5f"],
            None,
        ),
        (  # not working case --> no network interfaces
            "vpc-06e4ab6c6cEXAMPLE",
            [{"IpProtocol": "-1", "UserIdGroupPairs": [{"UserId": "123456789012", "GroupId": "sg-12345678"}]}],
            [],
            "doesn't have Elastic Network Interfaces attached",
        ),
        (  # not working case --> wrong vpc
            "vpc-06e4ab6c6ccWRONG",
            [{"IpProtocol": "-1", "UserIdGroupPairs": [{"UserId": "123456789012", "GroupId": "sg-12345678"}]}],
            ["eni-09b9460295ddd4e5f"],
            "only support using FSx file system that is in the same VPC as the stack",
        ),
        (  # not working case --> wrong ip permissions in security group
            "vpc-06e4ab6c6cWRONG",
            [
                {
                    "PrefixListIds": [],
                    "FromPort": 22,
                    "IpRanges": [{"CidrIp": "203.0.113.0/24"}],
                    "ToPort": 22,
                    "IpProtocol": "tcp",
                    "UserIdGroupPairs": [],
                }
            ],
            ["eni-09b9460295ddd4e5f"],
            "does not satisfy mounting requirement",
        ),
    ],
)
def test_fsx_id_validator(mocker, boto3_stubber, fsx_vpc, ip_permissions, network_interfaces, expected_message):
    describe_file_systems_response = {
        "FileSystems": [
            {
                "VpcId": fsx_vpc,
                "NetworkInterfaceIds": network_interfaces,
                "SubnetIds": ["subnet-12345678"],
                "FileSystemType": "LUSTRE",
                "CreationTime": 1567636453.038,
                "ResourceARN": "arn:aws:fsx:us-west-2:111122223333:file-system/fs-0ff8da96d57f3b4e3",
                "StorageCapacity": 3600,
                "LustreConfiguration": {"WeeklyMaintenanceStartTime": "4:07:00"},
                "FileSystemId": "fs-0ff8da96d57f3b4e3",
                "DNSName": "fs-0ff8da96d57f3b4e3.fsx.us-west-2.amazonaws.com",
                "OwnerId": "059623208481",
                "Lifecycle": "AVAILABLE",
            }
        ]
    }
    fsx_mocked_requests = [
        MockedBoto3Request(
            method="describe_file_systems",
            response=describe_file_systems_response,
            expected_params={"FileSystemIds": ["fs-0ff8da96d57f3b4e3"]},
        )
    ]
    boto3_stubber("fsx", fsx_mocked_requests)

    describe_subnets_response = {
        "Subnets": [
            {
                "AvailabilityZone": "us-east-2c",
                "AvailabilityZoneId": "use2-az3",
                "AvailableIpAddressCount": 248,
                "CidrBlock": "10.0.1.0/24",
                "DefaultForAz": False,
                "MapPublicIpOnLaunch": False,
                "State": "available",
                "SubnetId": "subnet-12345678",
                "VpcId": "vpc-06e4ab6c6cEXAMPLE",
                "OwnerId": "111122223333",
                "AssignIpv6AddressOnCreation": False,
                "Ipv6CidrBlockAssociationSet": [],
                "Tags": [{"Key": "Name", "Value": "MySubnet"}],
                "SubnetArn": "arn:aws:ec2:us-east-2:111122223333:subnet/subnet-12345678",
            }
        ]
    }
    ec2_mocked_requests = [
        MockedBoto3Request(
            method="describe_subnets",
            response=describe_subnets_response,
            expected_params={"SubnetIds": ["subnet-12345678"]},
        )
    ] * 2

    if network_interfaces:
        network_interfaces_in_response = []
        for network_interface in network_interfaces:
            network_interfaces_in_response.append(
                {
                    "Association": {
                        "AllocationId": "eipalloc-01564b674a1a88a47",
                        "AssociationId": "eipassoc-02726ee370e175cea",
                        "IpOwnerId": "111122223333",
                        "PublicDnsName": "ec2-34-248-114-123.eu-west-1.compute.amazonaws.com",
                        "PublicIp": "34.248.114.123",
                    },
                    "Attachment": {
                        "AttachmentId": "ela-attach-0cf98331",
                        "DeleteOnTermination": False,
                        "DeviceIndex": 1,
                        "InstanceOwnerId": "amazon-aws",
                        "Status": "attached",
                    },
                    "AvailabilityZone": "eu-west-1a",
                    "Description": "Interface for NAT Gateway nat-0a8b0e0d28266841f",
                    "Groups": [{"GroupName": "default", "GroupId": "sg-12345678"}],
                    "InterfaceType": "nat_gateway",
                    "Ipv6Addresses": [],
                    "MacAddress": "0a:e5:8a:82:fd:24",
                    "NetworkInterfaceId": network_interface,
                    "OwnerId": "111122223333",
                    "PrivateDnsName": "ip-10-0-124-85.eu-west-1.compute.internal",
                    "PrivateIpAddress": "10.0.124.85",
                    "PrivateIpAddresses": [
                        {
                            "Association": {
                                "AllocationId": "eipalloc-01564b674a1a88a47",
                                "AssociationId": "eipassoc-02726ee370e175cea",
                                "IpOwnerId": "111122223333",
                                "PublicDnsName": "ec2-34-248-114-123.eu-west-1.compute.amazonaws.com",
                                "PublicIp": "34.248.114.123",
                            },
                            "Primary": True,
                            "PrivateDnsName": "ip-10-0-124-85.eu-west-1.compute.internal",
                            "PrivateIpAddress": "10.0.124.85",
                        }
                    ],
                    "RequesterId": "036872051663",
                    "RequesterManaged": True,
                    "SourceDestCheck": False,
                    "Status": "in-use",
                    "SubnetId": "subnet-12345678",
                    "TagSet": [],
                    "VpcId": fsx_vpc,
                }
            )
        describe_network_interfaces_response = {"NetworkInterfaces": network_interfaces_in_response}
        ec2_mocked_requests.append(
            MockedBoto3Request(
                method="describe_network_interfaces",
                response=describe_network_interfaces_response,
                expected_params={"NetworkInterfaceIds": network_interfaces},
            )
        )

        if fsx_vpc == "vpc-06e4ab6c6cEXAMPLE":
            # the describe security group is performed only if the VPC of the network interface is the same of the FSX
            describe_security_groups_response = {
                "SecurityGroups": [
                    {
                        "IpPermissionsEgress": ip_permissions,
                        "Description": "My security group",
                        "IpPermissions": ip_permissions,
                        "GroupName": "MySecurityGroup",
                        "OwnerId": "123456789012",
                        "GroupId": "sg-12345678",
                    }
                ]
            }
            ec2_mocked_requests.append(
                MockedBoto3Request(
                    method="describe_security_groups",
                    response=describe_security_groups_response,
                    expected_params={"GroupIds": ["sg-12345678"]},
                )
            )

    boto3_stubber("ec2", ec2_mocked_requests)
    fsx_spy = mocker.patch(
        "pcluster.config.cfn_param_types.get_fsx_info",
        return_value={"DNSName": "my.fsx.dns.name", "LustreConfiguration": {"MountName": "somemountname"}},
    )
    config_parser_dict = {
        "cluster default": {"fsx_settings": "default", "vpc_settings": "default"},
        "vpc default": {"master_subnet_id": "subnet-12345678"},
        "fsx default": {"fsx_fs_id": "fs-0ff8da96d57f3b4e3"},
    }
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)
    fsx_spy.assert_called_with("fs-0ff8da96d57f3b4e3")


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        ({"enable_intel_hpc_platform": "true", "base_os": "centos7"}, None),
        ({"enable_intel_hpc_platform": "true", "base_os": "centos8"}, None),
        ({"enable_intel_hpc_platform": "true", "base_os": "alinux2"}, "it is required to set the 'base_os'"),
        ({"enable_intel_hpc_platform": "true", "base_os": "ubuntu1804"}, "it is required to set the 'base_os'"),
        # intel hpc disabled, you can use any os
        ({"enable_intel_hpc_platform": "false", "base_os": "alinux2"}, None),
    ],
)
def test_intel_hpc_os_validator(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        (
            {"disable_hyperthreading": True, "extra_json": '{"cluster": {"cfn_scheduler_slots": "vcpus"}}'},
            "cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true",
        ),
        (
            {"disable_hyperthreading": True, "extra_json": '{"cluster": {"cfn_scheduler_slots": "cores"}}'},
            "cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true",
        ),
        (
            {"disable_hyperthreading": True, "extra_json": '{"cluster": {"cfn_scheduler_slots": 3}}'},
            "cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true",
        ),
        ({"disable_hyperthreading": True, "extra_json": '{"cluster": {"other_param": "fake_value"}}'}, None),
        ({"disable_hyperthreading": True}, None),
        ({"disable_hyperthreading": False, "extra_json": '{"cluster": {"cfn_scheduler_slots": "vcpus"}}'}, None),
        ({"disable_hyperthreading": False, "extra_json": '{"cluster": {"cfn_scheduler_slots": "cores"}}'}, None),
        ({"disable_hyperthreading": False, "extra_json": '{"cluster": {"cfn_scheduler_slots": 3}}'}, None),
    ],
)
def test_disable_hyperthreading_validator(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "section_dict, bucket, expected_message",
    [
        (
            {"imported_file_chunk_size": 0, "import_path": "s3://test-import", "storage_capacity": 1200},
            None,
            "has a minimum size of 1 MiB, and max size of 512,000 MiB",
        ),
        (
            {"imported_file_chunk_size": 1, "import_path": "s3://test-import", "storage_capacity": 1200},
            {"Bucket": "test-import"},
            None,
        ),
        (
            {"imported_file_chunk_size": 10, "import_path": "s3://test-import", "storage_capacity": 1200},
            {"Bucket": "test-import"},
            None,
        ),
        (
            {"imported_file_chunk_size": 512000, "import_path": "s3://test-import", "storage_capacity": 1200},
            {"Bucket": "test-import"},
            None,
        ),
        (
            {"imported_file_chunk_size": 512001, "import_path": "s3://test-import", "storage_capacity": 1200},
            None,
            "has a minimum size of 1 MiB, and max size of 512,000 MiB",
        ),
    ],
)
def test_fsx_imported_file_chunk_size_validator(mocker, boto3_stubber, section_dict, bucket, expected_message):
    if bucket:
        _head_bucket_stubber(mocker, boto3_stubber, bucket, num_calls=1)
    config_parser_dict = {"cluster default": {"fsx_settings": "default"}, "fsx default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "section_dict, expected_error, expected_warning",
    [
        ({"enable_efa": "NONE"}, "invalid value", None),
        ({"enable_efa": "compute", "scheduler": "sge"}, "is required to set the 'compute_instance_type'", None),
        (
            {"enable_efa": "compute", "compute_instance_type": "t2.large", "scheduler": "sge"},
            None,
            "You may see better performance using a cluster placement group",
        ),
        (
            {
                "enable_efa": "compute",
                "compute_instance_type": "t2.large",
                "base_os": "alinux2",
                "scheduler": "awsbatch",
            },
            "it is required to set the 'scheduler'",
            None,
        ),
        (
            {
                "enable_efa": "compute",
                "compute_instance_type": "t2.large",
                "base_os": "centos7",
                "scheduler": "sge",
                "placement_group": "DYNAMIC",
            },
            None,
            None,
        ),
        (
            {
                "enable_efa": "compute",
                "compute_instance_type": "t2.large",
                "base_os": "alinux2",
                "scheduler": "sge",
                "placement_group": "DYNAMIC",
            },
            None,
            None,
        ),
        # Additional instance type
        (
            {
                "enable_efa": "compute",
                "compute_instance_type": "additional-instance-type",
                "base_os": "alinux2",
                "scheduler": "sge",
                "placement_group": "DYNAMIC",
                "instance_types_data": json.dumps(
                    {
                        "additional-instance-type": {
                            "InstanceType": "additional-instance-type",
                            "NetworkInfo": {"EfaSupported": True},
                        }
                    }
                ),
            },
            None,
            None,
        ),
    ],
)
def test_efa_validator(boto3_stubber, mocker, capsys, section_dict, expected_error, expected_warning):
    if section_dict.get("enable_efa") != "NONE":
        mocked_requests = [
            MockedBoto3Request(
                method="describe_instance_types",
                response={"InstanceTypes": [{"InstanceType": "t2.large"}]},
                expected_params={"Filters": [{"Name": "network-info.efa-supported", "Values": ["true"]}]},
            )
        ]
        boto3_stubber("ec2", mocked_requests)
    config_parser_dict = {"cluster default": section_dict}

    # Patch to prevent instance type validators to fail with additional instance type
    extra_patches = {
        "pcluster.config.validators.get_supported_instance_types": ["t2.large", "additional-instance-type"],
    }

    utils.assert_param_validator(
        mocker,
        config_parser_dict,
        expected_error,
        capsys,
        expected_warning,
        extra_patches=extra_patches,
        use_mock_instance_type_info=False,
    )


@pytest.mark.parametrize(
    "cluster_dict, expected_error",
    [
        # EFAGDR without EFA
        (
            {"enable_efa_gdr": "compute"},
            "The parameter 'enable_efa_gdr' can be used only in combination with 'enable_efa'",
        ),
        # EFAGDR with EFA
        ({"enable_efa": "compute", "enable_efa_gdr": "compute"}, None),
        # EFA withoud EFAGDR
        ({"enable_efa": "compute"}, None),
    ],
)
def test_efa_gdr_validator(cluster_dict, expected_error):
    config_parser_dict = {
        "cluster default": cluster_dict,
    }

    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    pcluster_config = utils.init_pcluster_config_from_configparser(config_parser, False, auto_refresh=False)
    enable_efa_gdr_value = pcluster_config.get_section("cluster").get_param_value("enable_efa_gdr")

    errors, warnings = efa_gdr_validator("enable_efa_gdr", enable_efa_gdr_value, pcluster_config)
    if expected_error:
        assert_that(errors[0]).matches(expected_error)
    else:
        assert_that(errors).is_empty()


@pytest.mark.parametrize(
    "ip_permissions, ip_permissions_egress, expected_message",
    [
        ([], [], "must allow all traffic in and out from itself"),
        (
            [{"IpProtocol": "-1", "UserIdGroupPairs": [{"UserId": "123456789012", "GroupId": "sg-12345678"}]}],
            [],
            "must allow all traffic in and out from itself",
        ),
        (
            [{"IpProtocol": "-1", "UserIdGroupPairs": [{"UserId": "123456789012", "GroupId": "sg-12345678"}]}],
            [{"IpProtocol": "-1", "UserIdGroupPairs": [{"UserId": "123456789012", "GroupId": "sg-12345678"}]}],
            None,
        ),
        (
            [
                {
                    "PrefixListIds": [],
                    "FromPort": 22,
                    "IpRanges": [{"CidrIp": "203.0.113.0/24"}],
                    "ToPort": 22,
                    "IpProtocol": "tcp",
                    "UserIdGroupPairs": [],
                }
            ],
            [],
            "must allow all traffic in and out from itself",
        ),
    ],
)
def test_efa_validator_with_vpc_security_group(
    boto3_stubber, mocker, ip_permissions, ip_permissions_egress, expected_message
):
    describe_security_groups_response = {
        "SecurityGroups": [
            {
                "IpPermissionsEgress": ip_permissions_egress,
                "Description": "My security group",
                "IpPermissions": ip_permissions,
                "GroupName": "MySecurityGroup",
                "OwnerId": "123456789012",
                "GroupId": "sg-12345678",
            }
        ]
    }
    mocked_requests = [
        MockedBoto3Request(
            method="describe_security_groups",
            response=describe_security_groups_response,
            expected_params={"GroupIds": ["sg-12345678"]},
        ),
        MockedBoto3Request(
            method="describe_instance_types",
            response={"InstanceTypes": [{"InstanceType": "t2.large"}]},
            expected_params={"Filters": [{"Name": "network-info.efa-supported", "Values": ["true"]}]},
        ),
        MockedBoto3Request(
            method="describe_security_groups",
            response=describe_security_groups_response,
            expected_params={"GroupIds": ["sg-12345678"]},
        ),  # it is called two times, for vpc_security_group_id validation and to validate efa
    ]

    boto3_stubber("ec2", mocked_requests)

    config_parser_dict = {
        "cluster default": {
            "enable_efa": "compute",
            "compute_instance_type": "t2.large",
            "placement_group": "DYNAMIC",
            "vpc_settings": "default",
            "scheduler": "sge",
        },
        "vpc default": {"vpc_security_group_id": "sg-12345678"},
    }
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "cluster_section_dict, ebs_section_dict, expected_message",
    [
        (
            {"ebs_settings": "vol1, vol2, vol3, vol4, vol5, vol6"},
            {
                "vol1": {"shared_dir": "/vol1"},
                "vol2": {"shared_dir": "/vol2"},
                "vol3": {"shared_dir": "/vol3"},
                "vol4": {"shared_dir": "/vol4"},
                "vol5": {"shared_dir": "/vol5"},
                "vol6": {"shared_dir": "/vol6"},
            },
            "Invalid number of 'ebs' sections specified. Max 5 expected.",
        ),
        (
            {"ebs_settings": "vol1, vol2 "},
            {"vol1": {"shared_dir": "vol1"}, "vol2": {"volume_type": "io1"}},
            "When using more than 1 EBS volume, shared_dir is required under each EBS section",
        ),
        (
            {"ebs_settings": "vol1,vol2"},
            {"vol1": {"shared_dir": "/NONE"}, "vol2": {"shared_dir": "vol2"}},
            "/NONE cannot be used as a shared directory",
        ),
        (
            {"ebs_settings": "vol1, vol2 "},
            {"vol1": {"shared_dir": "/vol1"}, "vol2": {"shared_dir": "NONE"}},
            "NONE cannot be used as a shared directory",
        ),
    ],
)
def test_ebs_settings_validator(mocker, cluster_section_dict, ebs_section_dict, expected_message):
    config_parser_dict = {"cluster default": cluster_section_dict}
    if ebs_section_dict:
        for vol in ebs_section_dict:
            config_parser_dict["ebs {0}".format(vol)] = ebs_section_dict.get(vol)
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        ({"shared_dir": "NONE"}, "NONE cannot be used as a shared directory"),
        ({"shared_dir": "/NONE"}, "/NONE cannot be used as a shared directory"),
        ({"shared_dir": "/NONEshared"}, None),
    ],
)
def test_shared_dir_validator(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "base_os, instance_type, access_from, expected_error, expected_warning",
    [
        ("centos7", "t2.medium", None, None, None),
        ("centos8", "t2.medium", None, None, None),
        ("ubuntu1804", "t2.medium", None, None, None),
        ("ubuntu1804", "t2.medium", "1.2.3.4/32", None, None),
        ("centos7", "t2.medium", "0.0.0.0/0", None, None),
        ("centos8", "t2.medium", "0.0.0.0/0", None, None),
        ("alinux2", "t2.medium", None, None, None),
        ("alinux2", "t2.nano", None, None, "is recommended to use an instance type with at least"),
        ("alinux2", "t2.micro", None, None, "is recommended to use an instance type with at least"),
        ("ubuntu1804", "m6g.xlarge", None, None, None),
        ("alinux2", "m6g.xlarge", None, None, None),
        ("centos7", "m6g.xlarge", None, None, None),
        ("centos8", "m6g.xlarge", None, None, None),
    ],
)
def test_dcv_enabled_validator(
    mocker, base_os, instance_type, expected_error, expected_warning, access_from, caplog, capsys
):
    config_parser_dict = {
        "cluster default": {"base_os": base_os, "dcv_settings": "dcv"},
        "dcv dcv": {"enable": "master"},
    }
    if access_from:
        config_parser_dict["dcv dcv"]["access_from"] = access_from

    architectures = ["x86_64"] if instance_type.startswith("t2") else ["arm64"]
    extra_patches = {
        "pcluster.config.validators.get_supported_instance_types": ["t2.nano", "t2.micro", "t2.medium", "m6g.xlarge"],
        "pcluster.config.validators.get_supported_architectures_for_instance_type": architectures,
        "pcluster.config.cfn_param_types.get_supported_architectures_for_instance_type": architectures,
    }
    utils.assert_param_validator(
        mocker, config_parser_dict, expected_error, capsys, expected_warning, extra_patches=extra_patches
    )
    access_from_error_msg = DCV_MESSAGES["warnings"]["access_from_world"].format(port=8443)
    assert_that(access_from_error_msg in caplog.text).is_equal_to(not access_from or access_from == "0.0.0.0/0")


@pytest.mark.parametrize(
    "architecture, base_os, expected_message",
    [
        # Supported combinations
        ("x86_64", "alinux2", None),
        ("x86_64", "centos7", None),
        ("x86_64", "centos8", None),
        ("x86_64", "ubuntu1804", None),
        ("arm64", "ubuntu1804", None),
        ("arm64", "alinux2", None),
        ("arm64", "centos7", None),
        ("arm64", "centos8", None),
        # Unsupported combinations
        (
            "UnsupportedArchitecture",
            "alinux2",
            FSX_MESSAGES["errors"]["unsupported_architecture"].format(
                supported_architectures=list(FSX_SUPPORTED_ARCHITECTURES_OSES.keys())
            ),
        ),
    ],
)
def test_fsx_architecture_os_validator(mocker, architecture, base_os, expected_message):
    config_parser_dict = {
        "cluster default": {"base_os": base_os, "fsx_settings": "fsx"},
        "fsx fsx": {"storage_capacity": 3200},
    }
    expected_message = re.escape(expected_message) if expected_message else None
    extra_patches = {
        "pcluster.config.cfn_param_types.get_supported_architectures_for_instance_type": [architecture],
        "pcluster.config.validators.get_supported_architectures_for_instance_type": [architecture],
    }
    utils.assert_param_validator(mocker, config_parser_dict, expected_message, extra_patches=extra_patches)


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        (
            {"initial_queue_size": "0", "maintain_initial_size": True},
            "maintain_initial_size cannot be set to true if initial_queue_size is 0",
        ),
        (
            {"scheduler": "awsbatch", "maintain_initial_size": True},
            "maintain_initial_size is not supported when using awsbatch as scheduler",
        ),
    ],
)
def test_maintain_initial_size_validator(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "cluster_section_dict, expected_message",
    [
        # SIT cluster, perfectly fine
        ({"scheduler": "slurm"}, None),
        # HIT cluster with one queue
        ({"scheduler": "slurm", "queue_settings": "queue1"}, None),
        ({"scheduler": "slurm", "queue_settings": "queue1,queue2,queue3,queue4,queue5"}, None),
        ({"scheduler": "slurm", "queue_settings": "queue1, queue2"}, None),
        (
            {"scheduler": "slurm", "queue_settings": "queue1,queue2,queue3,queue4,queue5,queue6"},
            "Invalid number of 'queue' sections specified. Max 5 expected.",
        ),
        (
            {"scheduler": "slurm", "queue_settings": "queue_1"},
            (
                "Invalid queue name 'queue_1'. Queue section names can be at most 30 chars long, must begin with"
                " a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use"
                " 'default' as a queue section name."
            ),
        ),
        (
            {"scheduler": "slurm", "queue_settings": "default"},
            (
                "Invalid queue name 'default'. Queue section names can be at most 30 chars long, must begin with"
                " a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use"
                " 'default' as a queue section name."
            ),
        ),
        (
            {"scheduler": "slurm", "queue_settings": "queue1, default"},
            (
                "Invalid queue name '.*'. Queue section names can be at most 30 chars long, must begin with"
                " a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use"
                " 'default' as a queue section name."
            ),
        ),
        (
            {"scheduler": "slurm", "queue_settings": "QUEUE"},
            (
                "Invalid queue name 'QUEUE'. Queue section names can be at most 30 chars long, must begin with"
                " a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use"
                " 'default' as a queue section name."
            ),
        ),
        (
            {"scheduler": "slurm", "queue_settings": "aQUEUEa"},
            (
                "Invalid queue name 'aQUEUEa'. Queue section names can be at most 30 chars long, must begin with"
                " a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use"
                " 'default' as a queue section name."
            ),
        ),
        ({"scheduler": "slurm", "queue_settings": "my-default-queue"}, None),
    ],
)
def test_queue_settings_validator(mocker, cluster_section_dict, expected_message):
    config_parser_dict = {"cluster default": cluster_section_dict}
    if cluster_section_dict.get("queue_settings"):
        for i, queue_name in enumerate(cluster_section_dict["queue_settings"].split(",")):
            config_parser_dict["queue {0}".format(queue_name.strip())] = {
                "compute_resource_settings": "cr{0}".format(i),
                "disable_hyperthreading": True,
                "enable_efa": True,
            }
            config_parser_dict["compute_resource cr{0}".format(i)] = {"instance_type": "t2.micro"}

    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "cluster_dict, queue_dict, expected_error_messages, expected_warning_messages",
    [
        (
            {"queue_settings": "default"},
            {"compute_resource_settings": "cr1,cr2", "enable_efa": True, "disable_hyperthreading": True},
            [
                "Duplicate instance type 't2.micro' found in queue 'default'. "
                "Compute resources in the same queue must use different instance types"
            ],
            [
                "EFA was enabled on queue 'default', but instance type 't2.micro' "
                "defined in compute resource settings cr1 does not support EFA.",
                "EFA was enabled on queue 'default', but instance type 't2.micro' "
                "defined in compute resource settings cr2 does not support EFA.",
            ],
        ),
        (
            {"queue_settings": "default"},
            {"compute_resource_settings": "cr3,cr4", "enable_efa": True, "disable_hyperthreading": True},
            [
                "Duplicate instance type 'c4.xlarge' found in queue 'default'. "
                "Compute resources in the same queue must use different instance types"
            ],
            [
                "EFA was enabled on queue 'default', but instance type 'c4.xlarge' "
                "defined in compute resource settings cr3 does not support EFA.",
                "EFA was enabled on queue 'default', but instance type 'c4.xlarge' "
                "defined in compute resource settings cr4 does not support EFA.",
            ],
        ),
        (
            {"queue_settings": "default"},
            {"compute_resource_settings": "cr1,cr3", "enable_efa": True, "disable_hyperthreading": True},
            None,
            [
                "EFA was enabled on queue 'default', but instance type 't2.micro' "
                "defined in compute resource settings cr1 does not support EFA.",
                "EFA was enabled on queue 'default', but instance type 'c4.xlarge' "
                "defined in compute resource settings cr3 does not support EFA.",
            ],
        ),
        (
            {"queue_settings": "default"},
            {"compute_resource_settings": "cr2,cr4", "enable_efa": True, "disable_hyperthreading": True},
            None,
            [
                "EFA was enabled on queue 'default', but instance type 't2.micro' "
                "defined in compute resource settings cr2 does not support EFA.",
                "EFA was enabled on queue 'default', but instance type 'c4.xlarge' "
                "defined in compute resource settings cr4 does not support EFA.",
            ],
        ),
        (
            {"queue_settings": "default"},
            {"compute_resource_settings": "cr2,cr4", "enable_efa": True, "enable_efa_gdr": True},
            None,
            [
                "EFA was enabled on queue 'default', but instance type 't2.micro' "
                "defined in compute resource settings cr2 does not support EFA.",
                "EFA GDR was enabled on queue 'default', but instance type 't2.micro' "
                "defined in compute resource settings cr2 does not support EFA GDR.",
                "EFA was enabled on queue 'default', but instance type 'c4.xlarge' "
                "defined in compute resource settings cr4 does not support EFA.",
                "EFA GDR was enabled on queue 'default', but instance type 'c4.xlarge' "
                "defined in compute resource settings cr4 does not support EFA GDR.",
            ],
        ),
        (
            {"queue_settings": "default"},
            {"compute_resource_settings": "efa_instance", "enable_efa_gdr": True},
            ["The parameter 'enable_efa_gdr' can be used only in combination with 'enable_efa'"],
            None,
        ),
        ({"queue_settings": "default"}, {"compute_resource_settings": "cr1"}, None, None),
        (
            {"queue_settings": "default", "enable_efa": "compute", "disable_hyperthreading": True},
            {"compute_resource_settings": "cr1", "enable_efa": True, "disable_hyperthreading": True},
            [
                "Parameter 'enable_efa' can be used only in 'cluster' or in 'queue' section",
                "Parameter 'disable_hyperthreading' can be used only in 'cluster' or in 'queue' section",
            ],
            [
                "EFA was enabled on queue 'default', but instance type 't2.micro' "
                "defined in compute resource settings cr1 does not support EFA."
            ],
        ),
        (
            {
                "queue_settings": "default",
                "enable_efa": "compute",
                "enable_efa_gdr": "compute",
                "disable_hyperthreading": True,
            },
            {
                "compute_resource_settings": "cr1",
                "enable_efa": False,
                "enable_efa_gdr": False,
                "disable_hyperthreading": False,
            },
            [
                "Parameter 'enable_efa' can be used only in 'cluster' or in 'queue' section",
                "Parameter 'enable_efa_gdr' can be used only in 'cluster' or in 'queue' section",
                "Parameter 'disable_hyperthreading' can be used only in 'cluster' or in 'queue' section",
            ],
            None,
        ),
        (
            {"queue_settings": "default"},
            {"compute_resource_settings": "efa_instance", "enable_efa": True},
            None,
            None,
        ),
    ],
)
def test_queue_validator(cluster_dict, queue_dict, expected_error_messages, expected_warning_messages):
    config_parser_dict = {
        "cluster default": cluster_dict,
        "queue default": queue_dict,
        "compute_resource cr1": {"instance_type": "t2.micro"},
        "compute_resource cr2": {"instance_type": "t2.micro"},
        "compute_resource cr3": {"instance_type": "c4.xlarge"},
        "compute_resource cr4": {"instance_type": "c4.xlarge"},
        "compute_resource efa_instance": {"instance_type": "p3dn.24xlarge"},
    }

    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    pcluster_config = utils.init_pcluster_config_from_configparser(config_parser, False, auto_refresh=False)

    efa_instance_compute_resource = pcluster_config.get_section("compute_resource", "efa_instance")
    if efa_instance_compute_resource:
        # Override `enable_efa` and `enable_efa_gdr` default value for instance with efa support
        efa_instance_compute_resource.get_param("enable_efa").value = True
        efa_instance_compute_resource.get_param("enable_efa_gdr").value = True

    errors, warnings = queue_validator("queue", "default", pcluster_config)

    if expected_error_messages:
        assert_that(expected_error_messages).is_equal_to(errors)
    else:
        assert_that(errors).is_empty()

    if expected_warning_messages:
        assert_that(expected_warning_messages).is_equal_to(warnings)
    else:
        assert_that(warnings).is_empty()


@pytest.mark.parametrize(
    "param_value, expected_message",
    [
        (
            "section1!2",
            "Invalid label 'section1!2' in param 'queue_settings'. "
            "Section labels can only contain alphanumeric characters, dashes or underscores.",
        ),
        (
            "section!123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyz_",
            "Invalid label 'section!123456789...' in param 'queue_settings'. "
            "Section labels can only contain alphanumeric characters, dashes or underscores.",
        ),
        ("section-1", None),
        ("section_1", None),
        (
            "section_123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyz_",
            "Invalid label 'section_123456789...' in param 'queue_settings'. "
            "The maximum length allowed for section labels is 64 characters",
        ),
    ],
)
def test_settings_validator(param_value, expected_message):
    errors, warnings = settings_validator("queue_settings", param_value, None)
    if expected_message:
        assert_that(errors and len(errors) == 1).is_true()
        assert_that(errors[0]).is_equal_to(expected_message)
    else:
        assert_that(errors).is_empty()


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        ({"min_count": -1, "initial_count": -1}, "Parameter 'min_count' must be 0 or greater than 0"),
        (
            {"min_count": 0, "initial_count": 1, "spot_price": -1.1},
            "Parameter 'spot_price' must be 0 or greater than 0",
        ),
        (
            {"min_count": 1, "max_count": 0, "initial_count": 1},
            "Parameter 'max_count' must be greater than or equal to 'min_count'",
        ),
        ({"min_count": 0, "max_count": 0, "initial_count": 0}, "Parameter 'max_count' must be 1 or greater than 1"),
        ({"min_count": 1, "max_count": 2, "spot_price": 1.5, "initial_count": 1}, None),
        (
            {"min_count": 2, "max_count": 4, "initial_count": 1},
            "Parameter 'initial_count' must be greater than or equal to 'min_count'",
        ),
        (
            {"min_count": 2, "max_count": 4, "initial_count": 5},
            "Parameter 'initial_count' must be lower than or equal to 'max_count'",
        ),
    ],
)
def test_compute_resource_validator(mocker, section_dict, expected_message):
    config_parser_dict = {
        "cluster default": {"queue_settings": "default"},
        "queue default": {"compute_resource_settings": "default"},
        "compute_resource default": section_dict,
    }

    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    mocker.patch(
        "pcluster.config.cfn_param_types.get_supported_architectures_for_instance_type", return_value=["x86_64"]
    )
    instance_type_info_mock = mocker.MagicMock()
    mocker.patch(
        "pcluster.config.cfn_param_types.InstanceTypeInfo.init_from_instance_type", return_value=instance_type_info_mock
    )
    instance_type_info_mock.max_network_interface_count.return_value = 1
    mocker.patch("pcluster.config.validators.get_supported_architectures_for_instance_type", return_value=["x86_64"])

    pcluster_config = utils.init_pcluster_config_from_configparser(config_parser, False)

    errors, warnings = compute_resource_validator("compute_resource", "default", pcluster_config)

    if expected_message:
        assert_that(expected_message in errors)
    else:
        assert_that(errors).is_empty()


@pytest.mark.parametrize(
    "cluster_section_dict, sections_dict, expected_message",
    [
        (
            {"vpc_settings": "vpc1, vpc2"},
            {"vpc vpc1": {}, "vpc vpc2": {}},
            "The value of 'vpc_settings' parameter is invalid. It can only contain a single vpc section label",
        ),
        (
            {"efs_settings": "efs1, efs2"},
            {"efs efs1": {}, "efs efs2": {}},
            "The value of 'efs_settings' parameter is invalid. It can only contain a single efs section label",
        ),
    ],
)
def test_single_settings_validator(mocker, cluster_section_dict, sections_dict, expected_message):
    config_parser_dict = {"cluster default": cluster_section_dict}
    if sections_dict:
        for key, section in sections_dict.items():
            config_parser_dict[key] = section
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


#########
#
# architecture validator tests
#
# Two things make it difficult to test validators that key on architecture in the same way that:
# 1) architecture is a derived parameter and cannot be configured directly via the config file
# 2) many validators key on the architecture, which makes it impossible to test some combinations of
#    parameters for validators that run later than others, because those run earlier will have
#    already raised exceptions.
#
# Thus, the following code mocks the pcluster_config object passed to the validator functions
# and calls those functions directly (as opposed to patching functions and instantiating a config
# as would be done when running `pcluster create/update`).
#
#########


def get_default_pcluster_sections_dict():
    """Return a dict similar in structure to that of a cluster config file."""
    default_pcluster_sections_dict = {}
    for section_default_dict in DefaultDict:
        if section_default_dict.name == "pcluster":  # Get rid of the extra layer in this case
            default_pcluster_sections_dict["cluster"] = section_default_dict.value.get("cluster")
        else:
            default_pcluster_sections_dict[section_default_dict.name] = section_default_dict.value
    return default_pcluster_sections_dict


def make_pcluster_config_mock(mocker, config_dict):
    """Mock the calls that made on a pcluster_config by validator functions."""
    cluster_config_dict = get_default_pcluster_sections_dict()
    for section_key in config_dict:
        cluster_config_dict = utils.merge_dicts(cluster_config_dict.get(section_key), config_dict.get(section_key))

    section_to_mocks = {}
    for section_key, section_dict in config_dict.items():
        section_mock = mocker.MagicMock()
        section_mock.get_param_value.side_effect = lambda param: section_dict.get(param)
        section_to_mocks[section_key] = section_mock

    pcluster_config_mock = mocker.MagicMock()
    pcluster_config_mock.get_section.side_effect = lambda section: section_to_mocks.get(section)
    return pcluster_config_mock


def run_architecture_validator_test(
    mocker,
    config,
    constrained_param_section,
    constrained_param_name,
    param_name,
    param_val,
    validator,
    expected_warnings,
    expected_errors,
):
    """Run a test for a validator that's concerned with the architecture param."""
    mocked_pcluster_config = make_pcluster_config_mock(mocker, config)
    errors, warnings = validator(param_name, param_val, mocked_pcluster_config)

    mocked_pcluster_config.get_section.assert_called_once_with(constrained_param_section)
    mocked_pcluster_config.get_section.side_effect(constrained_param_section).get_param_value.assert_called_with(
        constrained_param_name
    )
    assert_that(len(warnings)).is_equal_to(len(expected_warnings))
    for warnings, expected_warnings in zip(warnings, expected_warnings):
        assert_that(warnings).matches(re.escape(expected_warnings))

    assert_that(len(errors)).is_equal_to(len(expected_errors))
    for errors, expected_errors in zip(errors, expected_errors):
        assert_that(errors).matches(re.escape(expected_errors))


@pytest.mark.parametrize(
    "enabled, architecture, expected_errors",
    [
        (True, "x86_64", []),
        (True, "arm64", ["instance types and an AMI that support these architectures"]),
        (False, "x86_64", []),
        (False, "arm64", []),
    ],
)
def test_intel_hpc_architecture_validator(mocker, enabled, architecture, expected_errors):
    """Verify that setting enable_intel_hpc_platform is invalid when architecture != x86_64."""
    config_dict = {"cluster": {"enable_intel_hpc_platform": enabled, "architecture": architecture}}
    run_architecture_validator_test(
        mocker,
        config_dict,
        "cluster",
        "architecture",
        "enable_intel_hpc_platform",
        enabled,
        intel_hpc_architecture_validator,
        [],
        expected_errors,
    )


@pytest.mark.parametrize(
    "base_os, architecture, expected_warnings, expected_errors",
    [
        # All OSes supported for x86_64
        ("alinux2", "x86_64", [], []),
        ("centos7", "x86_64", [], []),
        ("centos8", "x86_64", [], []),
        ("ubuntu1804", "x86_64", [], []),
        # Only a subset of OSes supported for arm64
        ("alinux2", "arm64", [], []),
        (
            "centos7",
            "arm64",
            [
                "Warning: The aarch64 CentOS 7 OS is not validated for the 6th generation aarch64 instances "
                "(M6g, C6g, etc.). To proceed please provide a custom_ami, "
                "for more info see: https://wiki.centos.org/Cloud/AWS#aarch64_notes"
            ],
            [],
        ),
        ("centos8", "arm64", [], []),
        ("ubuntu1804", "arm64", [], []),
    ],
)
def test_architecture_os_validator(mocker, base_os, architecture, expected_warnings, expected_errors):
    """Verify that the correct set of OSes is supported for each supported architecture."""
    config_dict = {"cluster": {"base_os": base_os, "architecture": architecture}}
    run_architecture_validator_test(
        mocker,
        config_dict,
        "cluster",
        "architecture",
        "base_os",
        base_os,
        architecture_os_validator,
        expected_warnings,
        expected_errors,
    )


@pytest.mark.parametrize(
    "disable_hyperthreading, architecture, expected_errors",
    [
        (True, "x86_64", []),
        (False, "x86_64", []),
        (
            True,
            "arm64",
            ["disable_hyperthreading is only supported on instance types that support these architectures"],
        ),
        (False, "arm64", []),
    ],
)
def test_disable_hyperthreading_architecture_validator(mocker, disable_hyperthreading, architecture, expected_errors):
    config_dict = {"cluster": {"architecture": architecture, "disable_hyperthreading": disable_hyperthreading}}
    run_architecture_validator_test(
        mocker,
        config_dict,
        "cluster",
        "architecture",
        "disable_hyperthreading",
        disable_hyperthreading,
        disable_hyperthreading_architecture_validator,
        [],
        expected_errors,
    )


@pytest.mark.parametrize(
    "head_node_architecture, compute_architecture, compute_instance_type, expected_errors",
    [
        # Single compute_instance_type
        ("x86_64", "x86_64", "c5.xlarge", []),
        (
            "x86_64",
            "arm64",
            "m6g.xlarge",
            ["none of which are compatible with the architecture supported by the master_instance_type"],
        ),
        (
            "arm64",
            "x86_64",
            "c5.xlarge",
            ["none of which are compatible with the architecture supported by the master_instance_type"],
        ),
        ("arm64", "arm64", "m6g.xlarge", []),
        ("x86_64", "x86_64", "optimal", []),
        # Function to get supported architectures shouldn't be called because compute_instance_type arg
        # are instance families.
        ("x86_64", None, "m6g", []),
        ("x86_64", None, "c5", []),
        # The validator must handle the case where compute_instance_type is a CSV list
        ("arm64", "arm64", "m6g.xlarge,r6g.xlarge", []),
        (
            "x86_64",
            "arm64",
            "m6g.xlarge,r6g.xlarge",
            ["none of which are compatible with the architecture supported by the master_instance_type"] * 2,
        ),
    ],
)
def test_instances_architecture_compatibility_validator(
    mocker, caplog, head_node_architecture, compute_architecture, compute_instance_type, expected_errors
):
    def internal_is_instance_type(itype):
        return "." in itype or itype == "optimal"

    supported_architectures_patch = mocker.patch(
        "pcluster.config.validators.get_supported_architectures_for_instance_type", return_value=[compute_architecture]
    )
    is_instance_type_patch = mocker.patch(
        "pcluster.config.validators.is_instance_type_format", side_effect=internal_is_instance_type
    )
    logger_patch = mocker.patch.object(LOGFILE_LOGGER, "debug")
    run_architecture_validator_test(
        mocker,
        {"cluster": {"architecture": head_node_architecture}},
        "cluster",
        "architecture",
        "compute_instance_type",
        compute_instance_type,
        instances_architecture_compatibility_validator,
        [],
        expected_errors,
    )
    compute_instance_types = compute_instance_type.split(",")
    non_instance_families = [
        instance_type for instance_type in compute_instance_types if internal_is_instance_type(instance_type)
    ]
    assert_that(supported_architectures_patch.call_count).is_equal_to(len(non_instance_families))
    assert_that(logger_patch.call_count).is_equal_to(len(compute_instance_types) - len(non_instance_families))
    assert_that(is_instance_type_patch.call_count).is_equal_to(len(compute_instance_types))


@pytest.mark.parametrize(
    "section_dict, bucket, num_calls, expected_error",
    [
        (
            {
                "fsx_backup_id": "backup-0ff8da96d57f3b4e3",
                "deployment_type": "PERSISTENT_1",
                "per_unit_storage_throughput": 50,
            },
            None,
            0,
            "When restoring an FSx Lustre file system from backup, 'deployment_type' cannot be specified.",
        ),
        (
            {"fsx_backup_id": "backup-0ff8da96d57f3b4e3", "storage_capacity": 7200},
            None,
            0,
            "When restoring an FSx Lustre file system from backup, 'storage_capacity' cannot be specified.",
        ),
        (
            {
                "fsx_backup_id": "backup-0ff8da96d57f3b4e3",
                "deployment_type": "PERSISTENT_1",
                "per_unit_storage_throughput": 100,
            },
            None,
            0,
            "When restoring an FSx Lustre file system from backup, 'per_unit_storage_throughput' cannot be specified.",
        ),
        (
            {
                "fsx_backup_id": "backup-0ff8da96d57f3b4e3",
                "imported_file_chunk_size": 1024,
                "export_path": "s3://test",
                "import_path": "s3://test",
            },
            {"Bucket": "test"},
            2,
            "When restoring an FSx Lustre file system from backup, 'imported_file_chunk_size' cannot be specified.",
        ),
        (
            {
                "fsx_backup_id": "backup-0ff8da96d57f3b4e3",
                "fsx_kms_key_id": "somekey",
                "deployment_type": "PERSISTENT_1",
                "per_unit_storage_throughput": 50,
            },
            None,
            0,
            "When restoring an FSx Lustre file system from backup, 'fsx_kms_key_id' cannot be specified.",
        ),
        (
            {
                "fsx_backup_id": "backup-00000000000000000",
                "deployment_type": "PERSISTENT_1",
                "per_unit_storage_throughput": 50,
            },
            None,
            0,
            "Failed to retrieve backup with Id 'backup-00000000000000000'",
        ),
    ],
)
def test_fsx_lustre_backup_validator(mocker, boto3_stubber, section_dict, bucket, num_calls, expected_error):
    valid_key_id = "backup-0ff8da96d57f3b4e3"
    describe_backups_response = {
        "Backups": [
            {
                "BackupId": valid_key_id,
                "Lifecycle": "AVAILABLE",
                "Type": "USER_INITIATED",
                "CreationTime": 1594159673.559,
                "FileSystem": {
                    "StorageCapacity": 7200,
                    "StorageType": "SSD",
                    "LustreConfiguration": {"DeploymentType": "PERSISTENT_1", "PerUnitStorageThroughput": 200},
                },
            }
        ]
    }

    if bucket:
        _head_bucket_stubber(mocker, boto3_stubber, bucket, num_calls)
    generate_describe_backups_error = section_dict.get("fsx_backup_id") != valid_key_id
    fsx_mocked_requests = [
        MockedBoto3Request(
            method="describe_backups",
            response=expected_error if generate_describe_backups_error else describe_backups_response,
            expected_params={"BackupIds": [section_dict.get("fsx_backup_id")]},
            generate_error=generate_describe_backups_error,
        )
    ]
    boto3_stubber("fsx", fsx_mocked_requests)

    if "fsx_kms_key_id" in section_dict:
        describe_key_response = {"KeyMetadata": {"KeyId": section_dict.get("fsx_kms_key_id")}}
        kms_mocked_requests = [
            MockedBoto3Request(
                method="describe_key",
                response=describe_key_response,
                expected_params={"KeyId": section_dict.get("fsx_kms_key_id")},
            )
        ]
        boto3_stubber("kms", kms_mocked_requests)

    config_parser_dict = {"cluster default": {"fsx_settings": "default"}, "fsx default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_error=expected_error)


#########
#
# ignored FSx params validator test
#
# Testing a validator that requires the fsx_fs_id parameter to be specified requires a lot of
# boto3 stubbing due to the complexity contained in the fsx_id_validator.
#
# Thus, the following code mocks the pcluster_config object passed to the validator functions
# and calls the validator directly.
#
#########


@pytest.mark.parametrize(
    "section_dict, expected_error",
    [
        ({"fsx_fs_id": "fs-0123456789abcdef0", "shared_dir": "/fsx"}, None),
        (
            {"fsx_fs_id": "fs-0123456789abcdef0", "shared_dir": "/fsx", "storage_capacity": 3600},
            "storage_capacity is ignored when specifying an existing Lustre file system",
        ),
    ],
)
def test_fsx_ignored_parameters_validator(mocker, section_dict, expected_error):
    mocked_pcluster_config = utils.get_mocked_pcluster_config(mocker)
    fsx_section = CfnSection(FSX, mocked_pcluster_config, "default")
    for param_key, param_value in section_dict.items():
        param = FSX.get("params").get(param_key).get("type", CfnParam)
        param.value = param_value
        fsx_section.set_param(param_key, param)
    mocked_pcluster_config.add_section(fsx_section)
    errors, warnings = fsx_ignored_parameters_validator("fsx", "default", mocked_pcluster_config)
    assert_that(warnings).is_empty()
    if expected_error:
        assert_that(errors[0]).matches(expected_error)
    else:
        assert_that(errors).is_empty()


@pytest.mark.parametrize(
    "section_dict, expected_error",
    [
        ({"volume_type": "standard", "volume_size": 15}, None),
        ({"volume_type": "standard", "volume_size": 0}, "The size of standard volumes must be at least 1 GiB"),
        ({"volume_type": "standard", "volume_size": 1025}, "The size of standard volumes can not exceed 1024 GiB"),
        ({"volume_type": "io1", "volume_size": 15}, None),
        ({"volume_type": "io1", "volume_size": 3}, "The size of io1 volumes must be at least 4 GiB"),
        ({"volume_type": "io1", "volume_size": 16385}, "The size of io1 volumes can not exceed 16384 GiB"),
        ({"volume_type": "io2", "volume_size": 15}, None),
        ({"volume_type": "io2", "volume_size": 3}, "The size of io2 volumes must be at least 4 GiB"),
        ({"volume_type": "io2", "volume_size": 65537}, "The size of io2 volumes can not exceed 65536 GiB"),
        ({"volume_type": "gp2", "volume_size": 15}, None),
        ({"volume_type": "gp2", "volume_size": 0}, "The size of gp2 volumes must be at least 1 GiB"),
        ({"volume_type": "gp2", "volume_size": 16385}, "The size of gp2 volumes can not exceed 16384 GiB"),
        ({"volume_type": "gp3", "volume_size": 15}, None),
        ({"volume_type": "gp3", "volume_size": 0}, "The size of gp3 volumes must be at least 1 GiB"),
        ({"volume_type": "gp3", "volume_size": 16385}, "The size of gp3 volumes can not exceed 16384 GiB"),
        ({"volume_type": "st1", "volume_size": 500}, None),
        ({"volume_type": "st1", "volume_size": 20}, "The size of st1 volumes must be at least 500 GiB"),
        ({"volume_type": "st1", "volume_size": 16385}, "The size of st1 volumes can not exceed 16384 GiB"),
        ({"volume_type": "sc1", "volume_size": 500}, None),
        ({"volume_type": "sc1", "volume_size": 20}, "The size of sc1 volumes must be at least 500 GiB"),
        ({"volume_type": "sc1", "volume_size": 16385}, "The size of sc1 volumes can not exceed 16384 GiB"),
    ],
)
def test_ebs_volume_type_size_validator(mocker, section_dict, caplog, expected_error):
    config_parser_dict = {"cluster default": {"ebs_settings": "default"}, "ebs default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_error)


def test_ebs_allowed_values_all_have_volume_size_bounds():
    """Ensure that all known EBS volume types are accounted for by the volume size validator."""
    allowed_values_all_have_volume_size_bounds = set(ALLOWED_VALUES["volume_types"]) <= set(
        EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS.keys()
    )
    assert_that(allowed_values_all_have_volume_size_bounds).is_true()


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        ({"volume_type": "io1", "volume_size": 20, "volume_iops": 120}, None),
        (
            {"volume_type": "io1", "volume_size": 20, "volume_iops": 90},
            "IOPS rate must be between 100 and 64000 when provisioning io1 volumes.",
        ),
        (
            {"volume_type": "io1", "volume_size": 20, "volume_iops": 64001},
            "IOPS rate must be between 100 and 64000 when provisioning io1 volumes.",
        ),
        ({"volume_type": "io1", "volume_size": 20, "volume_iops": 1001}, "IOPS to volume size ratio of .* is too high"),
        ({"volume_type": "io2", "volume_size": 20, "volume_iops": 120}, None),
        (
            {"volume_type": "io2", "volume_size": 20, "volume_iops": 90},
            "IOPS rate must be between 100 and 256000 when provisioning io2 volumes.",
        ),
        (
            {"volume_type": "io2", "volume_size": 20, "volume_iops": 256001},
            "IOPS rate must be between 100 and 256000 when provisioning io2 volumes.",
        ),
        (
            {"volume_type": "io2", "volume_size": 20, "volume_iops": 20001},
            "IOPS to volume size ratio of .* is too high",
        ),
        ({"volume_type": "gp3", "volume_size": 20, "volume_iops": 3000}, None),
        (
            {"volume_type": "gp3", "volume_size": 20, "volume_iops": 2900},
            "IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.",
        ),
        (
            {"volume_type": "gp3", "volume_size": 20, "volume_iops": 16001},
            "IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.",
        ),
        (
            {"volume_type": "gp3", "volume_size": 20, "volume_iops": 10001},
            "IOPS to volume size ratio of .* is too high",
        ),
    ],
)
def test_ebs_volume_iops_validator(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": {"ebs_settings": "default"}, "ebs default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "section_dict, snapshot_size, state, partition, expected_warning, expected_error, "
    "raise_error_when_getting_snapshot_info",
    [
        (
            {"volume_size": 100, "ebs_snapshot_id": "snap-1234567890abcdef0"},
            50,
            "completed",
            "aws-cn",
            "The specified volume size is larger than snapshot size. In order to use the full capacity of the "
            "volume, you'll need to manually resize the partition "
            "according to this doc: "
            "https://docs.amazonaws.cn/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html",
            None,
            False,
        ),
        (
            {"volume_size": 100, "ebs_snapshot_id": "snap-1234567890abcdef0"},
            50,
            "completed",
            "aws-us-gov",
            "The specified volume size is larger than snapshot size. In order to use the full capacity of the "
            "volume, you'll need to manually resize the partition "
            "according to this doc: "
            "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html",
            None,
            False,
        ),
        (
            {"volume_size": 100, "ebs_snapshot_id": "snap-1234567890abcdef0"},
            50,
            "incompleted",
            "aws-us-gov",
            "Snapshot snap-1234567890abcdef0 is in state 'incompleted' not 'completed'",
            None,
            False,
        ),
        ({"ebs_snapshot_id": "snap-1234567890abcdef0"}, 50, "completed", "partition", None, None, False),
        (
            {"volume_size": 100, "ebs_snapshot_id": "snap-1234567891abcdef0"},
            120,
            "completed",
            "aws-us-gov",
            None,
            "The EBS volume size of the section 'default' must not be smaller than 120, because it is the size of the "
            "provided snapshot snap-1234567891abcdef0",
            False,
        ),
        (
            {"volume_size": 100, "ebs_snapshot_id": "snap-1234567890abcdef0"},
            None,
            "completed",
            "aws-cn",
            None,
            "Unable to get volume size for snapshot snap-1234567890abcdef0",
            False,
        ),
        (
            {"ebs_snapshot_id": "snap-1234567890abcdef0"},
            20,
            "completed",
            "aws",
            None,
            "some message",
            True,
        ),
    ],
)
def test_ebs_volume_size_snapshot_validator(
    section_dict,
    snapshot_size,
    state,
    partition,
    mocker,
    expected_warning,
    expected_error,
    raise_error_when_getting_snapshot_info,
    capsys,
):
    ebs_snapshot_id = section_dict["ebs_snapshot_id"]

    describe_snapshots_response = {
        "Description": "This is my snapshot",
        "Encrypted": False,
        "VolumeId": "vol-049df61146c4d7901",
        "State": state,
        "VolumeSize": snapshot_size,
        "StartTime": "2014-02-28T21:28:32.000Z",
        "Progress": "100%",
        "OwnerId": "012345678910",
        "SnapshotId": ebs_snapshot_id,
    }
    mocker.patch("pcluster.config.cfn_param_types.get_ebs_snapshot_info", return_value=describe_snapshots_response)
    if raise_error_when_getting_snapshot_info:
        mocker.patch("pcluster.config.validators.get_ebs_snapshot_info", side_effect=Exception(expected_error))
    else:
        mocker.patch("pcluster.config.validators.get_ebs_snapshot_info", return_value=describe_snapshots_response)
    mocker.patch(
        "pcluster.config.validators.get_partition", return_value="aws-cn" if partition == "aws-cn" else "aws-us-gov"
    )
    config_parser_dict = {"cluster default": {"ebs_settings": "default"}, "ebs default": section_dict}
    utils.assert_param_validator(
        mocker, config_parser_dict, expected_error=expected_error, capsys=capsys, expected_warning=expected_warning
    )


@pytest.mark.parametrize(
    "cluster_section_dict, ebs_section_dict1, ebs_section_dict2, expected_message",
    [
        (
            {"shared_dir": "shared_directory", "ebs_settings": "vol1"},
            {"volume_size": 30},
            {},
            None,
        ),
        (
            {"shared_dir": "shared_directory", "ebs_settings": "vol1"},
            {"shared_dir": "shared_directory1"},
            {},
            "'shared_dir' can not be specified both in cluster section and EBS section",
        ),
        (
            {"shared_dir": "shared_directory", "ebs_settings": "vol1, vol2"},
            {"shared_dir": "shared_directory1", "volume_size": 30},
            {"shared_dir": "shared_directory2", "volume_size": 30},
            "'shared_dir' can not be specified in cluster section when using multiple EBS volumes",
        ),
        (
            {"ebs_settings": "vol1, vol2"},
            {"shared_dir": "shared_directory1", "volume_size": 30},
            {"shared_dir": "shared_directory2", "volume_size": 30},
            None,
        ),
        (
            {"ebs_settings": "vol1"},
            {"volume_size": 30},
            {},
            None,
        ),
        (
            {"ebs_settings": "vol1"},
            {},
            {},
            None,
        ),
        (
            {"shared_dir": "shared_directory"},
            {},
            {},
            None,
        ),
    ],
)
def test_duplicate_shared_dir_validator(
    mocker, cluster_section_dict, ebs_section_dict1, ebs_section_dict2, expected_message
):
    config_parser_dict = {
        "cluster default": cluster_section_dict,
        "ebs vol1": ebs_section_dict1,
        "ebs vol2": ebs_section_dict2,
    }

    utils.assert_param_validator(mocker, config_parser_dict, expected_error=expected_message)


@pytest.mark.parametrize(
    "extra_json, expected_message",
    [
        (
            {"extra_json": {"cluster": {"cfn_scheduler_slots": "1"}}},
            "It is highly recommended to use the disable_hyperthreading parameter in order to control the "
            "hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json",
        ),
        (
            {"extra_json": {"cluster": {"cfn_scheduler_slots": "vcpus"}}},
            "It is highly recommended to use the disable_hyperthreading parameter in order to control the "
            "hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json",
        ),
        (
            {"extra_json": {"cluster": {"cfn_scheduler_slots": "cores"}}},
            "It is highly recommended to use the disable_hyperthreading parameter in order to control the "
            "hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json",
        ),
    ],
)
def test_extra_json_validator(mocker, capsys, extra_json, expected_message):
    config_parser_dict = {"cluster default": extra_json}
    utils.assert_param_validator(mocker, config_parser_dict, capsys=capsys, expected_warning=expected_message)


@pytest.mark.parametrize(
    "cluster_dict, architecture, expected_error",
    [
        ({"base_os": "alinux2", "enable_efa": "compute"}, "x86_64", None),
        ({"base_os": "alinux2", "enable_efa": "compute"}, "arm64", None),
        ({"base_os": "centos8", "enable_efa": "compute"}, "x86_64", None),
        ({"base_os": "centos8"}, "x86_64", None),
        (
            {"base_os": "centos8", "enable_efa": "compute"},
            "arm64",
            "EFA currently not supported on centos8 for arm64 architecture",
        ),
        ({"base_os": "centos8"}, "arm64", None),  # must not fail because by default EFA is disabled
        ({"base_os": "ubuntu1804", "enable_efa": "compute"}, "x86_64", None),
        ({"base_os": "ubuntu1804", "enable_efa": "compute"}, "arm64", None),
    ],
)
def test_efa_os_arch_validator(mocker, cluster_dict, architecture, expected_error):
    mocker.patch(
        "pcluster.config.cfn_param_types.BaseOSCfnParam.get_instance_type_architecture", return_value=architecture
    )

    config_parser_dict = {"cluster default": cluster_dict}
    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    pcluster_config = utils.init_pcluster_config_from_configparser(config_parser, False, auto_refresh=False)
    pcluster_config.get_section("cluster").get_param("architecture").value = architecture
    enable_efa_value = pcluster_config.get_section("cluster").get_param_value("enable_efa")

    errors, warnings = efa_os_arch_validator("enable_efa", enable_efa_value, pcluster_config)
    if expected_error:
        assert_that(errors[0]).matches(expected_error)
    else:
        assert_that(errors).is_empty()


@pytest.mark.parametrize(
    "section_dict, expected_message",
    [
        ({"volume_type": "gp3", "volume_throughput": 125}, None),
        (
            {"volume_type": "gp3", "volume_throughput": 100},
            "Throughput must be between 125 MB/s and 1000 MB/s when provisioning gp3 volumes.",
        ),
        (
            {"volume_type": "gp3", "volume_throughput": 1001},
            "Throughput must be between 125 MB/s and 1000 MB/s when provisioning gp3 volumes.",
        ),
        ({"volume_type": "gp3", "volume_throughput": 125, "volume_iops": 3000}, None),
        (
            {"volume_type": "gp3", "volume_throughput": 760, "volume_iops": 3000},
            "Throughput to IOPS ratio of .* is too high",
        ),
        ({"volume_type": "gp3", "volume_throughput": 760, "volume_iops": 10000}, None),
    ],
)
def test_ebs_volume_throughput_validator(mocker, section_dict, expected_message):
    config_parser_dict = {"cluster default": {"ebs_settings": "default"}, "ebs default": section_dict}
    utils.assert_param_validator(mocker, config_parser_dict, expected_message)


@pytest.mark.parametrize(
    "region, expected_message",
    [
        ("invalid-region", "Region 'invalid-region' is not yet officially supported "),
        ("us-east-1", None),
    ],
)
def test_region_validator(mocker, region, expected_message):
    pcluster_config = utils.get_mocked_pcluster_config(mocker)
    pcluster_config.region = region

    errors, warnings = region_validator("aws", None, pcluster_config)
    if expected_message:
        assert_that(len(errors)).is_greater_than(0)
        assert_that(errors[0]).matches(expected_message)
    else:
        assert_that(errors).is_empty()


@pytest.mark.parametrize(
    "usage_class, supported_usage_classes, expected_error_message, expected_warning_message",
    [
        ("ondemand", ["ondemand", "spot"], None, None),
        ("spot", ["ondemand", "spot"], None, None),
        ("ondemand", ["ondemand"], None, None),
        ("spot", ["spot"], None, None),
        ("spot", [], None, "Could not check support for usage class 'spot' with instance type 'instance-type'"),
        ("ondemand", [], None, "Could not check support for usage class 'ondemand' with instance type 'instance-type'"),
        ("spot", ["ondemand"], "Usage type 'spot' not supported with instance type 'instance-type'", None),
        ("ondemand", ["spot"], "Usage type 'ondemand' not supported with instance type 'instance-type'", None),
    ],
)
def test_check_usage_class(
    mocker, usage_class, supported_usage_classes, expected_error_message, expected_warning_message
):
    # This test checks the common logic triggered from cluster_type_validator and queue_compute_type_validator.
    instance_type_info_mock = mocker.MagicMock()
    mocker.patch(
        "pcluster.config.cfn_param_types.InstanceTypeInfo.init_from_instance_type", return_value=instance_type_info_mock
    )
    instance_type_info_mock.supported_usage_classes.return_value = supported_usage_classes

    errors = []
    warnings = []
    check_usage_class("instance-type", usage_class, errors, warnings)

    if expected_error_message:
        assert_that(errors).contains(expected_error_message)
    else:
        assert_that(errors).is_empty()

    if expected_warning_message:
        assert_that(warnings).contains(expected_warning_message)
    else:
        assert_that(warnings).is_empty()


@pytest.mark.parametrize(
    "scheduler, expected_usage_class_check", [("sge", True), ("torque", True), ("slurm", True), ("awsbatch", False)]
)
def test_cluster_type_validator(mocker, scheduler, expected_usage_class_check):
    # Usage class validation logic is tested in `test_check_usage_class`.
    # This test only makes sure that the logic is triggered from validator.
    mock = mocker.patch("pcluster.config.validators.check_usage_class", return_value=None)
    cluster_dict = {"compute_instance_type": "t2.micro", "scheduler": scheduler}
    config_parser_dict = {"cluster default": cluster_dict}
    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    pcluster_config = utils.init_pcluster_config_from_configparser(config_parser, False, auto_refresh=False)
    errors, warnings = cluster_type_validator("compute_type", "spot", pcluster_config)
    if expected_usage_class_check:
        mock.assert_called_with("t2.micro", "spot", [], [])
    else:
        mock.assert_not_called()

    assert_that(errors).is_equal_to([])
    assert_that(warnings).is_equal_to([])


@pytest.mark.parametrize("compute_type", [("ondemand"), ("spot")])
def test_queue_compute_type_validator(mocker, compute_type):
    # Usage class validation logic is tested in `test_check_usage_class`.
    # This test only makes sure that the logic is triggered from validator.
    mock = mocker.patch("pcluster.config.validators.check_usage_class", return_value=None)

    config_parser_dict = {
        "cluster default": {
            "queue_settings": "q1",
        },
        "queue q1": {"compute_resource_settings": "q1cr1, q1cr2", "compute_type": compute_type},
        "compute_resource q1cr1": {"instance_type": "q1cr1_instance_type"},
        "compute_resource q1cr2": {"instance_type": "q1cr2_instance_type"},
    }

    config_parser = configparser.ConfigParser()
    config_parser.read_dict(config_parser_dict)

    pcluster_config = utils.init_pcluster_config_from_configparser(config_parser, False, auto_refresh=False)
    errors, warnings = queue_compute_type_validator("queue", "q1", pcluster_config)
    mock.assert_has_calls(
        [
            mocker.call("q1cr1_instance_type", compute_type, [], []),
            mocker.call("q1cr2_instance_type", compute_type, [], []),
        ],
        any_order=True,
    )

    assert_that(errors).is_equal_to([])
    assert_that(warnings).is_equal_to([])

[((50, 1, 50, 17), 'pytest.fixture', 'pytest.fixture', ({}, {}), '()', False, 'import pytest\n'), ((55, 1, 90, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(56, 4, 56, 54): '"""section_dict, expected_message, expected_warning"""', (57, 4, 89, 5): "[({'scheduler': 'sge', 'initial_queue_size': 1, 'max_queue_size': 2,\n    'maintain_initial_size': True}, None, None), ({'scheduler': 'sge',\n    'initial_queue_size': 3, 'max_queue_size': 2, 'maintain_initial_size': \n    True},\n    'initial_queue_size must be fewer than or equal to max_queue_size',\n    None), ({'scheduler': 'sge', 'initial_queue_size': 3, 'max_queue_size':\n    2, 'maintain_initial_size': False},\n    'initial_queue_size must be fewer than or equal to max_queue_size',\n    None), ({'scheduler': 'awsbatch', 'min_vcpus': 1, 'desired_vcpus': 2,\n    'max_vcpus': 3}, None, None), ({'scheduler': 'awsbatch', 'min_vcpus': 3,\n    'desired_vcpus': 2, 'max_vcpus': 3},\n    'desired_vcpus must be greater than or equal to min_vcpus', None), ({\n    'scheduler': 'awsbatch', 'min_vcpus': 1, 'desired_vcpus': 4,\n    'max_vcpus': 3},\n    'desired_vcpus must be fewer than or equal to max_vcpus', None), ({\n    'scheduler': 'awsbatch', 'min_vcpus': 4, 'desired_vcpus': 4,\n    'max_vcpus': 3}, 'max_vcpus must be greater than or equal to min_vcpus',\n    None), ({'scheduler': 'awsbatch'}, None,\n    'If you do not specify a key pair')]"}, {}), "('section_dict, expected_message, expected_warning',\n    [({'scheduler': 'sge', 'initial_queue_size': 1, 'max_queue_size': 2,\n    'maintain_initial_size': True}, None, None), ({'scheduler': 'sge',\n    'initial_queue_size': 3, 'max_queue_size': 2, 'maintain_initial_size': \n    True},\n    'initial_queue_size must be fewer than or equal to max_queue_size',\n    None), ({'scheduler': 'sge', 'initial_queue_size': 3, 'max_queue_size':\n    2, 'maintain_initial_size': False},\n    'initial_queue_size must be fewer than or equal to max_queue_size',\n    None), ({'scheduler': 'awsbatch', 'min_vcpus': 1, 'desired_vcpus': 2,\n    'max_vcpus': 3}, None, None), ({'scheduler': 'awsbatch', 'min_vcpus': 3,\n    'desired_vcpus': 2, 'max_vcpus': 3},\n    'desired_vcpus must be greater than or equal to min_vcpus', None), ({\n    'scheduler': 'awsbatch', 'min_vcpus': 1, 'desired_vcpus': 4,\n    'max_vcpus': 3},\n    'desired_vcpus must be fewer than or equal to max_vcpus', None), ({\n    'scheduler': 'awsbatch', 'min_vcpus': 4, 'desired_vcpus': 4,\n    'max_vcpus': 3}, 'max_vcpus must be greater than or equal to min_vcpus',\n    None), ({'scheduler': 'awsbatch'}, None,\n    'If you do not specify a key pair')])", False, 'import pytest\n'), ((98, 1, 100, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(99, 4, 99, 37): '"""instance_type, expected_message"""', (99, 39, 99, 115): "[('t2.micro', None), ('c4.xlarge', None), ('c5.xlarge', 'is not supported')]"}, {}), "('instance_type, expected_message', [('t2.micro',\n    None), ('c4.xlarge', None), ('c5.xlarge', 'is not supported')])", False, 'import pytest\n'), ((106, 1, 106, 102), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(106, 25, 106, 58): '"""instance_type, expected_message"""', (106, 60, 106, 101): "[('t2.micro', None), ('c4.xlarge', None)]"}, {}), "('instance_type, expected_message', [('t2.micro',\n    None), ('c4.xlarge', None)])", False, 'import pytest\n'), ((112, 1, 126, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(113, 4, 113, 67): '"""scheduler, instance_type, expected_message, expected_warnings"""', (114, 4, 125, 5): "[('sge', 't2.micro', None, None), ('sge', 'c4.xlarge', None, None), ('sge',\n    'c5.xlarge', 'is not supported', None), ('awsbatch', 't2.micro', None,\n    None), ('awsbatch', 'c4.xlarge', 'is not supported', None), ('awsbatch',\n    't2', None, None), ('awsbatch', 'optimal', None, None), ('sge',\n    'p4d.24xlarge', None, 'has 4 Network Interfaces.'), ('slurm',\n    'p4d.24xlarge', None, None)]"}, {}), "(\n    'scheduler, instance_type, expected_message, expected_warnings', [(\n    'sge', 't2.micro', None, None), ('sge', 'c4.xlarge', None, None), (\n    'sge', 'c5.xlarge', 'is not supported', None), ('awsbatch', 't2.micro',\n    None, None), ('awsbatch', 'c4.xlarge', 'is not supported', None), (\n    'awsbatch', 't2', None, None), ('awsbatch', 'optimal', None, None), (\n    'sge', 'p4d.24xlarge', None, 'has 4 Network Interfaces.'), ('slurm',\n    'p4d.24xlarge', None, None)])", False, 'import pytest\n'), ((157, 1, 172, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(158, 4, 158, 67): '"""image_architecture, bad_ami_message, bad_architecture_message"""', (159, 4, 171, 5): "[('x86_64', None, None), ('arm64', None,\n    'incompatible with the architecture supported by the instance type chosen for the head node'\n    ), ('arm64', 'Unable to get information for AMI',\n    'incompatible with the architecture supported by the instance type chosen for the head node'\n    )]"}, {}), "(\n    'image_architecture, bad_ami_message, bad_architecture_message', [(\n    'x86_64', None, None), ('arm64', None,\n    'incompatible with the architecture supported by the instance type chosen for the head node'\n    ), ('arm64', 'Unable to get information for AMI',\n    'incompatible with the architecture supported by the instance type chosen for the head node'\n    )])", False, 'import pytest\n'), ((221, 1, 230, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(222, 4, 222, 36): '"""section_dict, expected_message"""', (223, 4, 229, 5): "[({'tags': {'key': 'value', 'key2': 'value2'}}, None), ({'tags': {'key':\n    'value', 'Version': 'value2'}}, 'Version.*reserved')]"}, {}), "('section_dict, expected_message', [({'tags': {'key':\n    'value', 'key2': 'value2'}}, None), ({'tags': {'key': 'value',\n    'Version': 'value2'}}, 'Version.*reserved')])", False, 'import pytest\n'), ((278, 1, 329, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(279, 4, 279, 50): '"""region, base_os, scheduler, expected_message"""', (280, 4, 328, 5): '[(\'ap-northeast-3\', \'alinux2\', \'awsbatch\',\n    "Region \'ap-northeast-3\' is not yet officially supported by ParallelCluster"\n    ), (\'us-gov-east-1\', \'alinux2\', \'awsbatch\', None), (\'us-gov-west-1\',\n    \'alinux2\', \'awsbatch\', None), (\'eu-west-1\', \'alinux2\', \'awsbatch\', None\n    ), (\'us-east-1\', \'alinux2\', \'awsbatch\', None), (\'eu-north-1\', \'alinux2\',\n    \'awsbatch\', None), (\'cn-north-1\', \'alinux2\', \'awsbatch\', None), (\n    \'cn-northwest-1\', \'alinux2\', \'awsbatch\', None), (\'cn-northwest-1\',\n    \'alinux2\', \'sge\', None), (\'us-gov-east-1\', \'alinux2\', \'sge\', None), (\n    \'cn-northwest-1\', \'alinux2\', \'slurm\', None), (\'us-gov-east-1\',\n    \'alinux2\', \'slurm\', None), (\'cn-northwest-1\', \'alinux2\', \'torque\', None\n    ), (\'us-gov-east-1\', \'alinux2\', \'torque\', None), (\'ap-northeast-3\',\n    \'alinux2\', \'sge\',\n    "Region \'ap-northeast-3\' is not yet officially supported by ParallelCluster"\n    ), (\'eu-west-1\', \'centos7\', \'awsbatch\',\n    \'scheduler supports the following Operating Systems\'), (\'eu-west-1\',\n    \'centos8\', \'awsbatch\',\n    \'scheduler supports the following Operating Systems\'), (\'eu-west-1\',\n    \'ubuntu1804\', \'awsbatch\',\n    \'scheduler supports the following Operating Systems\'), (\'eu-west-1\',\n    \'alinux2\', \'awsbatch\', None), (\'eu-west-1\', \'centos7\', \'sge\', None), (\n    \'eu-west-1\', \'centos8\', \'sge\', None), (\'eu-west-1\', \'ubuntu1804\', \'sge\',\n    None), (\'eu-west-1\', \'alinux2\', \'sge\', None), (\'eu-west-1\', \'centos7\',\n    \'slurm\', None), (\'eu-west-1\', \'centos8\', \'slurm\', None), (\'eu-west-1\',\n    \'ubuntu1804\', \'slurm\', None), (\'eu-west-1\', \'alinux2\', \'slurm\', None),\n    (\'eu-west-1\', \'centos7\', \'torque\', None), (\'eu-west-1\', \'centos8\',\n    \'torque\', None), (\'eu-west-1\', \'ubuntu1804\', \'torque\', None), (\n    \'eu-west-1\', \'alinux2\', \'torque\', None)]'}, {}), '(\'region, base_os, scheduler, expected_message\', [(\n    \'ap-northeast-3\', \'alinux2\', \'awsbatch\',\n    "Region \'ap-northeast-3\' is not yet officially supported by ParallelCluster"\n    ), (\'us-gov-east-1\', \'alinux2\', \'awsbatch\', None), (\'us-gov-west-1\',\n    \'alinux2\', \'awsbatch\', None), (\'eu-west-1\', \'alinux2\', \'awsbatch\', None\n    ), (\'us-east-1\', \'alinux2\', \'awsbatch\', None), (\'eu-north-1\', \'alinux2\',\n    \'awsbatch\', None), (\'cn-north-1\', \'alinux2\', \'awsbatch\', None), (\n    \'cn-northwest-1\', \'alinux2\', \'awsbatch\', None), (\'cn-northwest-1\',\n    \'alinux2\', \'sge\', None), (\'us-gov-east-1\', \'alinux2\', \'sge\', None), (\n    \'cn-northwest-1\', \'alinux2\', \'slurm\', None), (\'us-gov-east-1\',\n    \'alinux2\', \'slurm\', None), (\'cn-northwest-1\', \'alinux2\', \'torque\', None\n    ), (\'us-gov-east-1\', \'alinux2\', \'torque\', None), (\'ap-northeast-3\',\n    \'alinux2\', \'sge\',\n    "Region \'ap-northeast-3\' is not yet officially supported by ParallelCluster"\n    ), (\'eu-west-1\', \'centos7\', \'awsbatch\',\n    \'scheduler supports the following Operating Systems\'), (\'eu-west-1\',\n    \'centos8\', \'awsbatch\',\n    \'scheduler supports the following Operating Systems\'), (\'eu-west-1\',\n    \'ubuntu1804\', \'awsbatch\',\n    \'scheduler supports the following Operating Systems\'), (\'eu-west-1\',\n    \'alinux2\', \'awsbatch\', None), (\'eu-west-1\', \'centos7\', \'sge\', None), (\n    \'eu-west-1\', \'centos8\', \'sge\', None), (\'eu-west-1\', \'ubuntu1804\', \'sge\',\n    None), (\'eu-west-1\', \'alinux2\', \'sge\', None), (\'eu-west-1\', \'centos7\',\n    \'slurm\', None), (\'eu-west-1\', \'centos8\', \'slurm\', None), (\'eu-west-1\',\n    \'ubuntu1804\', \'slurm\', None), (\'eu-west-1\', \'alinux2\', \'slurm\', None),\n    (\'eu-west-1\', \'centos7\', \'torque\', None), (\'eu-west-1\', \'centos8\',\n    \'torque\', None), (\'eu-west-1\', \'ubuntu1804\', \'torque\', None), (\n    \'eu-west-1\', \'alinux2\', \'torque\', None)])', False, 'import pytest\n'), ((424, 1, 473, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(425, 4, 425, 61): '"""config, num_calls, error_code, bucket, expected_message"""', (426, 4, 472, 5): '[({\'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'s3://test/test1/test2\',\n    \'export_path\': \'s3://test/test1/test2\', \'auto_import_policy\': \'NEW\'}}, \n    2, None, {\'Bucket\': \'test\'},\n    \'AutoImport is not supported for cross-region buckets.\'), ({\n    \'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'s3://test/test1/test2\',\n    \'export_path\': \'s3://test/test1/test2\', \'auto_import_policy\': \'NEW\'}}, \n    2, \'NoSuchBucket\', {\'Bucket\': \'test\'},\n    "The S3 bucket \'test\' does not appear to exist."), ({\'cluster default\':\n    {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\'storage_capacity\': 1200,\n    \'import_path\': \'s3://test/test1/test2\', \'export_path\':\n    \'s3://test/test1/test2\', \'auto_import_policy\': \'NEW\'}}, 2,\n    \'AccessDenied\', {\'Bucket\': \'test\'},\n    \'You do not have access to the S3 bucket\')]'}, {}), '(\n    \'config, num_calls, error_code, bucket, expected_message\', [({\n    \'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'s3://test/test1/test2\',\n    \'export_path\': \'s3://test/test1/test2\', \'auto_import_policy\': \'NEW\'}}, \n    2, None, {\'Bucket\': \'test\'},\n    \'AutoImport is not supported for cross-region buckets.\'), ({\n    \'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'s3://test/test1/test2\',\n    \'export_path\': \'s3://test/test1/test2\', \'auto_import_policy\': \'NEW\'}}, \n    2, \'NoSuchBucket\', {\'Bucket\': \'test\'},\n    "The S3 bucket \'test\' does not appear to exist."), ({\'cluster default\':\n    {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\'storage_capacity\': 1200,\n    \'import_path\': \'s3://test/test1/test2\', \'export_path\':\n    \'s3://test/test1/test2\', \'auto_import_policy\': \'NEW\'}}, 2,\n    \'AccessDenied\', {\'Bucket\': \'test\'},\n    \'You do not have access to the S3 bucket\')])', False, 'import pytest\n'), ((519, 1, 549, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(520, 4, 520, 49): '"""config, num_calls, bucket, expected_message"""', (521, 4, 548, 5): '[({\'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'s3://test/test1/test2\',\n    \'export_path\': \'s3://test/test1/test2\'}}, 2, {\'Bucket\': \'test\'}, None),\n    ({\'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'http://test/test.json\',\n    \'export_path\': \'s3://test/test1/test2\'}}, 1, {\'Bucket\': \'test\'},\n    "The value \'http://test/test.json\' used for the parameter \'import_path\' is not a valid S3 URI."\n    )]'}, {}), '(\'config, num_calls, bucket, expected_message\', [({\n    \'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'s3://test/test1/test2\',\n    \'export_path\': \'s3://test/test1/test2\'}}, 2, {\'Bucket\': \'test\'}, None),\n    ({\'cluster default\': {\'fsx_settings\': \'fsx\'}, \'fsx fsx\': {\n    \'storage_capacity\': 1200, \'import_path\': \'http://test/test.json\',\n    \'export_path\': \'s3://test/test1/test2\'}}, 1, {\'Bucket\': \'test\'},\n    "The value \'http://test/test.json\' used for the parameter \'import_path\' is not a valid S3 URI."\n    )])', False, 'import pytest\n'), ((556, 1, 602, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(557, 4, 557, 64): '"""bucket, region, error_code, expected_message, client_error"""', (558, 4, 601, 5): '[(\'bucket\', \'us-east-1\', None, None, False), (\'bucket\', \'us-west-1\', None,\n    None, False), (\'bucket\', \'eu-west-1\', None,\n    \'cluster_resource_bucket must be in the same region of the cluster.\', \n    False), (\'not_existed_bucket\', \'af-south-1\', \'NoSuchBucket\',\n    "The S3 bucket \'not_existed_bucket\' does not appear to exist", True), (\n    \'access_denied_bucket\', \'af-south-1\', \'AccessDenied\',\n    "You do not have access to the S3 bucket \'access_denied_bucket\'", True),\n    (\'unexpected_error_bucket\', \'af-south-1\', None,\n    \'Unexpected error for S3 bucket\', True)]'}, {}), '(\n    \'bucket, region, error_code, expected_message, client_error\', [(\n    \'bucket\', \'us-east-1\', None, None, False), (\'bucket\', \'us-west-1\', None,\n    None, False), (\'bucket\', \'eu-west-1\', None,\n    \'cluster_resource_bucket must be in the same region of the cluster.\', \n    False), (\'not_existed_bucket\', \'af-south-1\', \'NoSuchBucket\',\n    "The S3 bucket \'not_existed_bucket\' does not appear to exist", True), (\n    \'access_denied_bucket\', \'af-south-1\', \'AccessDenied\',\n    "You do not have access to the S3 bucket \'access_denied_bucket\'", True),\n    (\'unexpected_error_bucket\', \'af-south-1\', None,\n    \'Unexpected error for S3 bucket\', True)])', False, 'import pytest\n'), ((783, 1, 795, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(784, 4, 784, 36): '"""section_dict, expected_message"""', (785, 4, 794, 5): '[({\'throughput_mode\': \'bursting\', \'provisioned_throughput\': 1024},\n    "When specifying \'provisioned_throughput\', the \'throughput_mode\' must be set to \'provisioned\'"\n    ), ({\'throughput_mode\': \'provisioned\', \'provisioned_throughput\': 1024},\n    None), ({\'shared_dir\': \'NONE\'},\n    \'NONE cannot be used as a shared directory\'), ({\'shared_dir\': \'/NONE\'},\n    \'/NONE cannot be used as a shared directory\'), ({\'shared_dir\': \'/efs\'},\n    None)]'}, {}), '(\'section_dict, expected_message\', [({\n    \'throughput_mode\': \'bursting\', \'provisioned_throughput\': 1024},\n    "When specifying \'provisioned_throughput\', the \'throughput_mode\' must be set to \'provisioned\'"\n    ), ({\'throughput_mode\': \'provisioned\', \'provisioned_throughput\': 1024},\n    None), ({\'shared_dir\': \'NONE\'},\n    \'NONE cannot be used as a shared directory\'), ({\'shared_dir\': \'/NONE\'},\n    \'/NONE cannot be used as a shared directory\'), ({\'shared_dir\': \'/efs\'},\n    None)])', False, 'import pytest\n'), ((801, 1, 841, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(802, 4, 802, 36): '"""section_dict, expected_message"""', (803, 4, 840, 5): "[({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 120}, None), ({\n    'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 64001},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 1001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 120}, None), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 256001},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 20001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 3000}, None), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 2900},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 16001},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 10001},\n    'IOPS to volume size ratio of .* is too high')]"}, {}), "('section_dict, expected_message', [({'volume_type':\n    'io1', 'volume_size': 20, 'volume_iops': 120}, None), ({'volume_type':\n    'io1', 'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 64001},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 1001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 120}, None), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 256001},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 20001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 3000}, None), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 2900},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 16001},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 10001},\n    'IOPS to volume size ratio of .* is too high')])", False, 'import pytest\n'), ((847, 1, 856, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(848, 4, 848, 34): '"""kms_key_id, expected_message"""', (849, 4, 855, 5): '[(\'9e8a129be-0e46-459d-865b-3a5bf974a22k\', None), (\n    \'9e7a129be-0e46-459d-865b-3a5bf974a22k\',\n    "Key \'arn:aws:kms:us-east-1:12345678:key/9e7a129be-0e46-459d-865b-3a5bf974a22k\' does not exist"\n    )]'}, {}), '(\'kms_key_id, expected_message\', [(\n    \'9e8a129be-0e46-459d-865b-3a5bf974a22k\', None), (\n    \'9e7a129be-0e46-459d-865b-3a5bf974a22k\',\n    "Key \'arn:aws:kms:us-east-1:12345678:key/9e7a129be-0e46-459d-865b-3a5bf974a22k\' does not exist"\n    )])', False, 'import pytest\n'), ((1164, 1, 1237, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1165, 4, 1165, 52): '"""section_dict, expected_error, expected_warning"""', (1166, 4, 1236, 5): '[({\'storage_capacity\': 1, \'deployment_type\': \'SCRATCH_1\'},\n    \'Capacity for FSx SCRATCH_1 filesystem is 1,200 GB, 2,400 GB or increments of 3,600 GB\'\n    , None), ({\'storage_capacity\': 1200, \'deployment_type\': \'SCRATCH_1\'},\n    None, None), ({\'storage_capacity\': 2400, \'deployment_type\': \'SCRATCH_1\'\n    }, None, None), ({\'storage_capacity\': 3600, \'deployment_type\':\n    \'SCRATCH_1\'}, None, None), ({\'storage_capacity\': 3600,\n    \'deployment_type\': \'SCRATCH_2\'},\n    \'Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB\'\n    , None), ({\'storage_capacity\': 3600, \'deployment_type\': \'PERSISTENT_1\',\n    \'per_unit_storage_throughput\': 50},\n    \'Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB\'\n    , None), ({\'storage_capacity\': 3601, \'deployment_type\': \'PERSISTENT_1\',\n    \'per_unit_storage_throughput\': 50},\n    \'Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB\'\n    , None), ({\'storage_capacity\': 7200}, None, None), ({\'deployment_type\':\n    \'SCRATCH_1\'},\n    "When specifying \'fsx\' section, the \'storage_capacity\' option must be specified"\n    , None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 1801, \'per_unit_storage_throughput\': 40},\n    \'Capacity for FSx PERSISTENT HDD 40 MB/s/TiB file systems is increments of 1,800 GiB\'\n    , None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 6001, \'per_unit_storage_throughput\': 12},\n    \'Capacity for FSx PERSISTENT HDD 12 MB/s/TiB file systems is increments of 6,000 GiB\'\n    , None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 1800, \'per_unit_storage_throughput\': 40}, None,\n    None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 6000, \'per_unit_storage_throughput\': 12}, None, None)]'}, {}), '(\'section_dict, expected_error, expected_warning\', [\n    ({\'storage_capacity\': 1, \'deployment_type\': \'SCRATCH_1\'},\n    \'Capacity for FSx SCRATCH_1 filesystem is 1,200 GB, 2,400 GB or increments of 3,600 GB\'\n    , None), ({\'storage_capacity\': 1200, \'deployment_type\': \'SCRATCH_1\'},\n    None, None), ({\'storage_capacity\': 2400, \'deployment_type\': \'SCRATCH_1\'\n    }, None, None), ({\'storage_capacity\': 3600, \'deployment_type\':\n    \'SCRATCH_1\'}, None, None), ({\'storage_capacity\': 3600,\n    \'deployment_type\': \'SCRATCH_2\'},\n    \'Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB\'\n    , None), ({\'storage_capacity\': 3600, \'deployment_type\': \'PERSISTENT_1\',\n    \'per_unit_storage_throughput\': 50},\n    \'Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB\'\n    , None), ({\'storage_capacity\': 3601, \'deployment_type\': \'PERSISTENT_1\',\n    \'per_unit_storage_throughput\': 50},\n    \'Capacity for FSx SCRATCH_2 and PERSISTENT_1 filesystems is 1,200 GB or increments of 2,400 GB\'\n    , None), ({\'storage_capacity\': 7200}, None, None), ({\'deployment_type\':\n    \'SCRATCH_1\'},\n    "When specifying \'fsx\' section, the \'storage_capacity\' option must be specified"\n    , None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 1801, \'per_unit_storage_throughput\': 40},\n    \'Capacity for FSx PERSISTENT HDD 40 MB/s/TiB file systems is increments of 1,800 GiB\'\n    , None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 6001, \'per_unit_storage_throughput\': 12},\n    \'Capacity for FSx PERSISTENT HDD 12 MB/s/TiB file systems is increments of 6,000 GiB\'\n    , None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 1800, \'per_unit_storage_throughput\': 40}, None,\n    None), ({\'storage_type\': \'HDD\', \'deployment_type\': \'PERSISTENT_1\',\n    \'storage_capacity\': 6000, \'per_unit_storage_throughput\': 12}, None, None)])', False, 'import pytest\n'), ((1264, 1, 1307, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1265, 4, 1265, 67): '"""fsx_vpc, ip_permissions, network_interfaces, expected_message"""', (1266, 4, 1306, 5): '[(\'vpc-06e4ab6c6cEXAMPLE\', [{\'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\n    \'UserId\': \'123456789012\', \'GroupId\': \'sg-12345678\'}]}], [\n    \'eni-09b9460295ddd4e5f\', \'eni-001b3cef7c78b45c4\'], None), (\n    \'vpc-06e4ab6c6cEXAMPLE\', [{\'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\n    \'UserId\': \'123456789012\', \'GroupId\': \'sg-12345678\'}]}], [\n    \'eni-09b9460295ddd4e5f\'], None), (\'vpc-06e4ab6c6cEXAMPLE\', [{\n    \'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\'UserId\': \'123456789012\',\n    \'GroupId\': \'sg-12345678\'}]}], [],\n    "doesn\'t have Elastic Network Interfaces attached"), (\n    \'vpc-06e4ab6c6ccWRONG\', [{\'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\n    \'UserId\': \'123456789012\', \'GroupId\': \'sg-12345678\'}]}], [\n    \'eni-09b9460295ddd4e5f\'],\n    \'only support using FSx file system that is in the same VPC as the stack\'\n    ), (\'vpc-06e4ab6c6cWRONG\', [{\'PrefixListIds\': [], \'FromPort\': 22,\n    \'IpRanges\': [{\'CidrIp\': \'203.0.113.0/24\'}], \'ToPort\': 22, \'IpProtocol\':\n    \'tcp\', \'UserIdGroupPairs\': []}], [\'eni-09b9460295ddd4e5f\'],\n    \'does not satisfy mounting requirement\')]'}, {}), '(\n    \'fsx_vpc, ip_permissions, network_interfaces, expected_message\', [(\n    \'vpc-06e4ab6c6cEXAMPLE\', [{\'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\n    \'UserId\': \'123456789012\', \'GroupId\': \'sg-12345678\'}]}], [\n    \'eni-09b9460295ddd4e5f\', \'eni-001b3cef7c78b45c4\'], None), (\n    \'vpc-06e4ab6c6cEXAMPLE\', [{\'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\n    \'UserId\': \'123456789012\', \'GroupId\': \'sg-12345678\'}]}], [\n    \'eni-09b9460295ddd4e5f\'], None), (\'vpc-06e4ab6c6cEXAMPLE\', [{\n    \'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\'UserId\': \'123456789012\',\n    \'GroupId\': \'sg-12345678\'}]}], [],\n    "doesn\'t have Elastic Network Interfaces attached"), (\n    \'vpc-06e4ab6c6ccWRONG\', [{\'IpProtocol\': \'-1\', \'UserIdGroupPairs\': [{\n    \'UserId\': \'123456789012\', \'GroupId\': \'sg-12345678\'}]}], [\n    \'eni-09b9460295ddd4e5f\'],\n    \'only support using FSx file system that is in the same VPC as the stack\'\n    ), (\'vpc-06e4ab6c6cWRONG\', [{\'PrefixListIds\': [], \'FromPort\': 22,\n    \'IpRanges\': [{\'CidrIp\': \'203.0.113.0/24\'}], \'ToPort\': 22, \'IpProtocol\':\n    \'tcp\', \'UserIdGroupPairs\': []}], [\'eni-09b9460295ddd4e5f\'],\n    \'does not satisfy mounting requirement\')])', False, 'import pytest\n'), ((1461, 1, 1471, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1462, 4, 1462, 36): '"""section_dict, expected_message"""', (1463, 4, 1470, 5): '[({\'enable_intel_hpc_platform\': \'true\', \'base_os\': \'centos7\'}, None), ({\n    \'enable_intel_hpc_platform\': \'true\', \'base_os\': \'centos8\'}, None), ({\n    \'enable_intel_hpc_platform\': \'true\', \'base_os\': \'alinux2\'},\n    "it is required to set the \'base_os\'"), ({\'enable_intel_hpc_platform\':\n    \'true\', \'base_os\': \'ubuntu1804\'}, "it is required to set the \'base_os\'"\n    ), ({\'enable_intel_hpc_platform\': \'false\', \'base_os\': \'alinux2\'}, None)]'}, {}), '(\'section_dict, expected_message\', [({\n    \'enable_intel_hpc_platform\': \'true\', \'base_os\': \'centos7\'}, None), ({\n    \'enable_intel_hpc_platform\': \'true\', \'base_os\': \'centos8\'}, None), ({\n    \'enable_intel_hpc_platform\': \'true\', \'base_os\': \'alinux2\'},\n    "it is required to set the \'base_os\'"), ({\'enable_intel_hpc_platform\':\n    \'true\', \'base_os\': \'ubuntu1804\'}, "it is required to set the \'base_os\'"\n    ), ({\'enable_intel_hpc_platform\': \'false\', \'base_os\': \'alinux2\'}, None)])', False, 'import pytest\n'), ((1477, 1, 1498, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1478, 4, 1478, 36): '"""section_dict, expected_message"""', (1479, 4, 1497, 5): '[({\'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": "vcpus"}}\'},\n    \'cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true\'\n    ), ({\'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": "cores"}}\'},\n    \'cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true\'\n    ), ({\'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": 3}}\'},\n    \'cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true\'\n    ), ({\'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"other_param": "fake_value"}}\'}, None), ({\n    \'disable_hyperthreading\': True}, None), ({\'disable_hyperthreading\': \n    False, \'extra_json\': \'{"cluster": {"cfn_scheduler_slots": "vcpus"}}\'},\n    None), ({\'disable_hyperthreading\': False, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": "cores"}}\'}, None), ({\n    \'disable_hyperthreading\': False, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": 3}}\'}, None)]'}, {}), '(\'section_dict, expected_message\', [({\n    \'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": "vcpus"}}\'},\n    \'cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true\'\n    ), ({\'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": "cores"}}\'},\n    \'cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true\'\n    ), ({\'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": 3}}\'},\n    \'cfn_scheduler_slots cannot be set in addition to disable_hyperthreading = true\'\n    ), ({\'disable_hyperthreading\': True, \'extra_json\':\n    \'{"cluster": {"other_param": "fake_value"}}\'}, None), ({\n    \'disable_hyperthreading\': True}, None), ({\'disable_hyperthreading\': \n    False, \'extra_json\': \'{"cluster": {"cfn_scheduler_slots": "vcpus"}}\'},\n    None), ({\'disable_hyperthreading\': False, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": "cores"}}\'}, None), ({\n    \'disable_hyperthreading\': False, \'extra_json\':\n    \'{"cluster": {"cfn_scheduler_slots": 3}}\'}, None)])', False, 'import pytest\n'), ((1504, 1, 1533, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1505, 4, 1505, 44): '"""section_dict, bucket, expected_message"""', (1506, 4, 1532, 5): "[({'imported_file_chunk_size': 0, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, None,\n    'has a minimum size of 1 MiB, and max size of 512,000 MiB'), ({\n    'imported_file_chunk_size': 1, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, {'Bucket': 'test-import'}, None), ({\n    'imported_file_chunk_size': 10, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, {'Bucket': 'test-import'}, None), ({\n    'imported_file_chunk_size': 512000, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, {'Bucket': 'test-import'}, None), ({\n    'imported_file_chunk_size': 512001, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, None,\n    'has a minimum size of 1 MiB, and max size of 512,000 MiB')]"}, {}), "('section_dict, bucket, expected_message', [({\n    'imported_file_chunk_size': 0, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, None,\n    'has a minimum size of 1 MiB, and max size of 512,000 MiB'), ({\n    'imported_file_chunk_size': 1, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, {'Bucket': 'test-import'}, None), ({\n    'imported_file_chunk_size': 10, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, {'Bucket': 'test-import'}, None), ({\n    'imported_file_chunk_size': 512000, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, {'Bucket': 'test-import'}, None), ({\n    'imported_file_chunk_size': 512001, 'import_path': 's3://test-import',\n    'storage_capacity': 1200}, None,\n    'has a minimum size of 1 MiB, and max size of 512,000 MiB')])", False, 'import pytest\n'), ((1633, 1, 1646, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1634, 4, 1634, 34): '"""cluster_dict, expected_error"""', (1635, 4, 1645, 5): '[({\'enable_efa_gdr\': \'compute\'},\n    "The parameter \'enable_efa_gdr\' can be used only in combination with \'enable_efa\'"\n    ), ({\'enable_efa\': \'compute\', \'enable_efa_gdr\': \'compute\'}, None), ({\n    \'enable_efa\': \'compute\'}, None)]'}, {}), '(\'cluster_dict, expected_error\', [({\'enable_efa_gdr\':\n    \'compute\'},\n    "The parameter \'enable_efa_gdr\' can be used only in combination with \'enable_efa\'"\n    ), ({\'enable_efa\': \'compute\', \'enable_efa_gdr\': \'compute\'}, None), ({\n    \'enable_efa\': \'compute\'}, None)])', False, 'import pytest\n'), ((1665, 1, 1694, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1666, 4, 1666, 61): '"""ip_permissions, ip_permissions_egress, expected_message"""', (1667, 4, 1693, 5): "[([], [], 'must allow all traffic in and out from itself'), ([{'IpProtocol':\n    '-1', 'UserIdGroupPairs': [{'UserId': '123456789012', 'GroupId':\n    'sg-12345678'}]}], [], 'must allow all traffic in and out from itself'),\n    ([{'IpProtocol': '-1', 'UserIdGroupPairs': [{'UserId': '123456789012',\n    'GroupId': 'sg-12345678'}]}], [{'IpProtocol': '-1', 'UserIdGroupPairs':\n    [{'UserId': '123456789012', 'GroupId': 'sg-12345678'}]}], None), ([{\n    'PrefixListIds': [], 'FromPort': 22, 'IpRanges': [{'CidrIp':\n    '203.0.113.0/24'}], 'ToPort': 22, 'IpProtocol': 'tcp',\n    'UserIdGroupPairs': []}], [],\n    'must allow all traffic in and out from itself')]"}, {}), "(\n    'ip_permissions, ip_permissions_egress, expected_message', [([], [],\n    'must allow all traffic in and out from itself'), ([{'IpProtocol': '-1',\n    'UserIdGroupPairs': [{'UserId': '123456789012', 'GroupId':\n    'sg-12345678'}]}], [], 'must allow all traffic in and out from itself'),\n    ([{'IpProtocol': '-1', 'UserIdGroupPairs': [{'UserId': '123456789012',\n    'GroupId': 'sg-12345678'}]}], [{'IpProtocol': '-1', 'UserIdGroupPairs':\n    [{'UserId': '123456789012', 'GroupId': 'sg-12345678'}]}], None), ([{\n    'PrefixListIds': [], 'FromPort': 22, 'IpRanges': [{'CidrIp':\n    '203.0.113.0/24'}], 'ToPort': 22, 'IpProtocol': 'tcp',\n    'UserIdGroupPairs': []}], [],\n    'must allow all traffic in and out from itself')])", False, 'import pytest\n'), ((1743, 1, 1774, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1744, 4, 1744, 62): '"""cluster_section_dict, ebs_section_dict, expected_message"""', (1745, 4, 1773, 5): '[({\'ebs_settings\': \'vol1, vol2, vol3, vol4, vol5, vol6\'}, {\'vol1\': {\n    \'shared_dir\': \'/vol1\'}, \'vol2\': {\'shared_dir\': \'/vol2\'}, \'vol3\': {\n    \'shared_dir\': \'/vol3\'}, \'vol4\': {\'shared_dir\': \'/vol4\'}, \'vol5\': {\n    \'shared_dir\': \'/vol5\'}, \'vol6\': {\'shared_dir\': \'/vol6\'}},\n    "Invalid number of \'ebs\' sections specified. Max 5 expected."), ({\n    \'ebs_settings\': \'vol1, vol2 \'}, {\'vol1\': {\'shared_dir\': \'vol1\'}, \'vol2\':\n    {\'volume_type\': \'io1\'}},\n    \'When using more than 1 EBS volume, shared_dir is required under each EBS section\'\n    ), ({\'ebs_settings\': \'vol1,vol2\'}, {\'vol1\': {\'shared_dir\': \'/NONE\'},\n    \'vol2\': {\'shared_dir\': \'vol2\'}},\n    \'/NONE cannot be used as a shared directory\'), ({\'ebs_settings\':\n    \'vol1, vol2 \'}, {\'vol1\': {\'shared_dir\': \'/vol1\'}, \'vol2\': {\'shared_dir\':\n    \'NONE\'}}, \'NONE cannot be used as a shared directory\')]'}, {}), '(\n    \'cluster_section_dict, ebs_section_dict, expected_message\', [({\n    \'ebs_settings\': \'vol1, vol2, vol3, vol4, vol5, vol6\'}, {\'vol1\': {\n    \'shared_dir\': \'/vol1\'}, \'vol2\': {\'shared_dir\': \'/vol2\'}, \'vol3\': {\n    \'shared_dir\': \'/vol3\'}, \'vol4\': {\'shared_dir\': \'/vol4\'}, \'vol5\': {\n    \'shared_dir\': \'/vol5\'}, \'vol6\': {\'shared_dir\': \'/vol6\'}},\n    "Invalid number of \'ebs\' sections specified. Max 5 expected."), ({\n    \'ebs_settings\': \'vol1, vol2 \'}, {\'vol1\': {\'shared_dir\': \'vol1\'}, \'vol2\':\n    {\'volume_type\': \'io1\'}},\n    \'When using more than 1 EBS volume, shared_dir is required under each EBS section\'\n    ), ({\'ebs_settings\': \'vol1,vol2\'}, {\'vol1\': {\'shared_dir\': \'/NONE\'},\n    \'vol2\': {\'shared_dir\': \'vol2\'}},\n    \'/NONE cannot be used as a shared directory\'), ({\'ebs_settings\':\n    \'vol1, vol2 \'}, {\'vol1\': {\'shared_dir\': \'/vol1\'}, \'vol2\': {\'shared_dir\':\n    \'NONE\'}}, \'NONE cannot be used as a shared directory\')])', False, 'import pytest\n'), ((1783, 1, 1790, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1784, 4, 1784, 36): '"""section_dict, expected_message"""', (1785, 4, 1789, 5): "[({'shared_dir': 'NONE'}, 'NONE cannot be used as a shared directory'), ({\n    'shared_dir': '/NONE'}, '/NONE cannot be used as a shared directory'),\n    ({'shared_dir': '/NONEshared'}, None)]"}, {}), "('section_dict, expected_message', [({'shared_dir':\n    'NONE'}, 'NONE cannot be used as a shared directory'), ({'shared_dir':\n    '/NONE'}, '/NONE cannot be used as a shared directory'), ({'shared_dir':\n    '/NONEshared'}, None)])", False, 'import pytest\n'), ((1796, 1, 1813, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1797, 4, 1797, 75): '"""base_os, instance_type, access_from, expected_error, expected_warning"""', (1798, 4, 1812, 5): "[('centos7', 't2.medium', None, None, None), ('centos8', 't2.medium', None,\n    None, None), ('ubuntu1804', 't2.medium', None, None, None), (\n    'ubuntu1804', 't2.medium', '1.2.3.4/32', None, None), ('centos7',\n    't2.medium', '0.0.0.0/0', None, None), ('centos8', 't2.medium',\n    '0.0.0.0/0', None, None), ('alinux2', 't2.medium', None, None, None), (\n    'alinux2', 't2.nano', None, None,\n    'is recommended to use an instance type with at least'), ('alinux2',\n    't2.micro', None, None,\n    'is recommended to use an instance type with at least'), ('ubuntu1804',\n    'm6g.xlarge', None, None, None), ('alinux2', 'm6g.xlarge', None, None,\n    None), ('centos7', 'm6g.xlarge', None, None, None), ('centos8',\n    'm6g.xlarge', None, None, None)]"}, {}), "(\n    'base_os, instance_type, access_from, expected_error, expected_warning',\n    [('centos7', 't2.medium', None, None, None), ('centos8', 't2.medium',\n    None, None, None), ('ubuntu1804', 't2.medium', None, None, None), (\n    'ubuntu1804', 't2.medium', '1.2.3.4/32', None, None), ('centos7',\n    't2.medium', '0.0.0.0/0', None, None), ('centos8', 't2.medium',\n    '0.0.0.0/0', None, None), ('alinux2', 't2.medium', None, None, None), (\n    'alinux2', 't2.nano', None, None,\n    'is recommended to use an instance type with at least'), ('alinux2',\n    't2.micro', None, None,\n    'is recommended to use an instance type with at least'), ('ubuntu1804',\n    'm6g.xlarge', None, None, None), ('alinux2', 'm6g.xlarge', None, None,\n    None), ('centos7', 'm6g.xlarge', None, None, None), ('centos8',\n    'm6g.xlarge', None, None, None)])", False, 'import pytest\n'), ((1872, 1, 1884, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1873, 4, 1873, 36): '"""section_dict, expected_message"""', (1874, 4, 1883, 5): "[({'initial_queue_size': '0', 'maintain_initial_size': True},\n    'maintain_initial_size cannot be set to true if initial_queue_size is 0'\n    ), ({'scheduler': 'awsbatch', 'maintain_initial_size': True},\n    'maintain_initial_size is not supported when using awsbatch as scheduler')]"}, {}), "('section_dict, expected_message', [({\n    'initial_queue_size': '0', 'maintain_initial_size': True},\n    'maintain_initial_size cannot be set to true if initial_queue_size is 0'\n    ), ({'scheduler': 'awsbatch', 'maintain_initial_size': True},\n    'maintain_initial_size is not supported when using awsbatch as scheduler')]\n    )", False, 'import pytest\n'), ((1890, 1, 1945, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1891, 4, 1891, 44): '"""cluster_section_dict, expected_message"""', (1892, 4, 1944, 5): '[({\'scheduler\': \'slurm\'}, None), ({\'scheduler\': \'slurm\', \'queue_settings\':\n    \'queue1\'}, None), ({\'scheduler\': \'slurm\', \'queue_settings\':\n    \'queue1,queue2,queue3,queue4,queue5\'}, None), ({\'scheduler\': \'slurm\',\n    \'queue_settings\': \'queue1, queue2\'}, None), ({\'scheduler\': \'slurm\',\n    \'queue_settings\': \'queue1,queue2,queue3,queue4,queue5,queue6\'},\n    "Invalid number of \'queue\' sections specified. Max 5 expected."), ({\n    \'scheduler\': \'slurm\', \'queue_settings\': \'queue_1\'},\n    "Invalid queue name \'queue_1\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'default\'},\n    "Invalid queue name \'default\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'queue1, default\'},\n    "Invalid queue name \'.*\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'QUEUE\'},\n    "Invalid queue name \'QUEUE\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'aQUEUEa\'},\n    "Invalid queue name \'aQUEUEa\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'my-default-queue\'}, None)]'}, {}), '(\'cluster_section_dict, expected_message\', [({\n    \'scheduler\': \'slurm\'}, None), ({\'scheduler\': \'slurm\', \'queue_settings\':\n    \'queue1\'}, None), ({\'scheduler\': \'slurm\', \'queue_settings\':\n    \'queue1,queue2,queue3,queue4,queue5\'}, None), ({\'scheduler\': \'slurm\',\n    \'queue_settings\': \'queue1, queue2\'}, None), ({\'scheduler\': \'slurm\',\n    \'queue_settings\': \'queue1,queue2,queue3,queue4,queue5,queue6\'},\n    "Invalid number of \'queue\' sections specified. Max 5 expected."), ({\n    \'scheduler\': \'slurm\', \'queue_settings\': \'queue_1\'},\n    "Invalid queue name \'queue_1\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'default\'},\n    "Invalid queue name \'default\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'queue1, default\'},\n    "Invalid queue name \'.*\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'QUEUE\'},\n    "Invalid queue name \'QUEUE\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'aQUEUEa\'},\n    "Invalid queue name \'aQUEUEa\'. Queue section names can be at most 30 chars long, must begin with a letter and only contain lowercase letters, digits and hyphens. It is forbidden to use \'default\' as a queue section name."\n    ), ({\'scheduler\': \'slurm\', \'queue_settings\': \'my-default-queue\'}, None)])', False, 'import pytest\n'), ((1960, 1, 2074, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(1961, 4, 1961, 82): '"""cluster_dict, queue_dict, expected_error_messages, expected_warning_messages"""', (1962, 4, 2073, 5): '[({\'queue_settings\': \'default\'}, {\'compute_resource_settings\': \'cr1,cr2\',\n    \'enable_efa\': True, \'disable_hyperthreading\': True}, [\n    "Duplicate instance type \'t2.micro\' found in queue \'default\'. Compute resources in the same queue must use different instance types"\n    ], [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr1 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr3,cr4\', \'enable_efa\': True, \'disable_hyperthreading\': True}, [\n    "Duplicate instance type \'c4.xlarge\' found in queue \'default\'. Compute resources in the same queue must use different instance types"\n    ], [\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr3 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr1,cr3\', \'enable_efa\': True, \'disable_hyperthreading\': True}, None, [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr1 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr3 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr2,cr4\', \'enable_efa\': True, \'disable_hyperthreading\': True}, None, [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr2,cr4\', \'enable_efa\': True, \'enable_efa_gdr\': True}, None, [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA."\n    ,\n    "EFA GDR was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA GDR."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA."\n    ,\n    "EFA GDR was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA GDR."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'efa_instance\', \'enable_efa_gdr\': True}, [\n    "The parameter \'enable_efa_gdr\' can be used only in combination with \'enable_efa\'"\n    ], None), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr1\'}, None, None), ({\'queue_settings\': \'default\', \'enable_efa\':\n    \'compute\', \'disable_hyperthreading\': True}, {\n    \'compute_resource_settings\': \'cr1\', \'enable_efa\': True,\n    \'disable_hyperthreading\': True}, [\n    "Parameter \'enable_efa\' can be used only in \'cluster\' or in \'queue\' section"\n    ,\n    "Parameter \'disable_hyperthreading\' can be used only in \'cluster\' or in \'queue\' section"\n    ], [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr1 does not support EFA."\n    ]), ({\'queue_settings\': \'default\', \'enable_efa\': \'compute\',\n    \'enable_efa_gdr\': \'compute\', \'disable_hyperthreading\': True}, {\n    \'compute_resource_settings\': \'cr1\', \'enable_efa\': False,\n    \'enable_efa_gdr\': False, \'disable_hyperthreading\': False}, [\n    "Parameter \'enable_efa\' can be used only in \'cluster\' or in \'queue\' section"\n    ,\n    "Parameter \'enable_efa_gdr\' can be used only in \'cluster\' or in \'queue\' section"\n    ,\n    "Parameter \'disable_hyperthreading\' can be used only in \'cluster\' or in \'queue\' section"\n    ], None), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'efa_instance\', \'enable_efa\': True}, None, None)]'}, {}), '(\n    \'cluster_dict, queue_dict, expected_error_messages, expected_warning_messages\'\n    , [({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr1,cr2\', \'enable_efa\': True, \'disable_hyperthreading\': True}, [\n    "Duplicate instance type \'t2.micro\' found in queue \'default\'. Compute resources in the same queue must use different instance types"\n    ], [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr1 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr3,cr4\', \'enable_efa\': True, \'disable_hyperthreading\': True}, [\n    "Duplicate instance type \'c4.xlarge\' found in queue \'default\'. Compute resources in the same queue must use different instance types"\n    ], [\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr3 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr1,cr3\', \'enable_efa\': True, \'disable_hyperthreading\': True}, None, [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr1 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr3 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr2,cr4\', \'enable_efa\': True, \'disable_hyperthreading\': True}, None, [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr2,cr4\', \'enable_efa\': True, \'enable_efa_gdr\': True}, None, [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA."\n    ,\n    "EFA GDR was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr2 does not support EFA GDR."\n    ,\n    "EFA was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA."\n    ,\n    "EFA GDR was enabled on queue \'default\', but instance type \'c4.xlarge\' defined in compute resource settings cr4 does not support EFA GDR."\n    ]), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'efa_instance\', \'enable_efa_gdr\': True}, [\n    "The parameter \'enable_efa_gdr\' can be used only in combination with \'enable_efa\'"\n    ], None), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'cr1\'}, None, None), ({\'queue_settings\': \'default\', \'enable_efa\':\n    \'compute\', \'disable_hyperthreading\': True}, {\n    \'compute_resource_settings\': \'cr1\', \'enable_efa\': True,\n    \'disable_hyperthreading\': True}, [\n    "Parameter \'enable_efa\' can be used only in \'cluster\' or in \'queue\' section"\n    ,\n    "Parameter \'disable_hyperthreading\' can be used only in \'cluster\' or in \'queue\' section"\n    ], [\n    "EFA was enabled on queue \'default\', but instance type \'t2.micro\' defined in compute resource settings cr1 does not support EFA."\n    ]), ({\'queue_settings\': \'default\', \'enable_efa\': \'compute\',\n    \'enable_efa_gdr\': \'compute\', \'disable_hyperthreading\': True}, {\n    \'compute_resource_settings\': \'cr1\', \'enable_efa\': False,\n    \'enable_efa_gdr\': False, \'disable_hyperthreading\': False}, [\n    "Parameter \'enable_efa\' can be used only in \'cluster\' or in \'queue\' section"\n    ,\n    "Parameter \'enable_efa_gdr\' can be used only in \'cluster\' or in \'queue\' section"\n    ,\n    "Parameter \'disable_hyperthreading\' can be used only in \'cluster\' or in \'queue\' section"\n    ], None), ({\'queue_settings\': \'default\'}, {\'compute_resource_settings\':\n    \'efa_instance\', \'enable_efa\': True}, None, None)])', False, 'import pytest\n'), ((2110, 1, 2131, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2111, 4, 2111, 35): '"""param_value, expected_message"""', (2112, 4, 2130, 5): '[(\'section1!2\',\n    "Invalid label \'section1!2\' in param \'queue_settings\'. Section labels can only contain alphanumeric characters, dashes or underscores."\n    ), (\n    \'section!123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyz_\'\n    ,\n    "Invalid label \'section!123456789...\' in param \'queue_settings\'. Section labels can only contain alphanumeric characters, dashes or underscores."\n    ), (\'section-1\', None), (\'section_1\', None), (\n    \'section_123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyz_\'\n    ,\n    "Invalid label \'section_123456789...\' in param \'queue_settings\'. The maximum length allowed for section labels is 64 characters"\n    )]'}, {}), '(\'param_value, expected_message\', [(\'section1!2\',\n    "Invalid label \'section1!2\' in param \'queue_settings\'. Section labels can only contain alphanumeric characters, dashes or underscores."\n    ), (\n    \'section!123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyz_\'\n    ,\n    "Invalid label \'section!123456789...\' in param \'queue_settings\'. Section labels can only contain alphanumeric characters, dashes or underscores."\n    ), (\'section-1\', None), (\'section_1\', None), (\n    \'section_123456789abcdefghijklmnopqrstuvwxyz_123456789abcdefghijklmnopqrstuvwxyz_\'\n    ,\n    "Invalid label \'section_123456789...\' in param \'queue_settings\'. The maximum length allowed for section labels is 64 characters"\n    )])', False, 'import pytest\n'), ((2141, 1, 2164, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2142, 4, 2142, 36): '"""section_dict, expected_message"""', (2143, 4, 2163, 5): '[({\'min_count\': -1, \'initial_count\': -1},\n    "Parameter \'min_count\' must be 0 or greater than 0"), ({\'min_count\': 0,\n    \'initial_count\': 1, \'spot_price\': -1.1},\n    "Parameter \'spot_price\' must be 0 or greater than 0"), ({\'min_count\': 1,\n    \'max_count\': 0, \'initial_count\': 1},\n    "Parameter \'max_count\' must be greater than or equal to \'min_count\'"),\n    ({\'min_count\': 0, \'max_count\': 0, \'initial_count\': 0},\n    "Parameter \'max_count\' must be 1 or greater than 1"), ({\'min_count\': 1,\n    \'max_count\': 2, \'spot_price\': 1.5, \'initial_count\': 1}, None), ({\n    \'min_count\': 2, \'max_count\': 4, \'initial_count\': 1},\n    "Parameter \'initial_count\' must be greater than or equal to \'min_count\'"\n    ), ({\'min_count\': 2, \'max_count\': 4, \'initial_count\': 5},\n    "Parameter \'initial_count\' must be lower than or equal to \'max_count\'")]'}, {}), '(\'section_dict, expected_message\', [({\'min_count\': -\n    1, \'initial_count\': -1},\n    "Parameter \'min_count\' must be 0 or greater than 0"), ({\'min_count\': 0,\n    \'initial_count\': 1, \'spot_price\': -1.1},\n    "Parameter \'spot_price\' must be 0 or greater than 0"), ({\'min_count\': 1,\n    \'max_count\': 0, \'initial_count\': 1},\n    "Parameter \'max_count\' must be greater than or equal to \'min_count\'"),\n    ({\'min_count\': 0, \'max_count\': 0, \'initial_count\': 0},\n    "Parameter \'max_count\' must be 1 or greater than 1"), ({\'min_count\': 1,\n    \'max_count\': 2, \'spot_price\': 1.5, \'initial_count\': 1}, None), ({\n    \'min_count\': 2, \'max_count\': 4, \'initial_count\': 1},\n    "Parameter \'initial_count\' must be greater than or equal to \'min_count\'"\n    ), ({\'min_count\': 2, \'max_count\': 4, \'initial_count\': 5},\n    "Parameter \'initial_count\' must be lower than or equal to \'max_count\'")])', False, 'import pytest\n'), ((2195, 1, 2209, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2196, 4, 2196, 59): '"""cluster_section_dict, sections_dict, expected_message"""', (2197, 4, 2208, 5): '[({\'vpc_settings\': \'vpc1, vpc2\'}, {\'vpc vpc1\': {}, \'vpc vpc2\': {}},\n    "The value of \'vpc_settings\' parameter is invalid. It can only contain a single vpc section label"\n    ), ({\'efs_settings\': \'efs1, efs2\'}, {\'efs efs1\': {}, \'efs efs2\': {}},\n    "The value of \'efs_settings\' parameter is invalid. It can only contain a single efs section label"\n    )]'}, {}), '(\'cluster_section_dict, sections_dict, expected_message\'\n    , [({\'vpc_settings\': \'vpc1, vpc2\'}, {\'vpc vpc1\': {}, \'vpc vpc2\': {}},\n    "The value of \'vpc_settings\' parameter is invalid. It can only contain a single vpc section label"\n    ), ({\'efs_settings\': \'efs1, efs2\'}, {\'efs efs1\': {}, \'efs efs2\': {}},\n    "The value of \'efs_settings\' parameter is invalid. It can only contain a single efs section label"\n    )])', False, 'import pytest\n'), ((2291, 1, 2299, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2292, 4, 2292, 44): '"""enabled, architecture, expected_errors"""', (2293, 4, 2298, 5): "[(True, 'x86_64', []), (True, 'arm64', [\n    'instance types and an AMI that support these architectures']), (False,\n    'x86_64', []), (False, 'arm64', [])]"}, {}), "('enabled, architecture, expected_errors', [(True,\n    'x86_64', []), (True, 'arm64', [\n    'instance types and an AMI that support these architectures']), (False,\n    'x86_64', []), (False, 'arm64', [])])", False, 'import pytest\n'), ((2316, 1, 2339, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2317, 4, 2317, 63): '"""base_os, architecture, expected_warnings, expected_errors"""', (2318, 4, 2338, 5): "[('alinux2', 'x86_64', [], []), ('centos7', 'x86_64', [], []), ('centos8',\n    'x86_64', [], []), ('ubuntu1804', 'x86_64', [], []), ('alinux2',\n    'arm64', [], []), ('centos7', 'arm64', [\n    'Warning: The aarch64 CentOS 7 OS is not validated for the 6th generation aarch64 instances (M6g, C6g, etc.). To proceed please provide a custom_ami, for more info see: https://wiki.centos.org/Cloud/AWS#aarch64_notes'\n    ], []), ('centos8', 'arm64', [], []), ('ubuntu1804', 'arm64', [], [])]"}, {}), "(\n    'base_os, architecture, expected_warnings, expected_errors', [(\n    'alinux2', 'x86_64', [], []), ('centos7', 'x86_64', [], []), ('centos8',\n    'x86_64', [], []), ('ubuntu1804', 'x86_64', [], []), ('alinux2',\n    'arm64', [], []), ('centos7', 'arm64', [\n    'Warning: The aarch64 CentOS 7 OS is not validated for the 6th generation aarch64 instances (M6g, C6g, etc.). To proceed please provide a custom_ami, for more info see: https://wiki.centos.org/Cloud/AWS#aarch64_notes'\n    ], []), ('centos8', 'arm64', [], []), ('ubuntu1804', 'arm64', [], [])])", False, 'import pytest\n'), ((2356, 1, 2368, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2357, 4, 2357, 59): '"""disable_hyperthreading, architecture, expected_errors"""', (2358, 4, 2367, 5): "[(True, 'x86_64', []), (False, 'x86_64', []), (True, 'arm64', [\n    'disable_hyperthreading is only supported on instance types that support these architectures'\n    ]), (False, 'arm64', [])]"}, {}), "('disable_hyperthreading, architecture, expected_errors'\n    , [(True, 'x86_64', []), (False, 'x86_64', []), (True, 'arm64', [\n    'disable_hyperthreading is only supported on instance types that support these architectures'\n    ]), (False, 'arm64', [])])", False, 'import pytest\n'), ((2384, 1, 2416, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2385, 4, 2385, 90): '"""head_node_architecture, compute_architecture, compute_instance_type, expected_errors"""', (2386, 4, 2415, 5): "[('x86_64', 'x86_64', 'c5.xlarge', []), ('x86_64', 'arm64', 'm6g.xlarge', [\n    'none of which are compatible with the architecture supported by the master_instance_type'\n    ]), ('arm64', 'x86_64', 'c5.xlarge', [\n    'none of which are compatible with the architecture supported by the master_instance_type'\n    ]), ('arm64', 'arm64', 'm6g.xlarge', []), ('x86_64', 'x86_64',\n    'optimal', []), ('x86_64', None, 'm6g', []), ('x86_64', None, 'c5', []),\n    ('arm64', 'arm64', 'm6g.xlarge,r6g.xlarge', []), ('x86_64', 'arm64',\n    'm6g.xlarge,r6g.xlarge', [\n    'none of which are compatible with the architecture supported by the master_instance_type'\n    ] * 2)]"}, {}), "(\n    'head_node_architecture, compute_architecture, compute_instance_type, expected_errors'\n    , [('x86_64', 'x86_64', 'c5.xlarge', []), ('x86_64', 'arm64',\n    'm6g.xlarge', [\n    'none of which are compatible with the architecture supported by the master_instance_type'\n    ]), ('arm64', 'x86_64', 'c5.xlarge', [\n    'none of which are compatible with the architecture supported by the master_instance_type'\n    ]), ('arm64', 'arm64', 'm6g.xlarge', []), ('x86_64', 'x86_64',\n    'optimal', []), ('x86_64', None, 'm6g', []), ('x86_64', None, 'c5', []),\n    ('arm64', 'arm64', 'm6g.xlarge,r6g.xlarge', []), ('x86_64', 'arm64',\n    'm6g.xlarge,r6g.xlarge', [\n    'none of which are compatible with the architecture supported by the master_instance_type'\n    ] * 2)])", False, 'import pytest\n'), ((2450, 1, 2512, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2451, 4, 2451, 53): '"""section_dict, bucket, num_calls, expected_error"""', (2452, 4, 2511, 5): '[({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'deployment_type\':\n    \'PERSISTENT_1\', \'per_unit_storage_throughput\': 50}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'deployment_type\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'storage_capacity\': \n    7200}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'storage_capacity\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'deployment_type\':\n    \'PERSISTENT_1\', \'per_unit_storage_throughput\': 100}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'per_unit_storage_throughput\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\',\n    \'imported_file_chunk_size\': 1024, \'export_path\': \'s3://test\',\n    \'import_path\': \'s3://test\'}, {\'Bucket\': \'test\'}, 2,\n    "When restoring an FSx Lustre file system from backup, \'imported_file_chunk_size\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'fsx_kms_key_id\':\n    \'somekey\', \'deployment_type\': \'PERSISTENT_1\',\n    \'per_unit_storage_throughput\': 50}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'fsx_kms_key_id\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-00000000000000000\', \'deployment_type\':\n    \'PERSISTENT_1\', \'per_unit_storage_throughput\': 50}, None, 0,\n    "Failed to retrieve backup with Id \'backup-00000000000000000\'")]'}, {}), '(\'section_dict, bucket, num_calls, expected_error\',\n    [({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'deployment_type\':\n    \'PERSISTENT_1\', \'per_unit_storage_throughput\': 50}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'deployment_type\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'storage_capacity\': \n    7200}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'storage_capacity\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'deployment_type\':\n    \'PERSISTENT_1\', \'per_unit_storage_throughput\': 100}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'per_unit_storage_throughput\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\',\n    \'imported_file_chunk_size\': 1024, \'export_path\': \'s3://test\',\n    \'import_path\': \'s3://test\'}, {\'Bucket\': \'test\'}, 2,\n    "When restoring an FSx Lustre file system from backup, \'imported_file_chunk_size\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-0ff8da96d57f3b4e3\', \'fsx_kms_key_id\':\n    \'somekey\', \'deployment_type\': \'PERSISTENT_1\',\n    \'per_unit_storage_throughput\': 50}, None, 0,\n    "When restoring an FSx Lustre file system from backup, \'fsx_kms_key_id\' cannot be specified."\n    ), ({\'fsx_backup_id\': \'backup-00000000000000000\', \'deployment_type\':\n    \'PERSISTENT_1\', \'per_unit_storage_throughput\': 50}, None, 0,\n    "Failed to retrieve backup with Id \'backup-00000000000000000\'")])', False, 'import pytest\n'), ((2572, 1, 2581, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2573, 4, 2573, 34): '"""section_dict, expected_error"""', (2574, 4, 2580, 5): "[({'fsx_fs_id': 'fs-0123456789abcdef0', 'shared_dir': '/fsx'}, None), ({\n    'fsx_fs_id': 'fs-0123456789abcdef0', 'shared_dir': '/fsx',\n    'storage_capacity': 3600},\n    'storage_capacity is ignored when specifying an existing Lustre file system'\n    )]"}, {}), "('section_dict, expected_error', [({'fsx_fs_id':\n    'fs-0123456789abcdef0', 'shared_dir': '/fsx'}, None), ({'fsx_fs_id':\n    'fs-0123456789abcdef0', 'shared_dir': '/fsx', 'storage_capacity': 3600},\n    'storage_capacity is ignored when specifying an existing Lustre file system'\n    )])", False, 'import pytest\n'), ((2598, 1, 2623, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2599, 4, 2599, 34): '"""section_dict, expected_error"""', (2600, 4, 2622, 5): "[({'volume_type': 'standard', 'volume_size': 15}, None), ({'volume_type':\n    'standard', 'volume_size': 0},\n    'The size of standard volumes must be at least 1 GiB'), ({'volume_type':\n    'standard', 'volume_size': 1025},\n    'The size of standard volumes can not exceed 1024 GiB'), ({\n    'volume_type': 'io1', 'volume_size': 15}, None), ({'volume_type': 'io1',\n    'volume_size': 3}, 'The size of io1 volumes must be at least 4 GiB'), (\n    {'volume_type': 'io1', 'volume_size': 16385},\n    'The size of io1 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'io2', 'volume_size': 15}, None), ({'volume_type': 'io2', 'volume_size':\n    3}, 'The size of io2 volumes must be at least 4 GiB'), ({'volume_type':\n    'io2', 'volume_size': 65537},\n    'The size of io2 volumes can not exceed 65536 GiB'), ({'volume_type':\n    'gp2', 'volume_size': 15}, None), ({'volume_type': 'gp2', 'volume_size':\n    0}, 'The size of gp2 volumes must be at least 1 GiB'), ({'volume_type':\n    'gp2', 'volume_size': 16385},\n    'The size of gp2 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'gp3', 'volume_size': 15}, None), ({'volume_type': 'gp3', 'volume_size':\n    0}, 'The size of gp3 volumes must be at least 1 GiB'), ({'volume_type':\n    'gp3', 'volume_size': 16385},\n    'The size of gp3 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'st1', 'volume_size': 500}, None), ({'volume_type': 'st1',\n    'volume_size': 20}, 'The size of st1 volumes must be at least 500 GiB'),\n    ({'volume_type': 'st1', 'volume_size': 16385},\n    'The size of st1 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'sc1', 'volume_size': 500}, None), ({'volume_type': 'sc1',\n    'volume_size': 20}, 'The size of sc1 volumes must be at least 500 GiB'),\n    ({'volume_type': 'sc1', 'volume_size': 16385},\n    'The size of sc1 volumes can not exceed 16384 GiB')]"}, {}), "('section_dict, expected_error', [({'volume_type':\n    'standard', 'volume_size': 15}, None), ({'volume_type': 'standard',\n    'volume_size': 0},\n    'The size of standard volumes must be at least 1 GiB'), ({'volume_type':\n    'standard', 'volume_size': 1025},\n    'The size of standard volumes can not exceed 1024 GiB'), ({\n    'volume_type': 'io1', 'volume_size': 15}, None), ({'volume_type': 'io1',\n    'volume_size': 3}, 'The size of io1 volumes must be at least 4 GiB'), (\n    {'volume_type': 'io1', 'volume_size': 16385},\n    'The size of io1 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'io2', 'volume_size': 15}, None), ({'volume_type': 'io2', 'volume_size':\n    3}, 'The size of io2 volumes must be at least 4 GiB'), ({'volume_type':\n    'io2', 'volume_size': 65537},\n    'The size of io2 volumes can not exceed 65536 GiB'), ({'volume_type':\n    'gp2', 'volume_size': 15}, None), ({'volume_type': 'gp2', 'volume_size':\n    0}, 'The size of gp2 volumes must be at least 1 GiB'), ({'volume_type':\n    'gp2', 'volume_size': 16385},\n    'The size of gp2 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'gp3', 'volume_size': 15}, None), ({'volume_type': 'gp3', 'volume_size':\n    0}, 'The size of gp3 volumes must be at least 1 GiB'), ({'volume_type':\n    'gp3', 'volume_size': 16385},\n    'The size of gp3 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'st1', 'volume_size': 500}, None), ({'volume_type': 'st1',\n    'volume_size': 20}, 'The size of st1 volumes must be at least 500 GiB'),\n    ({'volume_type': 'st1', 'volume_size': 16385},\n    'The size of st1 volumes can not exceed 16384 GiB'), ({'volume_type':\n    'sc1', 'volume_size': 500}, None), ({'volume_type': 'sc1',\n    'volume_size': 20}, 'The size of sc1 volumes must be at least 500 GiB'),\n    ({'volume_type': 'sc1', 'volume_size': 16385},\n    'The size of sc1 volumes can not exceed 16384 GiB')])", False, 'import pytest\n'), ((2637, 1, 2677, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2638, 4, 2638, 36): '"""section_dict, expected_message"""', (2639, 4, 2676, 5): "[({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 120}, None), ({\n    'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 64001},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 1001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 120}, None), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 256001},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 20001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 3000}, None), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 2900},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 16001},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 10001},\n    'IOPS to volume size ratio of .* is too high')]"}, {}), "('section_dict, expected_message', [({'volume_type':\n    'io1', 'volume_size': 20, 'volume_iops': 120}, None), ({'volume_type':\n    'io1', 'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 64001},\n    'IOPS rate must be between 100 and 64000 when provisioning io1 volumes.'\n    ), ({'volume_type': 'io1', 'volume_size': 20, 'volume_iops': 1001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 120}, None), ({'volume_type': 'io2',\n    'volume_size': 20, 'volume_iops': 90},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 256001},\n    'IOPS rate must be between 100 and 256000 when provisioning io2 volumes.'\n    ), ({'volume_type': 'io2', 'volume_size': 20, 'volume_iops': 20001},\n    'IOPS to volume size ratio of .* is too high'), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 3000}, None), ({'volume_type': 'gp3',\n    'volume_size': 20, 'volume_iops': 2900},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 16001},\n    'IOPS rate must be between 3000 and 16000 when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_size': 20, 'volume_iops': 10001},\n    'IOPS to volume size ratio of .* is too high')])", False, 'import pytest\n'), ((2683, 1, 2750, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2684, 4, 2685, 44): '"""section_dict, snapshot_size, state, partition, expected_warning, expected_error, raise_error_when_getting_snapshot_info"""', (2686, 4, 2749, 5): '[({\'volume_size\': 100, \'ebs_snapshot_id\': \'snap-1234567890abcdef0\'}, 50,\n    \'completed\', \'aws-cn\',\n    "The specified volume size is larger than snapshot size. In order to use the full capacity of the volume, you\'ll need to manually resize the partition according to this doc: https://docs.amazonaws.cn/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html"\n    , None, False), ({\'volume_size\': 100, \'ebs_snapshot_id\':\n    \'snap-1234567890abcdef0\'}, 50, \'completed\', \'aws-us-gov\',\n    "The specified volume size is larger than snapshot size. In order to use the full capacity of the volume, you\'ll need to manually resize the partition according to this doc: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html"\n    , None, False), ({\'volume_size\': 100, \'ebs_snapshot_id\':\n    \'snap-1234567890abcdef0\'}, 50, \'incompleted\', \'aws-us-gov\',\n    "Snapshot snap-1234567890abcdef0 is in state \'incompleted\' not \'completed\'"\n    , None, False), ({\'ebs_snapshot_id\': \'snap-1234567890abcdef0\'}, 50,\n    \'completed\', \'partition\', None, None, False), ({\'volume_size\': 100,\n    \'ebs_snapshot_id\': \'snap-1234567891abcdef0\'}, 120, \'completed\',\n    \'aws-us-gov\', None,\n    "The EBS volume size of the section \'default\' must not be smaller than 120, because it is the size of the provided snapshot snap-1234567891abcdef0"\n    , False), ({\'volume_size\': 100, \'ebs_snapshot_id\':\n    \'snap-1234567890abcdef0\'}, None, \'completed\', \'aws-cn\', None,\n    \'Unable to get volume size for snapshot snap-1234567890abcdef0\', False),\n    ({\'ebs_snapshot_id\': \'snap-1234567890abcdef0\'}, 20, \'completed\', \'aws\',\n    None, \'some message\', True)]'}, {}), '(\n    \'section_dict, snapshot_size, state, partition, expected_warning, expected_error, raise_error_when_getting_snapshot_info\'\n    , [({\'volume_size\': 100, \'ebs_snapshot_id\': \'snap-1234567890abcdef0\'}, \n    50, \'completed\', \'aws-cn\',\n    "The specified volume size is larger than snapshot size. In order to use the full capacity of the volume, you\'ll need to manually resize the partition according to this doc: https://docs.amazonaws.cn/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html"\n    , None, False), ({\'volume_size\': 100, \'ebs_snapshot_id\':\n    \'snap-1234567890abcdef0\'}, 50, \'completed\', \'aws-us-gov\',\n    "The specified volume size is larger than snapshot size. In order to use the full capacity of the volume, you\'ll need to manually resize the partition according to this doc: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html"\n    , None, False), ({\'volume_size\': 100, \'ebs_snapshot_id\':\n    \'snap-1234567890abcdef0\'}, 50, \'incompleted\', \'aws-us-gov\',\n    "Snapshot snap-1234567890abcdef0 is in state \'incompleted\' not \'completed\'"\n    , None, False), ({\'ebs_snapshot_id\': \'snap-1234567890abcdef0\'}, 50,\n    \'completed\', \'partition\', None, None, False), ({\'volume_size\': 100,\n    \'ebs_snapshot_id\': \'snap-1234567891abcdef0\'}, 120, \'completed\',\n    \'aws-us-gov\', None,\n    "The EBS volume size of the section \'default\' must not be smaller than 120, because it is the size of the provided snapshot snap-1234567891abcdef0"\n    , False), ({\'volume_size\': 100, \'ebs_snapshot_id\':\n    \'snap-1234567890abcdef0\'}, None, \'completed\', \'aws-cn\', None,\n    \'Unable to get volume size for snapshot snap-1234567890abcdef0\', False),\n    ({\'ebs_snapshot_id\': \'snap-1234567890abcdef0\'}, 20, \'completed\', \'aws\',\n    None, \'some message\', True)])', False, 'import pytest\n'), ((2789, 1, 2835, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2790, 4, 2790, 82): '"""cluster_section_dict, ebs_section_dict1, ebs_section_dict2, expected_message"""', (2791, 4, 2834, 5): '[({\'shared_dir\': \'shared_directory\', \'ebs_settings\': \'vol1\'}, {\n    \'volume_size\': 30}, {}, None), ({\'shared_dir\': \'shared_directory\',\n    \'ebs_settings\': \'vol1\'}, {\'shared_dir\': \'shared_directory1\'}, {},\n    "\'shared_dir\' can not be specified both in cluster section and EBS section"\n    ), ({\'shared_dir\': \'shared_directory\', \'ebs_settings\': \'vol1, vol2\'}, {\n    \'shared_dir\': \'shared_directory1\', \'volume_size\': 30}, {\'shared_dir\':\n    \'shared_directory2\', \'volume_size\': 30},\n    "\'shared_dir\' can not be specified in cluster section when using multiple EBS volumes"\n    ), ({\'ebs_settings\': \'vol1, vol2\'}, {\'shared_dir\': \'shared_directory1\',\n    \'volume_size\': 30}, {\'shared_dir\': \'shared_directory2\', \'volume_size\': \n    30}, None), ({\'ebs_settings\': \'vol1\'}, {\'volume_size\': 30}, {}, None),\n    ({\'ebs_settings\': \'vol1\'}, {}, {}, None), ({\'shared_dir\':\n    \'shared_directory\'}, {}, {}, None)]'}, {}), '(\n    \'cluster_section_dict, ebs_section_dict1, ebs_section_dict2, expected_message\'\n    , [({\'shared_dir\': \'shared_directory\', \'ebs_settings\': \'vol1\'}, {\n    \'volume_size\': 30}, {}, None), ({\'shared_dir\': \'shared_directory\',\n    \'ebs_settings\': \'vol1\'}, {\'shared_dir\': \'shared_directory1\'}, {},\n    "\'shared_dir\' can not be specified both in cluster section and EBS section"\n    ), ({\'shared_dir\': \'shared_directory\', \'ebs_settings\': \'vol1, vol2\'}, {\n    \'shared_dir\': \'shared_directory1\', \'volume_size\': 30}, {\'shared_dir\':\n    \'shared_directory2\', \'volume_size\': 30},\n    "\'shared_dir\' can not be specified in cluster section when using multiple EBS volumes"\n    ), ({\'ebs_settings\': \'vol1, vol2\'}, {\'shared_dir\': \'shared_directory1\',\n    \'volume_size\': 30}, {\'shared_dir\': \'shared_directory2\', \'volume_size\': \n    30}, None), ({\'ebs_settings\': \'vol1\'}, {\'volume_size\': 30}, {}, None),\n    ({\'ebs_settings\': \'vol1\'}, {}, {}, None), ({\'shared_dir\':\n    \'shared_directory\'}, {}, {}, None)])', False, 'import pytest\n'), ((2848, 1, 2867, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2849, 4, 2849, 34): '"""extra_json, expected_message"""', (2850, 4, 2866, 5): "[({'extra_json': {'cluster': {'cfn_scheduler_slots': '1'}}},\n    'It is highly recommended to use the disable_hyperthreading parameter in order to control the hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json'\n    ), ({'extra_json': {'cluster': {'cfn_scheduler_slots': 'vcpus'}}},\n    'It is highly recommended to use the disable_hyperthreading parameter in order to control the hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json'\n    ), ({'extra_json': {'cluster': {'cfn_scheduler_slots': 'cores'}}},\n    'It is highly recommended to use the disable_hyperthreading parameter in order to control the hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json'\n    )]"}, {}), "('extra_json, expected_message', [({'extra_json': {\n    'cluster': {'cfn_scheduler_slots': '1'}}},\n    'It is highly recommended to use the disable_hyperthreading parameter in order to control the hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json'\n    ), ({'extra_json': {'cluster': {'cfn_scheduler_slots': 'vcpus'}}},\n    'It is highly recommended to use the disable_hyperthreading parameter in order to control the hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json'\n    ), ({'extra_json': {'cluster': {'cfn_scheduler_slots': 'cores'}}},\n    'It is highly recommended to use the disable_hyperthreading parameter in order to control the hyper-threading configuration in the cluster rather than using cfn_scheduler_slots in extra_json'\n    )])", False, 'import pytest\n'), ((2873, 1, 2889, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2874, 4, 2874, 48): '"""cluster_dict, architecture, expected_error"""', (2875, 4, 2888, 5): "[({'base_os': 'alinux2', 'enable_efa': 'compute'}, 'x86_64', None), ({\n    'base_os': 'alinux2', 'enable_efa': 'compute'}, 'arm64', None), ({\n    'base_os': 'centos8', 'enable_efa': 'compute'}, 'x86_64', None), ({\n    'base_os': 'centos8'}, 'x86_64', None), ({'base_os': 'centos8',\n    'enable_efa': 'compute'}, 'arm64',\n    'EFA currently not supported on centos8 for arm64 architecture'), ({\n    'base_os': 'centos8'}, 'arm64', None), ({'base_os': 'ubuntu1804',\n    'enable_efa': 'compute'}, 'x86_64', None), ({'base_os': 'ubuntu1804',\n    'enable_efa': 'compute'}, 'arm64', None)]"}, {}), "('cluster_dict, architecture, expected_error', [({\n    'base_os': 'alinux2', 'enable_efa': 'compute'}, 'x86_64', None), ({\n    'base_os': 'alinux2', 'enable_efa': 'compute'}, 'arm64', None), ({\n    'base_os': 'centos8', 'enable_efa': 'compute'}, 'x86_64', None), ({\n    'base_os': 'centos8'}, 'x86_64', None), ({'base_os': 'centos8',\n    'enable_efa': 'compute'}, 'arm64',\n    'EFA currently not supported on centos8 for arm64 architecture'), ({\n    'base_os': 'centos8'}, 'arm64', None), ({'base_os': 'ubuntu1804',\n    'enable_efa': 'compute'}, 'x86_64', None), ({'base_os': 'ubuntu1804',\n    'enable_efa': 'compute'}, 'arm64', None)])", False, 'import pytest\n'), ((2910, 1, 2929, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2911, 4, 2911, 36): '"""section_dict, expected_message"""', (2912, 4, 2928, 5): "[({'volume_type': 'gp3', 'volume_throughput': 125}, None), ({'volume_type':\n    'gp3', 'volume_throughput': 100},\n    'Throughput must be between 125 MB/s and 1000 MB/s when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_throughput': 1001},\n    'Throughput must be between 125 MB/s and 1000 MB/s when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_throughput': 125, 'volume_iops': \n    3000}, None), ({'volume_type': 'gp3', 'volume_throughput': 760,\n    'volume_iops': 3000}, 'Throughput to IOPS ratio of .* is too high'), ({\n    'volume_type': 'gp3', 'volume_throughput': 760, 'volume_iops': 10000},\n    None)]"}, {}), "('section_dict, expected_message', [({'volume_type':\n    'gp3', 'volume_throughput': 125}, None), ({'volume_type': 'gp3',\n    'volume_throughput': 100},\n    'Throughput must be between 125 MB/s and 1000 MB/s when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_throughput': 1001},\n    'Throughput must be between 125 MB/s and 1000 MB/s when provisioning gp3 volumes.'\n    ), ({'volume_type': 'gp3', 'volume_throughput': 125, 'volume_iops': \n    3000}, None), ({'volume_type': 'gp3', 'volume_throughput': 760,\n    'volume_iops': 3000}, 'Throughput to IOPS ratio of .* is too high'), ({\n    'volume_type': 'gp3', 'volume_throughput': 760, 'volume_iops': 10000},\n    None)])", False, 'import pytest\n'), ((2935, 1, 2941, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2936, 4, 2936, 30): '"""region, expected_message"""', (2937, 4, 2940, 5): '[(\'invalid-region\',\n    "Region \'invalid-region\' is not yet officially supported "), (\n    \'us-east-1\', None)]'}, {}), '(\'region, expected_message\', [(\'invalid-region\',\n    "Region \'invalid-region\' is not yet officially supported "), (\n    \'us-east-1\', None)])', False, 'import pytest\n'), ((2954, 1, 2966, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2955, 4, 2955, 92): '"""usage_class, supported_usage_classes, expected_error_message, expected_warning_message"""', (2956, 4, 2965, 5): '[(\'ondemand\', [\'ondemand\', \'spot\'], None, None), (\'spot\', [\'ondemand\',\n    \'spot\'], None, None), (\'ondemand\', [\'ondemand\'], None, None), (\'spot\',\n    [\'spot\'], None, None), (\'spot\', [], None,\n    "Could not check support for usage class \'spot\' with instance type \'instance-type\'"\n    ), (\'ondemand\', [], None,\n    "Could not check support for usage class \'ondemand\' with instance type \'instance-type\'"\n    ), (\'spot\', [\'ondemand\'],\n    "Usage type \'spot\' not supported with instance type \'instance-type\'",\n    None), (\'ondemand\', [\'spot\'],\n    "Usage type \'ondemand\' not supported with instance type \'instance-type\'",\n    None)]'}, {}), '(\n    \'usage_class, supported_usage_classes, expected_error_message, expected_warning_message\'\n    , [(\'ondemand\', [\'ondemand\', \'spot\'], None, None), (\'spot\', [\'ondemand\',\n    \'spot\'], None, None), (\'ondemand\', [\'ondemand\'], None, None), (\'spot\',\n    [\'spot\'], None, None), (\'spot\', [], None,\n    "Could not check support for usage class \'spot\' with instance type \'instance-type\'"\n    ), (\'ondemand\', [], None,\n    "Could not check support for usage class \'ondemand\' with instance type \'instance-type\'"\n    ), (\'spot\', [\'ondemand\'],\n    "Usage type \'spot\' not supported with instance type \'instance-type\'",\n    None), (\'ondemand\', [\'spot\'],\n    "Usage type \'ondemand\' not supported with instance type \'instance-type\'",\n    None)])', False, 'import pytest\n'), ((2992, 1, 2994, 1), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(2993, 4, 2993, 43): '"""scheduler, expected_usage_class_check"""', (2993, 45, 2993, 116): "[('sge', True), ('torque', True), ('slurm', True), ('awsbatch', False)]"}, {}), "('scheduler, expected_usage_class_check', [('sge', \n    True), ('torque', True), ('slurm', True), ('awsbatch', False)])", False, 'import pytest\n'), ((3015, 1, 3015, 66), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(3015, 25, 3015, 39): '"""compute_type"""', (3015, 41, 3015, 65): "['ondemand', 'spot']"}, {}), "('compute_type', ['ondemand', 'spot'])", False, 'import pytest\n'), ((93, 4, 95, 5), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((103, 4, 103, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(103, 33, 103, 39): 'mocker', (103, 41, 103, 59): 'config_parser_dict', (103, 61, 103, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((109, 4, 109, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(109, 33, 109, 39): 'mocker', (109, 41, 109, 59): 'config_parser_dict', (109, 61, 109, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((134, 4, 136, 5), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((154, 4, 154, 60), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(154, 33, 154, 39): 'mocker', (154, 41, 154, 59): 'config_parser_dict'}, {}), '(mocker, config_parser_dict)', True, 'import tests.pcluster.config.utils as utils\n'), ((218, 4, 218, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(218, 33, 218, 39): 'mocker', (218, 41, 218, 59): 'config_parser_dict', (218, 61, 218, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((233, 4, 233, 93), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((275, 4, 275, 60), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(275, 33, 275, 39): 'mocker', (275, 41, 275, 59): 'config_parser_dict'}, {}), '(mocker, config_parser_dict)', True, 'import tests.pcluster.config.utils as utils\n'), ((339, 4, 339, 104), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(339, 33, 339, 39): 'mocker', (339, 41, 339, 59): 'config_parser_dict', (339, 61, 339, 77): 'expected_message', (339, 79, 339, 85): 'capsys', (339, 87, 339, 103): 'expected_warning'}, {}), '(mocker, config_parser_dict, expected_message,\n    capsys, expected_warning)', True, 'import tests.pcluster.config.utils as utils\n'), ((357, 4, 357, 60), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(357, 33, 357, 39): 'mocker', (357, 41, 357, 59): 'config_parser_dict'}, {}), '(mocker, config_parser_dict)', True, 'import tests.pcluster.config.utils as utils\n'), ((516, 4, 516, 66), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(516, 33, 516, 39): 'mocker', (516, 41, 516, 47): 'config', (516, 49, 516, 65): 'expected_message'}, {}), '(mocker, config, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((553, 4, 553, 66), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(553, 33, 553, 39): 'mocker', (553, 41, 553, 47): 'config', (553, 49, 553, 65): 'expected_message'}, {}), '(mocker, config, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((643, 20, 643, 47), 'configparser.ConfigParser', 'configparser.ConfigParser', ({}, {}), '()', False, 'import configparser\n'), ((646, 22, 646, 108), 'tests.pcluster.config.utils.init_pcluster_config_from_configparser', 'utils.init_pcluster_config_from_configparser', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((647, 23, 647, 101), 'pcluster.config.validators.s3_bucket_region_validator', 's3_bucket_region_validator', ({(647, 50, 647, 75): '"""cluster_resource_bucket"""', (647, 77, 647, 83): 'bucket', (647, 85, 647, 100): 'pcluster_config'}, {}), "('cluster_resource_bucket', bucket, pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((741, 4, 741, 60), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(741, 33, 741, 39): 'mocker', (741, 41, 741, 59): 'config_parser_dict'}, {}), '(mocker, config_parser_dict)', True, 'import tests.pcluster.config.utils as utils\n'), ((780, 4, 780, 60), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(780, 33, 780, 39): 'mocker', (780, 41, 780, 59): 'config_parser_dict'}, {}), '(mocker, config_parser_dict)', True, 'import tests.pcluster.config.utils as utils\n'), ((798, 4, 798, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(798, 33, 798, 39): 'mocker', (798, 41, 798, 59): 'config_parser_dict', (798, 61, 798, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((844, 4, 844, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(844, 33, 844, 39): 'mocker', (844, 41, 844, 59): 'config_parser_dict', (844, 61, 844, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((869, 4, 871, 5), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((1161, 4, 1161, 91), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((1240, 4, 1242, 5), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((1457, 4, 1457, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1457, 33, 1457, 39): 'mocker', (1457, 41, 1457, 59): 'config_parser_dict', (1457, 61, 1457, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1474, 4, 1474, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1474, 33, 1474, 39): 'mocker', (1474, 41, 1474, 59): 'config_parser_dict', (1474, 61, 1474, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1501, 4, 1501, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1501, 33, 1501, 39): 'mocker', (1501, 41, 1501, 59): 'config_parser_dict', (1501, 61, 1501, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1538, 4, 1538, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1538, 33, 1538, 39): 'mocker', (1538, 41, 1538, 59): 'config_parser_dict', (1538, 61, 1538, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1622, 4, 1630, 5), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((1652, 20, 1652, 47), 'configparser.ConfigParser', 'configparser.ConfigParser', ({}, {}), '()', False, 'import configparser\n'), ((1655, 22, 1655, 108), 'tests.pcluster.config.utils.init_pcluster_config_from_configparser', 'utils.init_pcluster_config_from_configparser', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((1658, 23, 1658, 97), 'pcluster.config.validators.efa_gdr_validator', 'efa_gdr_validator', ({(1658, 41, 1658, 57): '"""enable_efa_gdr"""', (1658, 59, 1658, 79): 'enable_efa_gdr_value', (1658, 81, 1658, 96): 'pcluster_config'}, {}), "('enable_efa_gdr', enable_efa_gdr_value, pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((1740, 4, 1740, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1740, 33, 1740, 39): 'mocker', (1740, 41, 1740, 59): 'config_parser_dict', (1740, 61, 1740, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1780, 4, 1780, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1780, 33, 1780, 39): 'mocker', (1780, 41, 1780, 59): 'config_parser_dict', (1780, 61, 1780, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1793, 4, 1793, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1793, 33, 1793, 39): 'mocker', (1793, 41, 1793, 59): 'config_parser_dict', (1793, 61, 1793, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1830, 4, 1832, 5), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((1869, 4, 1869, 107), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((1887, 4, 1887, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1887, 33, 1887, 39): 'mocker', (1887, 41, 1887, 59): 'config_parser_dict', (1887, 61, 1887, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((1957, 4, 1957, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(1957, 33, 1957, 39): 'mocker', (1957, 41, 1957, 59): 'config_parser_dict', (1957, 61, 1957, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((2086, 20, 2086, 47), 'configparser.ConfigParser', 'configparser.ConfigParser', ({}, {}), '()', False, 'import configparser\n'), ((2089, 22, 2089, 108), 'tests.pcluster.config.utils.init_pcluster_config_from_configparser', 'utils.init_pcluster_config_from_configparser', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((2097, 23, 2097, 75), 'pcluster.config.validators.queue_validator', 'queue_validator', ({(2097, 39, 2097, 46): '"""queue"""', (2097, 48, 2097, 57): '"""default"""', (2097, 59, 2097, 74): 'pcluster_config'}, {}), "('queue', 'default', pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2133, 23, 2133, 78), 'pcluster.config.validators.settings_validator', 'settings_validator', ({(2133, 42, 2133, 58): '"""queue_settings"""', (2133, 60, 2133, 71): 'param_value', (2133, 73, 2133, 77): 'None'}, {}), "('queue_settings', param_value, None)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2172, 20, 2172, 47), 'configparser.ConfigParser', 'configparser.ConfigParser', ({}, {}), '()', False, 'import configparser\n'), ((2185, 22, 2185, 88), 'tests.pcluster.config.utils.init_pcluster_config_from_configparser', 'utils.init_pcluster_config_from_configparser', ({(2185, 67, 2185, 80): 'config_parser', (2185, 82, 2185, 87): 'False'}, {}), '(config_parser, False)', True, 'import tests.pcluster.config.utils as utils\n'), ((2187, 23, 2187, 97), 'pcluster.config.validators.compute_resource_validator', 'compute_resource_validator', ({(2187, 50, 2187, 68): '"""compute_resource"""', (2187, 70, 2187, 79): '"""default"""', (2187, 81, 2187, 96): 'pcluster_config'}, {}), "('compute_resource', 'default', pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2215, 4, 2215, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(2215, 33, 2215, 39): 'mocker', (2215, 41, 2215, 59): 'config_parser_dict', (2215, 61, 2215, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((2556, 4, 2556, 91), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((2583, 29, 2583, 69), 'tests.pcluster.config.utils.get_mocked_pcluster_config', 'utils.get_mocked_pcluster_config', ({(2583, 62, 2583, 68): 'mocker'}, {}), '(mocker)', True, 'import tests.pcluster.config.utils as utils\n'), ((2584, 18, 2584, 68), 'pcluster.config.cfn_param_types.CfnSection', 'CfnSection', ({(2584, 29, 2584, 32): 'FSX', (2584, 34, 2584, 56): 'mocked_pcluster_config', (2584, 58, 2584, 67): '"""default"""'}, {}), "(FSX, mocked_pcluster_config, 'default')", False, 'from pcluster.config.cfn_param_types import CfnParam, CfnSection\n'), ((2590, 23, 2590, 97), 'pcluster.config.validators.fsx_ignored_parameters_validator', 'fsx_ignored_parameters_validator', ({(2590, 56, 2590, 61): '"""fsx"""', (2590, 63, 2590, 72): '"""default"""', (2590, 74, 2590, 96): 'mocked_pcluster_config'}, {}), "('fsx', 'default', mocked_pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2626, 4, 2626, 76), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(2626, 33, 2626, 39): 'mocker', (2626, 41, 2626, 59): 'config_parser_dict', (2626, 61, 2626, 75): 'expected_error'}, {}), '(mocker, config_parser_dict, expected_error)', True, 'import tests.pcluster.config.utils as utils\n'), ((2680, 4, 2680, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(2680, 33, 2680, 39): 'mocker', (2680, 41, 2680, 59): 'config_parser_dict', (2680, 61, 2680, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((2784, 4, 2786, 5), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((2845, 4, 2845, 93), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((2870, 4, 2870, 110), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((2896, 20, 2896, 47), 'configparser.ConfigParser', 'configparser.ConfigParser', ({}, {}), '()', False, 'import configparser\n'), ((2899, 22, 2899, 108), 'tests.pcluster.config.utils.init_pcluster_config_from_configparser', 'utils.init_pcluster_config_from_configparser', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((2903, 23, 2903, 93), 'pcluster.config.validators.efa_os_arch_validator', 'efa_os_arch_validator', ({(2903, 45, 2903, 57): '"""enable_efa"""', (2903, 59, 2903, 75): 'enable_efa_value', (2903, 77, 2903, 92): 'pcluster_config'}, {}), "('enable_efa', enable_efa_value, pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2932, 4, 2932, 78), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(2932, 33, 2932, 39): 'mocker', (2932, 41, 2932, 59): 'config_parser_dict', (2932, 61, 2932, 77): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((2943, 22, 2943, 62), 'tests.pcluster.config.utils.get_mocked_pcluster_config', 'utils.get_mocked_pcluster_config', ({(2943, 55, 2943, 61): 'mocker'}, {}), '(mocker)', True, 'import tests.pcluster.config.utils as utils\n'), ((2946, 23, 2946, 69), 'pcluster.config.validators.region_validator', 'region_validator', ({(2946, 40, 2946, 45): '"""aws"""', (2946, 47, 2946, 51): 'None', (2946, 53, 2946, 68): 'pcluster_config'}, {}), "('aws', None, pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2979, 4, 2979, 69), 'pcluster.config.validators.check_usage_class', 'check_usage_class', ({(2979, 22, 2979, 37): '"""instance-type"""', (2979, 39, 2979, 50): 'usage_class', (2979, 52, 2979, 58): 'errors', (2979, 60, 2979, 68): 'warnings'}, {}), "('instance-type', usage_class, errors, warnings)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((3001, 20, 3001, 47), 'configparser.ConfigParser', 'configparser.ConfigParser', ({}, {}), '()', False, 'import configparser\n'), ((3004, 22, 3004, 108), 'tests.pcluster.config.utils.init_pcluster_config_from_configparser', 'utils.init_pcluster_config_from_configparser', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((3005, 23, 3005, 86), 'pcluster.config.validators.cluster_type_validator', 'cluster_type_validator', ({(3005, 46, 3005, 60): '"""compute_type"""', (3005, 62, 3005, 68): '"""spot"""', (3005, 70, 3005, 85): 'pcluster_config'}, {}), "('compute_type', 'spot', pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((3030, 20, 3030, 47), 'configparser.ConfigParser', 'configparser.ConfigParser', ({}, {}), '()', False, 'import configparser\n'), ((3033, 22, 3033, 108), 'tests.pcluster.config.utils.init_pcluster_config_from_configparser', 'utils.init_pcluster_config_from_configparser', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((3034, 23, 3034, 83), 'pcluster.config.validators.queue_compute_type_validator', 'queue_compute_type_validator', ({(3034, 52, 3034, 59): '"""queue"""', (3034, 61, 3034, 65): '"""q1"""', (3034, 67, 3034, 82): 'pcluster_config'}, {}), "('queue', 'q1', pcluster_config)", False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((146, 8, 148, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((206, 8, 211, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((262, 8, 266, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((347, 8, 351, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((371, 8, 373, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((381, 8, 381, 82), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(381, 37, 381, 43): 'mocker', (381, 45, 381, 63): 'config_parser_dict', (381, 65, 381, 81): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((421, 8, 421, 114), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', (), '', True, 'import tests.pcluster.config.utils as utils\n'), ((673, 8, 675, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((685, 8, 689, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((692, 8, 696, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((703, 8, 703, 82), 'tests.pcluster.config.utils.assert_param_validator', 'utils.assert_param_validator', ({(703, 37, 703, 43): 'mocker', (703, 45, 703, 63): 'config_parser_dict', (703, 65, 703, 81): 'expected_message'}, {}), '(mocker, config_parser_dict, expected_message)', True, 'import tests.pcluster.config.utils as utils\n'), ((728, 8, 732, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((767, 8, 771, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1160, 25, 1160, 50), 're.escape', 're.escape', ({(1160, 35, 1160, 49): 'expected_error'}, {}), '(expected_error)', False, 'import re\n'), ((1328, 8, 1332, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1711, 8, 1715, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1716, 8, 1720, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1721, 8, 1725, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1864, 23, 1864, 50), 're.escape', 're.escape', ({(1864, 33, 1864, 49): 'expected_message'}, {}), '(expected_message)', False, 'import re\n'), ((2190, 8, 2190, 47), 'assertpy.assert_that', 'assert_that', ({(2190, 20, 2190, 46): '(expected_message in errors)'}, {}), '(expected_message in errors)', False, 'from assertpy import assert_that\n'), ((388, 12, 392, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((401, 12, 407, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((495, 12, 495, 107), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((499, 12, 501, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((505, 12, 511, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((621, 12, 626, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((630, 12, 636, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((879, 28, 879, 78), 'datetime.datetime', 'datetime.datetime', ({(879, 46, 879, 50): '(2019)', (879, 52, 879, 53): '(1)', (879, 55, 879, 57): '(10)', (879, 59, 879, 61): '(11)', (879, 63, 879, 65): '(25)', (879, 67, 879, 69): '(59)', (879, 71, 879, 77): '(128000)'}, {}), '(2019, 1, 10, 11, 25, 59, 128000)', False, 'import datetime\n'), ((890, 8, 895, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1258, 8, 1258, 103), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1357, 8, 1361, 9), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1418, 12, 1422, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1608, 12, 1612, 13), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1834, 4, 1834, 53), 'assertpy.assert_that', 'assert_that', ({(1834, 16, 1834, 52): '(access_from_error_msg in caplog.text)'}, {}), '(access_from_error_msg in caplog.text)', False, 'from assertpy import assert_that\n'), ((2284, 38, 2284, 66), 're.escape', 're.escape', ({(2284, 48, 2284, 65): 'expected_warnings'}, {}), '(expected_warnings)', False, 'import re\n'), ((2288, 36, 2288, 62), 're.escape', 're.escape', ({(2288, 46, 2288, 61): 'expected_errors'}, {}), '(expected_errors)', False, 'import re\n'), ((2445, 4, 2445, 57), 'assertpy.assert_that', 'assert_that', ({(2445, 16, 2445, 56): 'supported_architectures_patch.call_count'}, {}), '(supported_architectures_patch.call_count)', False, 'from assertpy import assert_that\n'), ((2446, 4, 2446, 40), 'assertpy.assert_that', 'assert_that', ({(2446, 16, 2446, 39): 'logger_patch.call_count'}, {}), '(logger_patch.call_count)', False, 'from assertpy import assert_that\n'), ((2447, 4, 2447, 50), 'assertpy.assert_that', 'assert_that', ({(2447, 16, 2447, 49): 'is_instance_type_patch.call_count'}, {}), '(is_instance_type_patch.call_count)', False, 'from assertpy import assert_that\n'), ((2591, 4, 2591, 25), 'assertpy.assert_that', 'assert_that', ({(2591, 16, 2591, 24): 'warnings'}, {}), '(warnings)', False, 'from assertpy import assert_that\n'), ((2632, 8, 2632, 52), 'pcluster.config.validators.EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS.keys', 'EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS.keys', ({}, {}), '()', False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2634, 4, 2634, 59), 'assertpy.assert_that', 'assert_that', ({(2634, 16, 2634, 58): 'allowed_values_all_have_volume_size_bounds'}, {}), '(allowed_values_all_have_volume_size_bounds)', False, 'from assertpy import assert_that\n'), ((3011, 4, 3011, 23), 'assertpy.assert_that', 'assert_that', ({(3011, 16, 3011, 22): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((3012, 4, 3012, 25), 'assertpy.assert_that', 'assert_that', ({(3012, 16, 3012, 24): 'warnings'}, {}), '(warnings)', False, 'from assertpy import assert_that\n'), ((3043, 4, 3043, 23), 'assertpy.assert_that', 'assert_that', ({(3043, 16, 3043, 22): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((3044, 4, 3044, 25), 'assertpy.assert_that', 'assert_that', ({(3044, 16, 3044, 24): 'warnings'}, {}), '(warnings)', False, 'from assertpy import assert_that\n'), ((650, 8, 650, 30), 'assertpy.assert_that', 'assert_that', ({(650, 20, 650, 29): 'errors[0]'}, {}), '(errors[0])', False, 'from assertpy import assert_that\n'), ((652, 8, 652, 27), 'assertpy.assert_that', 'assert_that', ({(652, 20, 652, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((1440, 16, 1444, 17), 'tests.common.MockedBoto3Request', 'MockedBoto3Request', (), '', False, 'from tests.common import MockedBoto3Request\n'), ((1591, 39, 1598, 17), 'json.dumps', 'json.dumps', ({(1592, 20, 1597, 21): "{'additional-instance-type': {'InstanceType': 'additional-instance-type',\n    'NetworkInfo': {'EfaSupported': True}}}"}, {}), "({'additional-instance-type': {'InstanceType':\n    'additional-instance-type', 'NetworkInfo': {'EfaSupported': True}}})", False, 'import json\n'), ((1660, 8, 1660, 30), 'assertpy.assert_that', 'assert_that', ({(1660, 20, 1660, 29): 'errors[0]'}, {}), '(errors[0])', False, 'from assertpy import assert_that\n'), ((1662, 8, 1662, 27), 'assertpy.assert_that', 'assert_that', ({(1662, 20, 1662, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2100, 8, 2100, 44), 'assertpy.assert_that', 'assert_that', ({(2100, 20, 2100, 43): 'expected_error_messages'}, {}), '(expected_error_messages)', False, 'from assertpy import assert_that\n'), ((2102, 8, 2102, 27), 'assertpy.assert_that', 'assert_that', ({(2102, 20, 2102, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2105, 8, 2105, 46), 'assertpy.assert_that', 'assert_that', ({(2105, 20, 2105, 45): 'expected_warning_messages'}, {}), '(expected_warning_messages)', False, 'from assertpy import assert_that\n'), ((2107, 8, 2107, 29), 'assertpy.assert_that', 'assert_that', ({(2107, 20, 2107, 28): 'warnings'}, {}), '(warnings)', False, 'from assertpy import assert_that\n'), ((2136, 8, 2136, 30), 'assertpy.assert_that', 'assert_that', ({(2136, 20, 2136, 29): 'errors[0]'}, {}), '(errors[0])', False, 'from assertpy import assert_that\n'), ((2138, 8, 2138, 27), 'assertpy.assert_that', 'assert_that', ({(2138, 20, 2138, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2192, 8, 2192, 27), 'assertpy.assert_that', 'assert_that', ({(2192, 20, 2192, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2284, 8, 2284, 29), 'assertpy.assert_that', 'assert_that', ({(2284, 20, 2284, 28): 'warnings'}, {}), '(warnings)', False, 'from assertpy import assert_that\n'), ((2288, 8, 2288, 27), 'assertpy.assert_that', 'assert_that', ({(2288, 20, 2288, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2593, 8, 2593, 30), 'assertpy.assert_that', 'assert_that', ({(2593, 20, 2593, 29): 'errors[0]'}, {}), '(errors[0])', False, 'from assertpy import assert_that\n'), ((2595, 8, 2595, 27), 'assertpy.assert_that', 'assert_that', ({(2595, 20, 2595, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2905, 8, 2905, 30), 'assertpy.assert_that', 'assert_that', ({(2905, 20, 2905, 29): 'errors[0]'}, {}), '(errors[0])', False, 'from assertpy import assert_that\n'), ((2907, 8, 2907, 27), 'assertpy.assert_that', 'assert_that', ({(2907, 20, 2907, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2949, 8, 2949, 30), 'assertpy.assert_that', 'assert_that', ({(2949, 20, 2949, 29): 'errors[0]'}, {}), '(errors[0])', False, 'from assertpy import assert_that\n'), ((2951, 8, 2951, 27), 'assertpy.assert_that', 'assert_that', ({(2951, 20, 2951, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2982, 8, 2982, 27), 'assertpy.assert_that', 'assert_that', ({(2982, 20, 2982, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2984, 8, 2984, 27), 'assertpy.assert_that', 'assert_that', ({(2984, 20, 2984, 26): 'errors'}, {}), '(errors)', False, 'from assertpy import assert_that\n'), ((2987, 8, 2987, 29), 'assertpy.assert_that', 'assert_that', ({(2987, 20, 2987, 28): 'warnings'}, {}), '(warnings)', False, 'from assertpy import assert_that\n'), ((2989, 8, 2989, 29), 'assertpy.assert_that', 'assert_that', ({(2989, 20, 2989, 28): 'warnings'}, {}), '(warnings)', False, 'from assertpy import assert_that\n'), ((1854, 45, 1854, 84), 'pcluster.config.validators.FSX_SUPPORTED_ARCHITECTURES_OSES.keys', 'FSX_SUPPORTED_ARCHITECTURES_OSES.keys', ({}, {}), '()', False, 'from pcluster.config.validators import DCV_MESSAGES, EBS_VOLUME_TYPE_TO_VOLUME_SIZE_BOUNDS, FSX_MESSAGES, FSX_SUPPORTED_ARCHITECTURES_OSES, LOGFILE_LOGGER, architecture_os_validator, check_usage_class, cluster_type_validator, compute_resource_validator, disable_hyperthreading_architecture_validator, efa_gdr_validator, efa_os_arch_validator, fsx_ignored_parameters_validator, instances_architecture_compatibility_validator, intel_hpc_architecture_validator, queue_compute_type_validator, queue_validator, region_validator, s3_bucket_region_validator, settings_validator\n'), ((2586, 16, 2586, 33), 'pcluster.config.mappings.FSX.get', 'FSX.get', ({(2586, 24, 2586, 32): '"""params"""'}, {}), "('params')", False, 'from pcluster.config.mappings import ALLOWED_VALUES, FSX\n')]