diff --git a/changelogs/fragments/1601-ecs_service-support_constraints_and_strategy_update.yml b/changelogs/fragments/1601-ecs_service-support_constraints_and_strategy_update.yml new file mode 100644 index 00000000000..daa58d11ae7 --- /dev/null +++ b/changelogs/fragments/1601-ecs_service-support_constraints_and_strategy_update.yml @@ -0,0 +1,2 @@ +bugfixes: + - ecs_service - respect ``placement_constraints`` for existing ecs services (https://github.com/ansible-collections/community.aws/pull/1601). diff --git a/plugins/modules/ecs_service.py b/plugins/modules/ecs_service.py index e69ad4676f8..c6980c6f2d6 100644 --- a/plugins/modules/ecs_service.py +++ b/plugins/modules/ecs_service.py @@ -819,14 +819,24 @@ def create_service(self, service_name, cluster_name, task_definition, load_balan response = self.ecs.create_service(**params) return self.jsonize(response['service']) - def update_service(self, service_name, cluster_name, task_definition, - desired_count, deployment_configuration, network_configuration, - health_check_grace_period_seconds, force_new_deployment, capacity_provider_strategy): + def update_service(self, service_name, cluster_name, task_definition, desired_count, + deployment_configuration, placement_constraints, placement_strategy, + network_configuration, health_check_grace_period_seconds, + force_new_deployment, capacity_provider_strategy): params = dict( cluster=cluster_name, service=service_name, taskDefinition=task_definition, deploymentConfiguration=deployment_configuration) + # filter placement_constraint and left only those where value is not None + # use-case: `distinctInstance` type should never contain `expression`, but None will fail `str` type validation + if placement_constraints: + params['placementConstraints'] = [{key: value for key, value in constraint.items() if value is not None} + for constraint in placement_constraints] + + if placement_strategy: + params['placementStrategy'] = placement_strategy + if network_configuration: params['networkConfiguration'] = network_configuration if force_new_deployment: @@ -1038,6 +1048,8 @@ def main(): task_definition, module.params['desired_count'], deploymentConfiguration, + module.params['placement_constraints'], + module.params['placement_strategy'], network_configuration, module.params['health_check_grace_period_seconds'], module.params['force_new_deployment'], diff --git a/tests/integration/targets/ecs_cluster/tasks/main.yml b/tests/integration/targets/ecs_cluster/tasks/main.yml index 57a010a5bdb..edeaf7b452e 100644 --- a/tests/integration/targets/ecs_cluster/tasks/main.yml +++ b/tests/integration/targets/ecs_cluster/tasks/main.yml @@ -108,19 +108,21 @@ vpc_id: '{{ setup_vpc.vpc.id }}' rules: # allow all ssh traffic but nothing else - ports: 22 - cidr: 0.0.0.0/0 + cidr_ip: 0.0.0.0/0 register: setup_sg - - name: find a suitable AMI - ec2_ami_info: - owner: amazon - filters: - description: "Amazon Linux AMI* ECS *" - register: ec2_ami_info + - set_fact: + # As a lookup plugin we don't have access to module_defaults + connection_args: + region: "{{ aws_region }}" + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + aws_security_token: "{{ security_token | default(omit) }}" + no_log: True - name: set image id fact set_fact: - ecs_image_id: "{{ (ec2_ami_info.images|last).image_id }}" + ecs_image_id: "{{ lookup('aws_ssm', '/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id', **connection_args) }}" - name: provision ec2 instance to create an image ec2_instance: @@ -146,6 +148,10 @@ modify_targets: no vpc_id: '{{ setup_vpc.vpc.id }}' target_type: instance + health_check_interval: 5 + health_check_timeout: 2 + healthy_threshold_count: 2 + unhealthy_threshold_count: 2 register: elb_target_group_instance - name: create second target group to use ip target_type @@ -157,6 +163,10 @@ modify_targets: no vpc_id: '{{ setup_vpc.vpc.id }}' target_type: ip + health_check_interval: 5 + health_check_timeout: 2 + healthy_threshold_count: 2 + unhealthy_threshold_count: 2 register: elb_target_group_ip - name: create load balancer @@ -598,6 +608,143 @@ that: - ecs_task_definition_constraints_delete is not changed + - name: Create ecs service with placement constraints + ecs_service: + name: "{{ ecs_service_name }}-constraint" + cluster: "{{ ecs_cluster_name }}" + load_balancers: + - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}" + containerName: "{{ ecs_task_name }}" + containerPort: "{{ ecs_task_container_port }}" + task_definition: "{{ ecs_task_name }}:{{ ecs_task_definition.taskdefinition.revision }}" + scheduling_strategy: "REPLICA" + placement_constraints: + - type: distinctInstance + desired_count: 1 + state: present + register: ecs_service_creation_constraints + + - name: Assert ecs service constraint + assert: + that: + - ecs_service_creation_constraints.changed + - "ecs_service_creation_constraints.service.placementConstraints | length == 1" + - "ecs_service_creation_constraints.service.placementConstraints[0].type == distinctInstance" + + - name: Update ecs service's placement constraints + ecs_service: + name: "{{ ecs_service_name }}-constraint" + cluster: "{{ ecs_cluster_name }}" + load_balancers: + - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}" + containerName: "{{ ecs_task_name }}" + containerPort: "{{ ecs_task_container_port }}" + task_definition: "{{ ecs_task_name }}:{{ ecs_task_definition.taskdefinition.revision }}" + scheduling_strategy: "REPLICA" + placement_constraints: + - type: memberOf + expression: 'attribute:ecs.instance-type == t3.micro' + desired_count: 1 + state: present + register: ecs_service_update_constraints + + - name: Assert ecs service constraint + assert: + that: + - ecs_service_update_constraints.changed + - "ecs_service_update_constraints.service.placementConstraints | length == 1" + - "ecs_service_update_constraints.service.placementConstraints[0].type == memberOf" + - "ecs_service_update_constraints.service.placementConstraints[0].expression == 'attribute:ecs.instance-type == t3.micro'" + + - name: Remove ecs service's placement constraints + ecs_service: + name: "{{ ecs_service_name }}-constraint" + cluster: "{{ ecs_cluster_name }}" + load_balancers: + - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}" + containerName: "{{ ecs_task_name }}" + containerPort: "{{ ecs_task_container_port }}" + task_definition: "{{ ecs_task_name }}:{{ ecs_task_definition.taskdefinition.revision }}" + scheduling_strategy: "REPLICA" + desired_count: 1 + state: present + register: ecs_service_remove_constraints + + - name: Assert ecs service constraint + assert: + that: + - ecs_service_remove_constraints.changed + - "ecs_service_remove_constraints.service.placementConstraints | length == 0" + + - name: Create ecs service with placement strategy + ecs_service: + name: "{{ ecs_service_name }}-strategy" + cluster: "{{ ecs_cluster_name }}" + load_balancers: + - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}" + containerName: "{{ ecs_task_name }}" + containerPort: "{{ ecs_task_container_port }}" + task_definition: "{{ ecs_task_name }}:{{ ecs_task_definition.taskdefinition.revision }}" + scheduling_strategy: "REPLICA" + placement_strategy: + - type: binpack + field: MEMORY + desired_count: 1 + state: present + register: ecs_service_creation_strategy + + - name: Assert ecs service strategy + assert: + that: + - ecs_service_creation_strategy.changed + - "ecs_service_creation_strategy.service.placementStrategy | length == 1" + - "ecs_service_creation_strategy.service.placementStrategy[0].type == binpack" + - "ecs_service_creation_strategy.service.placementStrategy[0].field == MEMORY" + + - name: Update ecs service's placement strategy + ecs_service: + name: "{{ ecs_service_name }}-strategy" + cluster: "{{ ecs_cluster_name }}" + load_balancers: + - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}" + containerName: "{{ ecs_task_name }}" + containerPort: "{{ ecs_task_container_port }}" + task_definition: "{{ ecs_task_name }}:{{ ecs_task_definition.taskdefinition.revision }}" + scheduling_strategy: "REPLICA" + placement_strategy: + - type: spread + field: instanceId + desired_count: 1 + state: present + register: ecs_service_update_strategy + + - name: Assert ecs service strategy + assert: + that: + - ecs_service_update_strategy.changed + - "ecs_service_update_strategy.service.placementStrategy | length == 1" + - "ecs_service_update_strategy.service.placementStrategy[0].type == spread" + - "ecs_service_update_strategy.service.placementStrategy[0].field == instanceId" + + - name: Remove ecs service's placement strategy + ecs_service: + name: "{{ ecs_service_name }}-strategy" + cluster: "{{ ecs_cluster_name }}" + load_balancers: + - targetGroupArn: "{{ elb_target_group_instance.target_group_arn }}" + containerName: "{{ ecs_task_name }}" + containerPort: "{{ ecs_task_container_port }}" + task_definition: "{{ ecs_task_name }}:{{ ecs_task_definition.taskdefinition.revision }}" + scheduling_strategy: "REPLICA" + desired_count: 1 + state: present + register: ecs_service_remove_strategy + + - name: Assert ecs service strategy + assert: + that: + - ecs_service_remove_strategy.changed + - "ecs_service_remove_strategy.service.placementStrategy | length == 0" # ============================================================ # Begin tests for Fargate @@ -985,6 +1132,24 @@ wait: yes ignore_errors: yes + - name: remove constraints ecs service + ecs_service: + state: absent + cluster: "{{ ecs_cluster_name }}" + name: "{{ ecs_service_name }}-constraint" + force_deletion: yes + wait: yes + ignore_errors: yes + + - name: remove strategy ecs service + ecs_service: + state: absent + cluster: "{{ ecs_cluster_name }}" + name: "{{ ecs_service_name }}-strategy" + force_deletion: yes + wait: yes + ignore_errors: yes + - name: remove scheduling_strategy ecs service ecs_service: state: absent