def test_validate(self, mocker, attrs, event_in_window, log_stream_prefix, expected_error): log_group_name = "log_group_name" creation_time_mock = 1623061001000 mock_aws_api(mocker) describe_log_group_mock = mocker.patch( "pcluster.aws.logs.LogsClient.describe_log_group", return_value={"creationTime": creation_time_mock}) filter_log_events_mock = mocker.patch( "pcluster.aws.logs.LogsClient.filter_log_events", return_value=event_in_window) export_logs_filters = LogGroupTimeFiltersParser( log_group_name, attrs.get("start_time", None), attrs.get("end_time", None)) if expected_error: with pytest.raises(FiltersParserError, match=expected_error): export_logs_filters.validate(log_stream_prefix) else: export_logs_filters.validate(log_stream_prefix) filter_log_events_mock.assert_called_with( log_group_name, log_stream_prefix, export_logs_filters.start_time, export_logs_filters.end_time, ) if "start_time" not in attrs: describe_log_group_mock.assert_called_with(log_group_name) assert_that(export_logs_filters.start_time).is_equal_to( creation_time_mock)
def test_setup_bucket_with_bucket_configuration_failure( mocker, check_bucket_is_bootstrapped_error, bucket_configure_error, upload_bootstrapped_file_error, cluster_action_error, ): """Verify that create_bucket_with_batch_resources behaves as expected in case of bucket configuration failure.""" mock_aws_api(mocker) # mock bucket initialization mock_bucket(mocker) # mock bucket utils mock_bucket_utils(mocker, configure_bucket_side_effect=bucket_configure_error) cluster = _mock_cluster(mocker, "slurm") # mock bucket object utils mock_bucket_object_utils( mocker, check_bucket_is_bootstrapped_side_effect= check_bucket_is_bootstrapped_error, upload_bootstrapped_file_side_effect=upload_bootstrapped_file_error, ) with pytest.raises(ClusterActionError, match=cluster_action_error): bucket_name = cluster.bucket.name assert_that(bucket_name).is_equal_to( "parallelcluster-a69601b5ee1fc2f2-v1-do-not-delete")
def test_validate_empty_change_set(self, mocker, force): mock_aws_api(mocker) cluster = Cluster( FAKE_NAME, stack=ClusterStack({ "StackName": FAKE_NAME, "CreationTime": "2021-06-04 10:23:20.199000+00:00", "StackStatus": ClusterStatus.CREATE_COMPLETE, }), config=OLD_CONFIGURATION, ) mocker.patch("pcluster.aws.cfn.CfnClient.stack_exists", return_value=True) if force: _, changes, _ = cluster.validate_update_request( target_source_config=OLD_CONFIGURATION, validator_suppressors={AllValidatorsSuppressor()}, force=force, ) assert_that(changes).is_length(1) else: with pytest.raises( BadRequestClusterActionError, match="No changes found in your cluster configuration."): cluster.validate_update_request( target_source_config=OLD_CONFIGURATION, validator_suppressors={AllValidatorsSuppressor()}, force=force, )
def test_persist_stack_resources(self, cluster, mocker, template): """Verify that _persist_stack_resources behaves as expected.""" mocker.patch("pcluster.models.cluster.Cluster._get_artifact_dir") mocker.patch("pcluster.models.cluster.Cluster._get_stack_template", return_value=template) update_stack_template_mock = mocker.patch( "pcluster.models.cluster.Cluster._update_stack_template") mock_aws_api(mocker) mocker.patch("pcluster.aws.cfn.CfnClient.update_stack_from_url") mock_bucket(mocker) mock_bucket_utils(mocker) mock_bucket_object_utils(mocker) if "Resources" not in template: expected_error_message = "Resources" elif "key" not in template.get("Resources"): expected_error_message = "key" else: expected_error_message = None if expected_error_message: with pytest.raises(KeyError, match=expected_error_message): cluster._persist_stack_resources(["key"]) assert_that(update_stack_template_mock.called).is_false() else: cluster._persist_stack_resources(["key"]) assert_that(update_stack_template_mock.called).is_true() assert_that(cluster._get_stack_template()["Resources"]["key"] ["DeletionPolicy"]).is_equal_to("Retain")
def _test_build( mocker, resource, ami_response, ami_side_effect, instance_response, supported_architecture, expected_failure_messages, expected_failure_levels, ): mocker.patch("pcluster.imagebuilder_utils.get_ami_id", return_value="ami-0185634c5a8a37250") mocker.patch( "pcluster.utils.get_supported_architectures_for_instance_type", return_value=supported_architecture) mock_aws_api(mocker) mocker.patch("pcluster.aws.ec2.Ec2Client.describe_image", return_value=ami_response, side_effect=ami_side_effect) mocker.patch("pcluster.aws.ec2.Ec2Client.list_instance_types", return_value=instance_response) build = imagebuilder_factory(resource).get("build") validation_failures = build.validate() for validation_failure, expected_failure_level, expected_failure_message in zip( validation_failures, expected_failure_levels, expected_failure_messages): assert_validation_result(validation_failure, expected_failure_level, expected_failure_message)
def test_persist_cloudwatch_log_groups(self, cluster, mocker, caplog, template, expected_retain, fail_on_persist): """Verify that _persist_cloudwatch_log_groups behaves as expected.""" mocker.patch("pcluster.models.cluster.Cluster._get_artifact_dir") mocker.patch("pcluster.models.cluster.Cluster._get_stack_template", return_value=template) client_error = AWSClientError("function", "Generic error.") update_template_mock = mocker.patch.object( cluster, "_update_stack_template", side_effect=client_error if fail_on_persist else None ) mock_aws_api(mocker) mocker.patch("pcluster.aws.cfn.CfnClient.update_stack_from_url") mock_bucket(mocker) mock_bucket_utils(mocker) mock_bucket_object_utils(mocker) if expected_retain: keys = ["key"] else: keys = [] get_unretained_cw_log_group_resource_keys_mock = mocker.patch.object( cluster, "_get_unretained_cw_log_group_resource_keys", return_value=keys ) if fail_on_persist: with pytest.raises(ClusterActionError) as e: cluster._persist_cloudwatch_log_groups() assert_that(str(e)).contains("Unable to persist logs") else: cluster._persist_cloudwatch_log_groups() assert_that(get_unretained_cw_log_group_resource_keys_mock.call_count).is_equal_to(1) assert_that(update_template_mock.call_count).is_equal_to(1 if expected_retain else 0)
def test_list_log_streams(self, image_builder, mocker, log_group_exists, client_error, expected_error): mock_aws_api(mocker) cw_log_exists_mock = mocker.patch( "pcluster.aws.logs.LogsClient.log_group_exists", return_value=log_group_exists) describe_log_streams_mock = mocker.patch( "pcluster.aws.logs.LogsClient.describe_log_streams", side_effect=AWSClientError("describe_log_streams", "error") if client_error else None, ) if expected_error or not log_group_exists: with pytest.raises(ImageBuilderActionError, match=expected_error): image_builder.list_log_streams() else: # Note: client error for describe_log_streams doesn't raise an exception image_builder.list_log_streams() # check steps cw_log_exists_mock.assert_called() if log_group_exists: describe_log_streams_mock.assert_called() else: describe_log_streams_mock.assert_not_called()
def test_get_log_events(self, image_builder, mocker, log_stream_name, stack_exists, client_error, expected_error): mock_aws_api(mocker) stack_exists_mock = mocker.patch( "pcluster.models.imagebuilder.ImageBuilder._stack_exists", return_value=stack_exists) get_stack_events_mock = mocker.patch( "pcluster.aws.cfn.CfnClient.get_stack_events", side_effect=AWSClientError("get_log_events", "error") if client_error else None, ) get_log_events_mock = mocker.patch( "pcluster.aws.logs.LogsClient.get_log_events", side_effect=AWSClientError("get_log_events", "error") if client_error else None, ) if expected_error or client_error: with pytest.raises(ImageBuilderActionError, match=expected_error): image_builder.get_log_events(log_stream_name) else: image_builder.get_log_events(log_stream_name) if log_stream_name == f"{FAKE_ID}-cfn-events": stack_exists_mock.assert_called() if stack_exists: get_stack_events_mock.assert_called() else: get_stack_events_mock.assert_not_called() else: stack_exists_mock.assert_not_called() get_stack_events_mock.assert_not_called() get_log_events_mock.assert_called()
def test_list_log_streams( self, cluster, mocker, set_env, stack_exists, logging_enabled, client_error, expected_error, ): mock_aws_api(mocker) set_env("AWS_DEFAULT_REGION", "us-east-2") stack_exists_mock = mocker.patch("pcluster.aws.cfn.CfnClient.stack_exists", return_value=stack_exists) describe_logs_mock = mocker.patch( "pcluster.aws.logs.LogsClient.describe_log_streams", side_effect=AWSClientError("describe_log_streams", "error") if client_error else None, ) mocker.patch( "pcluster.models.cluster.Cluster._init_list_logs_filters", return_value=_MockListClusterLogsFiltersParser() ) mocker.patch( "pcluster.models.cluster.ClusterStack.log_group_name", new_callable=PropertyMock(return_value="log-group-name" if logging_enabled else None), ) if expected_error or client_error: with pytest.raises(ClusterActionError, match=expected_error): cluster.list_log_streams() else: cluster.list_log_streams() if logging_enabled: describe_logs_mock.assert_called() # check preliminary steps stack_exists_mock.assert_called_with(cluster.stack_name)
def test_setup_bucket_with_resources_success( mocker, scheduler, cluster_name, expected_config, expected_template, expected_dirs, mock_generated_bucket_name, expected_bucket_name, provided_bucket_name, ): """Verify that create_bucket_with_batch_resources behaves as expected.""" # mock calls for bucket property in cluster object artifact_dir = f"parallelcluster/clusters/{cluster_name}-abc123" mock_aws_api(mocker) # mock bucket initialization mock_bucket(mocker) # mock bucket object utils mock_dict = mock_bucket_object_utils(mocker) upload_config_mock = mock_dict.get("upload_config") upload_template_mock = mock_dict.get("upload_cfn_template") upload_custom_resources_mock = mock_dict.get("upload_resources") # mock bucket utils check_bucket_mock = mock_bucket_utils( mocker, root_service_dir=f"{cluster_name}-abc123")["check_bucket_exists"] if provided_bucket_name: cluster = _mock_cluster(mocker, scheduler, bucket_name=provided_bucket_name, artifact_directory=artifact_dir) cluster.config.custom_s3_bucket = provided_bucket_name else: cluster = _mock_cluster(mocker, scheduler, bucket_name=mock_generated_bucket_name, artifact_directory=artifact_dir) cluster.bucket.upload_config(expected_config, "fake_config_name") cluster.bucket.upload_cfn_template(expected_template, "fake_template_name") for dir in expected_dirs: cluster.bucket.upload_resources(dir) check_bucket_mock.assert_called_with() # assert upload has been called upload_config_mock.assert_called_with(expected_config, "fake_config_name") upload_template_mock.assert_called_with(expected_template, "fake_template_name") upload_custom_resources_mock.assert_has_calls( [mocker.call(dir) for dir in expected_dirs]) # assert bucket properties assert_that(cluster.bucket.name).is_equal_to(expected_bucket_name) assert_that(cluster.bucket.artifact_directory).is_equal_to(artifact_dir) assert_that(cluster.bucket._root_directory).is_equal_to("parallelcluster")
def test_awsbatch_instances_architecture_compatibility_validator( mocker, head_node_architecture, compute_architecture, compute_instance_types, expected_message): def _internal_is_instance_type(itype): return "." in itype mock_aws_api(mocker) supported_architectures_patch = mocker.patch( "pcluster.aws.ec2.Ec2Client.get_supported_architectures", return_value=[compute_architecture]) is_instance_type_patch = mocker.patch( "pcluster.validators.awsbatch_validators.AwsBatchInstancesArchitectureCompatibilityValidator." "_is_instance_type_format", side_effect=_internal_is_instance_type, ) actual_failures = AwsBatchInstancesArchitectureCompatibilityValidator( ).execute(compute_instance_types, head_node_architecture) assert_failure_messages(actual_failures, expected_message) if expected_message: assert_that(len(actual_failures)).is_equal_to( len(compute_instance_types)) 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(is_instance_type_patch.call_count).is_equal_to( len(compute_instance_types))
def test_role_validator(mocker, role_arn, side_effect, expected_message): mock_aws_api(mocker) mocker.patch("pcluster.aws.iam.IamClient.get_role", side_effect=side_effect) actual_failures = RoleValidator().execute(role_arn=role_arn) assert_failure_messages(actual_failures, expected_message)
def test_imagebuilder_schema(mocker, test_datadir, config_file_name, response): mock_aws_api(mocker) mocker.patch("pcluster.imagebuilder_utils.get_ami_id", return_value="ami-0185634c5a8a37250") mocker.patch( "pcluster.aws.ec2.Ec2Client.describe_image", return_value=response, ) # Load imagebuilder model from Yaml file input_yaml = load_yaml_dict(test_datadir / config_file_name) print(input_yaml) imagebuilder_config = ImageBuilderSchema().load(input_yaml) print(imagebuilder_config) # Re-create Yaml file from model and compare content image_builder_schema = ImageBuilderSchema() image_builder_schema.context = {"delete_defaults_when_dump": True} output_json = image_builder_schema.dump(imagebuilder_config) # Assert imagebuilder config file can be convert to imagebuilder config assert_that(json.dumps(input_yaml, sort_keys=True)).is_equal_to( json.dumps(output_json, sort_keys=True)) # Print output yaml output_yaml = yaml.dump(output_json) print(output_yaml)
def _mock_aws_api_required_calls(mocker): supported_instance_types = [ "t2.nano", "t2.micro", "t2.large", "c5.xlarge", "g3.8xlarge", "m6g.xlarge", "p4d.24xlarge", ] mock_aws_api(mocker, mock_instance_type_info=False) mocker.patch("pcluster.aws.ec2.Ec2Client.get_default_instance_type", return_value="t2.micro") mocker.patch("pcluster.aws.ec2.Ec2Client.list_instance_types", return_value=supported_instance_types) mocker.patch("pcluster.aws.ec2.Ec2Client.get_subnet_avail_zone", return_value="mocked_avail_zone") mocker.patch( "pcluster.aws.ec2.Ec2Client.get_instance_type_info", side_effect=[ InstanceTypeInfo( { "InstanceType": instance_type, "VCpuInfo": {"DefaultVCpus": 4, "DefaultCores": 2}, "NetworkInfo": {"EfaSupported": False}, } ) for instance_type in supported_instance_types ], )
def test_instance_type_validator(mocker, instance_type, expected_message): mock_aws_api(mocker) mocker.patch("pcluster.aws.ec2.Ec2Client.list_instance_types", return_value=["t2.micro", "c4.xlarge"]) actual_failures = InstanceTypeValidator().execute(instance_type) assert_failure_messages(actual_failures, expected_message)
def test_key_pair_validator(mocker, key_pair, side_effect, expected_message): mock_aws_api(mocker) mocker.patch("pcluster.aws.ec2.Ec2Client.describe_key_pair", return_value=key_pair, side_effect=side_effect) actual_failures = KeyPairValidator().execute(key_name=key_pair) assert_failure_messages(actual_failures, expected_message)
def test_cw_dashboard_builder(mocker, test_datadir, config_file_name): mock_aws_api(mocker) mocker.patch( "pcluster.config.cluster_config.HeadNodeNetworking.availability_zone", new_callable=PropertyMock(return_value="us-east-1a"), ) # mock bucket initialization parameters mock_bucket(mocker) input_yaml = load_yaml_dict(test_datadir / config_file_name) cluster_config = ClusterSchema(cluster_name="clustername").load(input_yaml) print(cluster_config) generated_template = CDKTemplateBuilder().build_cluster_template( cluster_config=cluster_config, bucket=dummy_cluster_bucket(), stack_name="clustername") output_yaml = yaml.dump(generated_template, width=float("inf")) print(output_yaml) if cluster_config.is_cw_dashboard_enabled: if cluster_config.shared_storage: _verify_ec2_metrics_conditions(cluster_config, output_yaml) if cluster_config.is_cw_logging_enabled: _verify_head_node_logs_conditions(cluster_config, output_yaml) else: assert_that(output_yaml).does_not_contain("Head Node Logs")
def test_image_exists(mocker, response, is_error): """Verify that EC2Client.image_exists behaves as expected.""" should_exist = not is_error mock_aws_api(mocker) mocker.patch("pcluster.aws.ec2.Ec2Client.describe_images", side_effect=response) assert_that(_DummyAWSApi().instance().ec2.image_exists( FAKE_IMAGE_ID)).is_equal_to(should_exist)
def test_delete_with_ec2_error(mocker, error, returned_error): with pytest.raises(returned_error): mock_aws_api(mocker) mocker.patch("pcluster.aws.cfn.CfnClient.stack_exists", return_value=False) mocker.patch("pcluster.aws.ec2.Ec2Client.image_exists", side_effect=(error)) ImageBuilder("imageId").delete(force=True)
def test_compute_ami_os_compatible_validator(mocker, image_id, os, ami_info, expected_message): mock_aws_api(mocker) mocker.patch("pcluster.aws.ec2.Ec2Client.describe_image", return_value=ami_info) actual_failures = ComputeAmiOsCompatibleValidator().execute( image_id=image_id, os=os) assert_failure_messages(actual_failures, expected_message)
def test_head_node_root_volume_schema(mocker, config_dict, failure_message): mock_aws_api(mocker) if failure_message: with pytest.raises(ValidationError, match=failure_message): HeadNodeRootVolumeSchema().load(config_dict) else: HeadNodeRootVolumeSchema().load(config_dict)
def test_additional_iam_policy_validator(mocker, policy_arn, expected_get_policy_side_effect, expected_message): mock_aws_api(mocker) mocker.patch("pcluster.aws.iam.IamClient.get_policy", side_effect=expected_get_policy_side_effect) actual_failures = AdditionalIamPolicyValidator().execute(policy=policy_arn) assert_failure_messages(actual_failures, expected_message)
def test_shared_storage_schema(mocker, config_dict, failure_message): mock_aws_api(mocker) if failure_message: with pytest.raises(ValidationError, match=failure_message): SharedStorageSchema().load(config_dict) else: SharedStorageSchema().load(config_dict)
def test_stack_exists(mocker, response, is_error): """Verify that CfnClient.stack_exists behaves as expected.""" should_exist = not is_error mock_aws_api(mocker) mocker.patch("pcluster.aws.cfn.CfnClient.describe_stack", side_effect=response) assert_that(_DummyAWSApi().instance().cfn.stack_exists( FAKE_STACK_NAME)).is_equal_to(should_exist)
def test_scheduling_schema(mocker, config_dict, failure_message): mock_aws_api(mocker) if failure_message: with pytest.raises(ValidationError, match=failure_message): SchedulingSchema().load(config_dict) else: SchedulingSchema().load(config_dict)
def test_instance_architecture_compatibility_validator( mocker, head_node_architecture, compute_architecture, compute_instance_type, expected_message ): mock_aws_api(mocker) mocker.patch("pcluster.aws.ec2.Ec2Client.get_supported_architectures", return_value=[compute_architecture]) actual_failures = InstanceArchitectureCompatibilityValidator().execute( compute_instance_type, head_node_architecture ) assert_failure_messages(actual_failures, expected_message)
def test_instance_profile_validator(mocker, instance_profile_arn, side_effect, expected_message): mock_aws_api(mocker) mocker.patch("pcluster.aws.iam.IamClient.get_instance_profile", side_effect=side_effect) actual_failures = InstanceProfileValidator().execute( instance_profile_arn=instance_profile_arn) assert_failure_messages(actual_failures, expected_message)
def test_cluster_builder_from_configuration_file(mocker, config_file_name): mock_aws_api(mocker) # mock bucket initialization parameters mock_bucket(mocker) input_yaml, cluster = load_cluster_model_from_yaml(config_file_name) generated_template = CDKTemplateBuilder().build_cluster_template( cluster_config=cluster, bucket=dummy_cluster_bucket(), stack_name="clustername" ) print(yaml.dump(generated_template))
def test_export_logs( self, mocker, image_builder, stack_exists, log_group_exists, expected_error, kwargs, ): mock_aws_api(mocker) stack_exists_mock = mocker.patch( "pcluster.models.imagebuilder.ImageBuilder._stack_exists", return_value=stack_exists) mocker.patch("pcluster.aws.logs.LogsClient.log_group_exists", return_value=log_group_exists) download_stack_events_mock = mocker.patch( "pcluster.models.imagebuilder.export_stack_events") create_logs_archive_mock = mocker.patch( "pcluster.models.imagebuilder.create_logs_archive") upload_archive_mock = mocker.patch( "pcluster.models.imagebuilder.upload_archive") presign_mock = mocker.patch( "pcluster.models.imagebuilder.create_s3_presigned_url") # Following mocks are used only if CW loggins is enabled logs_filter_mock = mocker.patch( "pcluster.models.imagebuilder.ImageBuilder._init_export_logs_filters", return_value=_MockExportImageLogsFiltersParser(), ) cw_logs_exporter_mock = mocker.patch( "pcluster.models.imagebuilder.CloudWatchLogsExporter", autospec=True) kwargs.update({"bucket": "bucket_name"}) if expected_error: with pytest.raises(ImageBuilderActionError, match=expected_error): image_builder.export_logs(**kwargs) else: image_builder.export_logs(**kwargs) stack_exists_mock.assert_called() if stack_exists: download_stack_events_mock.assert_called() else: download_stack_events_mock.assert_not_called() if log_group_exists: cw_logs_exporter_mock.assert_called() logs_filter_mock.assert_called() else: cw_logs_exporter_mock.assert_not_called() logs_filter_mock.assert_not_called() create_logs_archive_mock.assert_called() if "output_file" not in kwargs: upload_archive_mock.assert_called() presign_mock.assert_called()
def test_awsbatch_cluster_builder(mocker): mock_aws_api(mocker) # mock bucket initialization parameters mock_bucket(mocker) generated_template = CDKTemplateBuilder().build_cluster_template( cluster_config=dummy_awsbatch_cluster_config(mocker), bucket=dummy_cluster_bucket(), stack_name="clustername") print(yaml.dump(generated_template))