From 01409a03cd1773ec923e36d2760c1e8ae046827c Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Tue, 8 Feb 2022 14:30:16 -0800 Subject: [PATCH 01/12] Add functionality to detach specified instances from ASG --- plugins/modules/ec2_asg.py | 68 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 46cdcbf15b8..0c1ddf08f8b 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -182,6 +182,19 @@ matching the current launch configuration. type: list elements: str + detach_instances: + description: + - Removes one or more instances from the specified AutoScalingGroup. + - If I(decrement_desired_capacity) flag is not set, new instance(s) are lauched to replace the detached instance(s). + - If a Classic Load Balancer attached to the AutoScalingGroup, the instances are also deregistered from the load balancer. + - If there are target groups attached to the AutoScalingGroup, the instances are also deregistered from the target groups. + type: list + elements: str + decrement_desired_capacity: + description: + - Indicates whether the AutoScalingGroup decrements the desired capacity value by the number of instances detached. + default: false + type: bool lc_check: description: - Check to make sure instances that are being replaced with I(replace_instances) do not already have the current I(launch_config). @@ -756,6 +769,12 @@ def terminate_asg_instance(connection, instance_id, decrement_capacity): ShouldDecrementDesiredCapacity=decrement_capacity) +@AWSRetry.jittered_backoff(**backoff_params) +def detach_asg_instances(connection, instance_ids, as_group_name, decrement_capacity): + connection.detach_instances(InstanceIds=instance_ids, AutoScalingGroupName=as_group_name, + ShouldDecrementDesiredCapacity=decrement_capacity) + + def enforce_required_arguments_for_create(): ''' As many arguments are not required for autoscale group deletion they cannot be mandatory arguments for the module, so we enforce @@ -1523,6 +1542,37 @@ def replace(connection): return changed, asg_properties +def detach(connection): + group_name = module.params.get('name') + detach_instances = module.params.get('detach_instances') + as_group = describe_autoscaling_groups(connection, group_name)[0] + decrement_desired_capacity = module.params.get('decrement_desired_capacity') + min_size = module.params.get('min_size') + props = get_properties(as_group) + instances = props['instances'] + + # check if provided instance exists in asg + for instance_id in detach_instances: + if instance_id not in instances: + module.fail_json(msg="Provided instance ID: {0} does not belong to the AutoScalingGroup: {1}".format( + instance_id, group_name)) + + # check if minimum size is greater than what setting decrement_desired_capacity would make it + if min_size and min_size > len(instances) - len(detach_instances) and decrement_desired_capacity: + module.fail_json( + msg="Detaching instance(s) with 'decrement_desired_capacity' flag set reduces number of instances below min_size, \ + please update AutoScalingGroup Sizes properly.") + + if detach_instances: + try: + detach_asg_instances(connection, detach_instances, group_name, decrement_desired_capacity) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to describe launch configurations") + + asg_properties = get_properties(as_group) + return True, asg_properties + + def get_instances_by_launch_config(props, lc_check, initial_instances): new_instances = [] old_instances = [] @@ -1776,6 +1826,8 @@ def main(): replace_batch_size=dict(type='int', default=1), replace_all_instances=dict(type='bool', default=False), replace_instances=dict(type='list', default=[], elements='str'), + detach_instances=dict(type='list', default=[], elements='str'), + decrement_desired_capacity=dict(type='bool', default=False), lc_check=dict(type='bool', default=True), lt_check=dict(type='bool', default=True), wait_timeout=dict(type='int', default=300), @@ -1821,16 +1873,18 @@ def main(): argument_spec=argument_spec, mutually_exclusive=[ ['replace_all_instances', 'replace_instances'], - ['launch_config_name', 'launch_template'] + ['replace_all_instances', 'detach_instances'], + ['launch_config_name', 'launch_template'], ] ) state = module.params.get('state') replace_instances = module.params.get('replace_instances') replace_all_instances = module.params.get('replace_all_instances') + detach_instances = module.params.get('detach_instances') connection = module.client('autoscaling') - changed = create_changed = replace_changed = False + changed = create_changed = replace_changed = detach_changed = False exists = asg_exists(connection) if state == 'present': @@ -1847,7 +1901,15 @@ def main(): ): replace_changed, asg_properties = replace(connection) - if create_changed or replace_changed: + # Only detach instances if asg existed at start of call + if ( + exists + and (detach_instances) + and (module.params.get('launch_config_name') or module.params.get('launch_template')) + ): + detach_changed, asg_properties = detach(connection) + + if create_changed or replace_changed or detach_changed: changed = True module.exit_json(changed=changed, **asg_properties) From 19bca4beef9cb3faad2916cc4b963f08751b5844 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Tue, 15 Feb 2022 12:44:47 -0800 Subject: [PATCH 02/12] Modified based on feedback --- plugins/modules/ec2_asg.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 0c1ddf08f8b..504680b367f 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -190,11 +190,13 @@ - If there are target groups attached to the AutoScalingGroup, the instances are also deregistered from the target groups. type: list elements: str + version_added: 3.2.0 decrement_desired_capacity: description: - Indicates whether the AutoScalingGroup decrements the desired capacity value by the number of instances detached. default: false type: bool + version_added: 3.2.0 lc_check: description: - Check to make sure instances that are being replaced with I(replace_instances) do not already have the current I(launch_config). @@ -1557,11 +1559,14 @@ def detach(connection): module.fail_json(msg="Provided instance ID: {0} does not belong to the AutoScalingGroup: {1}".format( instance_id, group_name)) - # check if minimum size is greater than what setting decrement_desired_capacity would make it - if min_size and min_size > len(instances) - len(detach_instances) and decrement_desired_capacity: - module.fail_json( - msg="Detaching instance(s) with 'decrement_desired_capacity' flag set reduces number of instances below min_size, \ - please update AutoScalingGroup Sizes properly.") + # check if setting decrement_desired_capacity will make desired_capacity smaller + # than the currently set minimum size in ASG configuration + if decrement_desired_capacity: + decremented_desired_capacity = len(instances) - len(detach_instances) + if min_size and min_size > decremented_desired_capacity: + module.fail_json( + msg="Detaching instance(s) with 'decrement_desired_capacity' flag set reduces number of instances below min_size,\ + please update AutoScalingGroup Sizes properly.") if detach_instances: try: From 459af5e5490e698b982431364744a4aaa9ee8638 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Wed, 16 Feb 2022 12:18:36 -0800 Subject: [PATCH 03/12] WIP: adding integration tests for detach instances feature --- .../targets/ec2_asg/tasks/instance_detach.yml | 206 ++++++++++++++++++ .../targets/ec2_asg/tasks/main.yml | 7 +- 2 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 tests/integration/targets/ec2_asg/tasks/instance_detach.yml diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml new file mode 100644 index 00000000000..9b82ef2acf2 --- /dev/null +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -0,0 +1,206 @@ +- name: Running instance detach tests + block: + #------------------------------------------------------------------------------------------------------------ + - name: create a launch configuration + ec2_lc: + name: "{{ resource_prefix }}-lc-detach-test" + image_id: "{{ ec2_ami_image }}" + region: "{{ aws_region }}" + instance_type: t2.micro + assign_public_ip: yes + + #------------------------------------------------------------------------------------------------------------ + - name: create a AutoScalingGroup + ec2_asg: + name: "{{ resource_prefix }}-asg-detach-test" + launch_config_name: "{{ resource_prefix }}-lc-detach-test" + health_check_period: 60 + health_check_type: ELB + replace_all_instances: yes + min_size: 3 + max_size: 6 + desired_capacity: 3 + region: "{{ aws_region }}" + + # gather info about asg and get instance ids and instance count + - ec2_asg_info: + name: "{{ resource_prefix }}-asg-detach-test" + register: asg_info + # create a list of instance ids from info result + - set_fact: + instances: "{{ asg_info.results[0].instances | map(attribute='instance_id') | list }}" + - set_fact: + instance_0: "{{ instances[0] }}" + instance_1: "{{ instances[1] }}" + instance_2: "{{ instances[2] }}" + + - name: Gather information about instance 0 + amazon.aws.ec2_instance_info: + instance_ids: + - "{{ instance_0 }}" + register: instance_0_info + - name: Gather information about instance 1 + amazon.aws.ec2_instance_info: + instance_ids: + - "{{ instance_1 }}" + register: instance_1_info + - name: Gather information about instance 2 + amazon.aws.ec2_instance_info: + instance_ids: + - "{{ instance_2 }}" + register: instance_2_info + + # assert that there are 3 instances in the AutoScalingGroup + - assert: + that: + - "{{ instances | length }} == 3" + - "'{{ instance_0_info.instances[0].state.name }}' == 'running'" + - "'{{ instance_1_info.instances[0].state.name }}' == 'running'" + - "'{{ instance_2_info.instances[0].state.name }}' == 'running'" + + #------------------------------------------------------------------------------------------------------------ + + - name: detach 2 instance from the asg and replace with other instances + ec2_asg: + name: "{{ resource_prefix }}-asg-detach-test" + launch_config_name: "{{ resource_prefix }}-lc-detach-test" + health_check_period: 60 + health_check_type: ELB + min_size: 3 + max_size: 3 + desired_capacity: 3 + region: "{{ aws_region }}" + detach_instances: + - '{{ instances[0] }}' + - '{{ instances[1] }}' + + # pause to allow completion of instance replacement + - name: Pause for 1 minute + pause: + minutes: 1 + + # gather info about asg and make sure the instances are detached + # also make sure the instances are replaced and the count is maintained + - ec2_asg_info: + name: "{{ resource_prefix }}-asg-detach-test" + register: asg_info + # create a list of instance ids from info result + - set_fact: + instances_detach_replace: "{{ asg_info.results[0].instances | map(attribute='instance_id') | list }}" + - set_fact: + instance_4: "{{ instances_detach_replace[0] }}" + instance_5: "{{ instances_detach_replace[1] }}" + instance_6: "{{ instances_detach_replace[2] }}" + + - name: Gather information about instance 0 + amazon.aws.ec2_instance_info: + instance_ids: + - "{{ instance_0 }}" + register: instance_0_info + - name: Gather information about instance 1 + amazon.aws.ec2_instance_info: + instance_ids: + - "{{ instance_1 }}" + register: instance_1_info + + # assert that there are 3 still instances in the AutoScalingGroup + # assert that two specified instances are detached and still running (not terminated) + - assert: + that: + - "{{ instances_detach_replace | length }} == 3" + - "'{{ instance_0 }}' not in {{ instances_detach_replace }}" + - "'{{ instance_1 }}' not in {{ instances_detach_replace }}" + - "'{{ instance_0_info.instances[0].state.name }}' == 'running'" + - "'{{ instance_1_info.instances[0].state.name }}' == 'running'" + + #------------------------------------------------------------------------------------------------------------ + + # detach 2 instances from the asg and reduce the desired capacity from 3 to 1 + - name: detach 2 instance from the asg and reduce the desired capacity from 3 to 1 + ec2_asg: + name: "{{ resource_prefix }}-asg-detach-test" + launch_config_name: "{{ resource_prefix }}-lc-detach-test" + health_check_period: 60 + health_check_type: ELB + min_size: 1 + max_size: 5 + desired_capacity: 3 + region: "{{ aws_region }}" + decrement_desired_capacity: true + detach_instances: + - '{{ instances_detach_replace[0] }}' + - '{{ instances_detach_replace[1] }}' + + # pause to allow completion of instance detach and decrement desired capacity + - name: Pause for 1 minute + pause: + minutes: 1 + + # gather info about asg and make sure the instances are detached + # also make sure the instances are not replaced and the count and desired_capacity is reduced + - ec2_asg_info: + name: "{{ resource_prefix }}-asg-detach-test" + register: asg_info_decrement + # create a list of instance ids from info result + - set_fact: + instances_detach_no_replace: "{{ asg_info_decrement.results[0].instances | map(attribute='instance_id') | list }}" + - set_fact: + instance_7: "{{ instances_detach_no_replace[0] }}" + + - name: Gather information about instance 4 + amazon.aws.ec2_instance_info: + instance_ids: + - "{{ instance_4 }}" + register: instance_4_info + - name: Gather information about instance 5 + amazon.aws.ec2_instance_info: + instance_ids: + - "{{ instance_5 }}" + register: instance_5_info + + # assert that there are 3 instances in the AutoScalingGroup + - assert: + that: + - "{{ instances_detach_no_replace | length }} == 1" + - "'{{ instance_4 }}' not in {{ instances_detach_no_replace }}" + - "'{{ instance_5 }}' not in {{ instances_detach_no_replace }}" + - "'{{ instance_4_info.instances[0].state.name }}' == 'running'" + - "'{{ instance_5_info.instances[0].state.name }}' == 'running'" + - "'{{ instance_6 }}' in {{ instances_detach_no_replace }}" + + #------------------------------------------------------------------------------------------------------------ + + always: + + - name: terminate any instances created during this test + amazon.aws.ec2_instance: + instance_ids: + - "{{ instance_0 }}" + - "{{ instance_1 }}" + - "{{ instance_2 }}" + - "{{ instance_4 }}" + - "{{ instance_5 }}" + - "{{ instance_6 }}" + - "{{ instance_7 }}" + state: absent + register: terminate_instances + + # - debug: msg="{{ terminate_instances }}" + + - name: kill asg created in this test + ec2_asg: + name: "{{ resource_prefix }}-asg-detach-test" + state: absent + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + + - name: remove launch config created in this test + ec2_lc: + name: "{{ resource_prefix }}-lc-detach-test" + state: absent + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 \ No newline at end of file diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index 800c167bde8..8147941b2ed 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -30,9 +30,10 @@ aws_secret_key: "{{ aws_secret_key }}" security_token: "{{ security_token | default(omit) }}" region: "{{ aws_region }}" - collections: - amazon.aws + vars: + instance_ids: [] block: @@ -117,6 +118,8 @@ - "{{ resource_prefix }}-lc" - "{{ resource_prefix }}-lc-2" + - include_tasks: instance_detach.yml + # ============================================================ - name: launch asg and wait for instances to be deemed healthy (no ELB) @@ -724,6 +727,8 @@ - "output.target_group_arns[0] == out_tg1.target_group_arn" - "output.changed == false" + + # ============================================================ always: From 8d35f2cf30ec7c957feadafc199a699473581983 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Fri, 18 Feb 2022 19:03:38 -0800 Subject: [PATCH 04/12] WIP: adding integration tests for detach instances feature --- .../targets/ec2_asg/tasks/instance_detach.yml | 93 +- .../targets/ec2_asg/tasks/main.yml | 1213 ++++++++--------- 2 files changed, 657 insertions(+), 649 deletions(-) diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml index 9b82ef2acf2..1c1d1e7fb57 100644 --- a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -8,6 +8,14 @@ region: "{{ aws_region }}" instance_type: t2.micro assign_public_ip: yes + register: create_lc + + - name: ensure that lc is created + assert: + that: + - create_lc is changed + - create_lc.failed is false + - '"autoscaling:CreateLaunchConfiguration" in create_lc.resource_actions' #------------------------------------------------------------------------------------------------------------ - name: create a AutoScalingGroup @@ -21,18 +29,25 @@ max_size: 6 desired_capacity: 3 region: "{{ aws_region }}" + register: create_asg + + - name: ensure that AutoScalingGroup is created + assert: + that: + - create_asg is changed + - create_asg.failed is false + - create_asg.instances | length == 3 + - create_asg.desired_capacity == 3 + - '"autoscaling:CreateAutoScalingGroup" in create_asg.resource_actions' - # gather info about asg and get instance ids and instance count + # gather info about asg, get instance ids - ec2_asg_info: name: "{{ resource_prefix }}-asg-detach-test" register: asg_info - # create a list of instance ids from info result - - set_fact: - instances: "{{ asg_info.results[0].instances | map(attribute='instance_id') | list }}" - set_fact: - instance_0: "{{ instances[0] }}" - instance_1: "{{ instances[1] }}" - instance_2: "{{ instances[2] }}" + instance_0: "{{ asg_info.results[0].instances[0].instance_id }}" + instance_1: "{{ asg_info.results[0].instances[1].instance_id }}" + instance_2: "{{ asg_info.results[0].instances[2].instance_id }}" - name: Gather information about instance 0 amazon.aws.ec2_instance_info: @@ -83,35 +98,33 @@ # also make sure the instances are replaced and the count is maintained - ec2_asg_info: name: "{{ resource_prefix }}-asg-detach-test" - register: asg_info + register: asg_info_replaced # create a list of instance ids from info result - set_fact: - instances_detach_replace: "{{ asg_info.results[0].instances | map(attribute='instance_id') | list }}" + instances_detach_replace: "{{ asg_info_replaced.results[0].instances | map(attribute='instance_id') | list }}" - set_fact: instance_4: "{{ instances_detach_replace[0] }}" instance_5: "{{ instances_detach_replace[1] }}" instance_6: "{{ instances_detach_replace[2] }}" - - name: Gather information about instance 0 + - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: instance_ids: - "{{ instance_0 }}" - register: instance_0_info - - name: Gather information about instance 1 - amazon.aws.ec2_instance_info: - instance_ids: - "{{ instance_1 }}" - register: instance_1_info + register: detached_instances_info # assert that there are 3 still instances in the AutoScalingGroup # assert that two specified instances are detached and still running (not terminated) - assert: that: + # - asg_info_replaced.desired_capacity == 1 + # - asg_info_replaced.instances | length == 1 - "{{ instances_detach_replace | length }} == 3" - "'{{ instance_0 }}' not in {{ instances_detach_replace }}" - "'{{ instance_1 }}' not in {{ instances_detach_replace }}" - - "'{{ instance_0_info.instances[0].state.name }}' == 'running'" - - "'{{ instance_1_info.instances[0].state.name }}' == 'running'" + - "'{{ detached_instances_info.instances[0].state.name }}' == 'running'" + - "'{{ detached_instances_info.instances[1].state.name }}' == 'running'" #------------------------------------------------------------------------------------------------------------ @@ -131,41 +144,38 @@ - '{{ instances_detach_replace[0] }}' - '{{ instances_detach_replace[1] }}' - # pause to allow completion of instance detach and decrement desired capacity - - name: Pause for 1 minute + - name: Pause for 1 minute to allow completion of above task pause: minutes: 1 - # gather info about asg and make sure the instances are detached - # also make sure the instances are not replaced and the count and desired_capacity is reduced + # gather information about asg - ec2_asg_info: name: "{{ resource_prefix }}-asg-detach-test" register: asg_info_decrement - # create a list of instance ids from info result + # create a list of instance ids from info result and set variable value to instance ID - set_fact: instances_detach_no_replace: "{{ asg_info_decrement.results[0].instances | map(attribute='instance_id') | list }}" - set_fact: instance_7: "{{ instances_detach_no_replace[0] }}" - - name: Gather information about instance 4 + - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: instance_ids: - "{{ instance_4 }}" - register: instance_4_info - - name: Gather information about instance 5 - amazon.aws.ec2_instance_info: - instance_ids: - "{{ instance_5 }}" - register: instance_5_info + register: detached_instances_info - # assert that there are 3 instances in the AutoScalingGroup + # assert that + # detached instances are not replaced and there is only 1 instance in the AutoScalingGroup + # desired capacity is reduced to 1 - assert: that: - - "{{ instances_detach_no_replace | length }} == 1" + - asg_info_decrement.results[0].desired_capacity == 1 + - asg_info_decrement.results[0].instances | length == 1 - "'{{ instance_4 }}' not in {{ instances_detach_no_replace }}" - "'{{ instance_5 }}' not in {{ instances_detach_no_replace }}" - - "'{{ instance_4_info.instances[0].state.name }}' == 'running'" - - "'{{ instance_5_info.instances[0].state.name }}' == 'running'" + - "'{{ detached_instances_info.instances[0].state.name }}' == 'running'" + - "'{{ detached_instances_info.instances[1].state.name }}' == 'running'" - "'{{ instance_6 }}' in {{ instances_detach_no_replace }}" #------------------------------------------------------------------------------------------------------------ @@ -175,17 +185,16 @@ - name: terminate any instances created during this test amazon.aws.ec2_instance: instance_ids: - - "{{ instance_0 }}" - - "{{ instance_1 }}" - - "{{ instance_2 }}" - - "{{ instance_4 }}" - - "{{ instance_5 }}" - - "{{ instance_6 }}" - - "{{ instance_7 }}" + -"{{ item }}" state: absent - register: terminate_instances - - # - debug: msg="{{ terminate_instances }}" + loop: + - "{{ instance_0 }}" + - "{{ instance_1 }}" + - "{{ instance_2 }}" + - "{{ instance_4 }}" + - "{{ instance_5 }}" + - "{{ instance_6 }}" + - "{{ instance_7 }}" - name: kill asg created in this test ec2_asg: diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index 8147941b2ed..b82553dfd43 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -120,614 +120,613 @@ - include_tasks: instance_detach.yml - # ============================================================ - - - name: launch asg and wait for instances to be deemed healthy (no ELB) - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - desired_capacity: 1 - min_size: 1 - max_size: 1 - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - state: present - wait_for_instances: yes - register: output - - - assert: - that: - - "output.viable_instances == 1" - - - name: Tag asg - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_a: 'value 1' - propagate_at_launch: no - - tag_b: 'value 2' - propagate_at_launch: yes - register: output - - - assert: - that: - - "output.tags | length == 2" - - output is changed - - - name: Re-Tag asg (different order) - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_b: 'value 2' - propagate_at_launch: yes - - tag_a: 'value 1' - propagate_at_launch: no - register: output - - - assert: - that: - - "output.tags | length == 2" - - output is not changed - - - name: Re-Tag asg new tags - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_c: 'value 3' - propagate_at_launch: no - register: output - - - assert: - that: - - "output.tags | length == 1" - - output is changed - - - name: Re-Tag asg update propagate_at_launch - ec2_asg: - name: "{{ resource_prefix }}-asg" - tags: - - tag_c: 'value 3' - propagate_at_launch: yes - register: output - - - assert: - that: - - "output.tags | length == 1" - - output is changed - - - name: Enable metrics collection - ec2_asg: - name: "{{ resource_prefix }}-asg" - metrics_collection: yes - register: output - - - assert: - that: - - output is changed - - - name: Enable metrics collection (check idempotency) - ec2_asg: - name: "{{ resource_prefix }}-asg" - metrics_collection: yes - register: output - - - assert: - that: - - output is not changed - - - name: Disable metrics collection - ec2_asg: - name: "{{ resource_prefix }}-asg" - metrics_collection: no - register: output - - - assert: - that: - - output is changed - - - name: Disable metrics collection (check idempotency) - ec2_asg: - name: "{{ resource_prefix }}-asg" - metrics_collection: no - register: output - - - assert: - that: - - output is not changed - - - name: kill asg - ec2_asg: - name: "{{ resource_prefix }}-asg" - state: absent - wait_timeout: 800 - async: 400 - - # ============================================================ - - - name: launch asg and do not wait for instances to be deemed healthy (no ELB) - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - desired_capacity: 1 - min_size: 1 - max_size: 1 - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - wait_for_instances: no - state: present - register: output - - - assert: - that: - - "output.viable_instances == 0" - - - name: kill asg - ec2_asg: - name: "{{ resource_prefix }}-asg" - state: absent - wait_timeout: 800 - register: output - retries: 3 - until: output is succeeded - delay: 10 - async: 400 - - # ============================================================ - - - name: create asg with asg metrics enabled - ec2_asg: - name: "{{ resource_prefix }}-asg" - metrics_collection: true - launch_config_name: "{{ resource_prefix }}-lc" - desired_capacity: 0 - min_size: 0 - max_size: 0 - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - state: present - register: output - - - assert: - that: - - "'Group' in output.metrics_collection.0.Metric" - - - name: kill asg - ec2_asg: - name: "{{ resource_prefix }}-asg" - state: absent - wait_timeout: 800 - async: 400 - - # ============================================================ - - - name: launch load balancer - ec2_elb_lb: - name: "{{ load_balancer_name }}" - state: present - security_group_ids: - - "{{ sg.group_id }}" - subnets: "{{ testing_subnet.subnet.id }}" - connection_draining_timeout: 60 - listeners: - - protocol: http - load_balancer_port: 80 - instance_port: 80 - health_check: - ping_protocol: tcp - ping_port: 80 - ping_path: "/" - response_timeout: 5 - interval: 10 - unhealthy_threshold: 4 - healthy_threshold: 2 - register: load_balancer - - - name: launch asg and wait for instances to be deemed healthy (ELB) - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - health_check_type: ELB - desired_capacity: 1 - min_size: 1 - max_size: 1 - health_check_period: 300 - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - load_balancers: "{{ load_balancer_name }}" - wait_for_instances: yes - wait_timeout: 900 - state: present - register: output - - - assert: - that: - - "output.viable_instances == 1" - - # ============================================================ - - # grow scaling group to 3 - - name: add 2 more instances wait for instances to be deemed healthy (ELB) - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - health_check_type: ELB - desired_capacity: 3 - min_size: 3 - max_size: 5 - health_check_period: 600 - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - load_balancers: "{{ load_balancer_name }}" - wait_for_instances: yes - wait_timeout: 1200 - state: present - register: output - - - assert: - that: - - "output.viable_instances == 3" - - # ============================================================ - - # Test max_instance_lifetime option - - name: enable asg max_instance_lifetime - ec2_asg: - name: "{{ resource_prefix }}-asg" - max_instance_lifetime: 604801 - register: output - - - name: ensure max_instance_lifetime is set - assert: - that: - - output.max_instance_lifetime == 604801 - - - name: run without max_instance_lifetime - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - - - name: ensure max_instance_lifetime not affected by defaults - assert: - that: - - output.max_instance_lifetime == 604801 - - - name: disable asg max_instance_lifetime - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - max_instance_lifetime: 0 - register: output - - - name: ensure max_instance_lifetime is not set - assert: - that: - - not output.max_instance_lifetime - - # ============================================================ - - # perform rolling replace with different launch configuration - - name: perform rolling update to new AMI - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc-2" - health_check_type: ELB - desired_capacity: 3 - min_size: 1 - max_size: 5 - health_check_period: 900 - load_balancers: "{{ load_balancer_name }}" - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - wait_for_instances: yes - replace_all_instances: yes - wait_timeout: 1800 - state: present - register: output - - # ensure that all instances have new launch config - - assert: - that: - - "item.value.launch_config_name == '{{ resource_prefix }}-lc-2'" - loop: "{{ output.instance_facts | dict2items }}" - - # assert they are all healthy and that the rolling update resulted in the appropriate number of instances - - assert: - that: - - "output.viable_instances == 3" - - # ============================================================ - - # perform rolling replace with the original launch configuration - - name: perform rolling update to new AMI while removing the load balancer - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - health_check_type: EC2 - desired_capacity: 3 - min_size: 1 - max_size: 5 - health_check_period: 900 - load_balancers: [] - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - wait_for_instances: yes - replace_all_instances: yes - wait_timeout: 1800 - state: present - register: output - - # ensure that all instances have new launch config - - assert: - that: - - "item.value.launch_config_name == '{{ resource_prefix }}-lc'" - loop: "{{ output.instance_facts | dict2items }}" - - # assert they are all healthy and that the rolling update resulted in the appropriate number of instances - # there should be the same number of instances as there were before the rolling update was performed - - assert: - that: - - "output.viable_instances == 3" - - # ============================================================ - - # perform rolling replace with new launch configuration and lc_check:false - - name: "perform rolling update to new AMI with lc_check: false" - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc-2" - health_check_type: EC2 - desired_capacity: 3 - min_size: 1 - max_size: 5 - health_check_period: 900 - load_balancers: [] - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - wait_for_instances: yes - replace_all_instances: yes - replace_batch_size: 3 - lc_check: false - wait_timeout: 1800 - state: present - - # Collect ec2_asg_info - - name: get ec2_asg info - ec2_asg_info: - name: "{{ resource_prefix }}-asg" - register: output - - # Since we started with 3 instances and replace all of them. - # We should see only 3 instances total. - - assert: - that: - - output.results[0].instances | length == 3 - - # ============================================================ - - - name: kill asg - ec2_asg: - name: "{{ resource_prefix }}-asg" - state: absent - wait_timeout: 800 - async: 400 - - # Create new asg with replace_all_instances and lc_check:false - - name: "new asg with lc_check: false" - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_config_name: "{{ resource_prefix }}-lc" - health_check_type: EC2 - desired_capacity: 3 - min_size: 1 - max_size: 5 - health_check_period: 900 - load_balancers: [] - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - wait_for_instances: yes - replace_all_instances: yes - replace_batch_size: 3 - lc_check: false - wait_timeout: 1800 - state: present - - # Collect ec2_asg_info - - name: get ec2_asg information - ec2_asg_info: - name: "{{ resource_prefix }}-asg" - register: output - - # Get all instance_ids we saw and assert we saw number expected - # Should only see 3 (don't replace instances we just created) - - assert: - that: - - output.results[0].instances | length == 3 - - # we need a launch template, otherwise we cannot test the mixed instance policy - - name: create launch template for autoscaling group to test its mixed instances policy - ec2_launch_template: - template_name: "{{ resource_prefix }}-lt" - image_id: "{{ ec2_ami_image }}" - instance_type: t3.micro - credit_specification: - cpu_credits: standard - network_interfaces: - - associate_public_ip_address: yes - delete_on_termination: yes - device_index: 0 - groups: - - "{{ sg.group_id }}" - - - name: update autoscaling group with mixed-instances policy with mixed instances types - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_template: - launch_template_name: "{{ resource_prefix }}-lt" - desired_capacity: 1 - min_size: 1 - max_size: 1 - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - state: present - mixed_instances_policy: - instance_types: - - t3.micro - - t2.nano - wait_for_instances: yes - register: output - - - assert: - that: - - "output.mixed_instances_policy | length == 2" - - "output.mixed_instances_policy[0] == 't3.micro'" - - "output.mixed_instances_policy[1] == 't2.nano'" - - - name: update autoscaling group with mixed-instances policy with instances_distribution - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_template: - launch_template_name: "{{ resource_prefix }}-lt" - desired_capacity: 1 - min_size: 1 - max_size: 1 - vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - state: present - mixed_instances_policy: - instance_types: - - t3.micro - - t2.nano - instances_distribution: - on_demand_percentage_above_base_capacity: 0 - spot_allocation_strategy: capacity-optimized - wait_for_instances: yes - register: output - - - assert: - that: - - "output.mixed_instances_policy_full['launch_template']['overrides'][0]['instance_type'] == 't3.micro'" - - "output.mixed_instances_policy_full['launch_template']['overrides'][1]['instance_type'] == 't2.nano'" - - "output.mixed_instances_policy_full['instances_distribution']['on_demand_percentage_above_base_capacity'] == 0" - - "output.mixed_instances_policy_full['instances_distribution']['spot_allocation_strategy'] == 'capacity-optimized'" - - # ============================================================ - - # Target group names have max length of 32 characters - - set_fact: - tg1_name: "{{ (resource_prefix + '-tg1' ) | regex_search('(.{1,32})$') }}" - tg2_name: "{{ (resource_prefix + '-tg2' ) | regex_search('(.{1,32})$') }}" - - - name: create target group 1 - elb_target_group: - name: "{{ tg1_name }}" - protocol: tcp - port: 80 - health_check_protocol: tcp - health_check_port: 80 - healthy_threshold_count: 2 - unhealthy_threshold_count: 2 - vpc_id: "{{ testing_vpc.vpc.id }}" - state: present - register: out_tg1 - - - name: create target group 2 - elb_target_group: - name: "{{ tg2_name }}" - protocol: tcp - port: 80 - health_check_protocol: tcp - health_check_port: 80 - healthy_threshold_count: 2 - unhealthy_threshold_count: 2 - vpc_id: "{{ testing_vpc.vpc.id }}" - state: present - register: out_tg2 - - - name: update autoscaling group with tg1 - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_template: - launch_template_name: "{{ resource_prefix }}-lt" - target_group_arns: - - "{{ out_tg1.target_group_arn }}" - desired_capacity: 1 - min_size: 1 - max_size: 1 - state: present - wait_for_instances: yes - register: output - - - assert: - that: - - output.target_group_arns[0] == out_tg1.target_group_arn - - - name: update autoscaling group add tg2 - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_template: - launch_template_name: "{{ resource_prefix }}-lt" - target_group_arns: - - "{{ out_tg1.target_group_arn }}" - - "{{ out_tg2.target_group_arn }}" - desired_capacity: 1 - min_size: 1 - max_size: 1 - state: present - wait_for_instances: yes - register: output - - - assert: - that: - - "output.target_group_arns | length == 2" - - - name: update autoscaling group remove tg1 - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_template: - launch_template_name: "{{ resource_prefix }}-lt" - target_group_arns: - - "{{ out_tg2.target_group_arn }}" - desired_capacity: 1 - min_size: 1 - max_size: 1 - state: present - wait_for_instances: yes - register: output - - - assert: - that: - - "output.target_group_arns | length == 1" - - "output.target_group_arns[0] == out_tg2.target_group_arn" - - - name: update autoscaling group remove tg2 and add tg1 - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_template: - launch_template_name: "{{ resource_prefix }}-lt" - target_group_arns: - - "{{ out_tg1.target_group_arn }}" - desired_capacity: 1 - min_size: 1 - max_size: 1 - state: present - wait_for_instances: yes - register: output - - - assert: - that: - - "output.target_group_arns | length == 1" - - "output.target_group_arns[0] == out_tg1.target_group_arn" - - - name: target group no change - ec2_asg: - name: "{{ resource_prefix }}-asg" - launch_template: - launch_template_name: "{{ resource_prefix }}-lt" - target_group_arns: - - "{{ out_tg1.target_group_arn }}" - desired_capacity: 1 - min_size: 1 - max_size: 1 - state: present - wait_for_instances: yes - register: output - - - assert: - that: - - "output.target_group_arns | length == 1" - - "output.target_group_arns[0] == out_tg1.target_group_arn" - - "output.changed == false" + # # ============================================================ + + # - name: launch asg and wait for instances to be deemed healthy (no ELB) + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # state: present + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - "output.viable_instances == 1" + + # - name: Tag asg + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # tags: + # - tag_a: 'value 1' + # propagate_at_launch: no + # - tag_b: 'value 2' + # propagate_at_launch: yes + # register: output + + # - assert: + # that: + # - "output.tags | length == 2" + # - output is changed + + # - name: Re-Tag asg (different order) + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # tags: + # - tag_b: 'value 2' + # propagate_at_launch: yes + # - tag_a: 'value 1' + # propagate_at_launch: no + # register: output + + # - assert: + # that: + # - "output.tags | length == 2" + # - output is not changed + + # - name: Re-Tag asg new tags + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # tags: + # - tag_c: 'value 3' + # propagate_at_launch: no + # register: output + + # - assert: + # that: + # - "output.tags | length == 1" + # - output is changed + + # - name: Re-Tag asg update propagate_at_launch + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # tags: + # - tag_c: 'value 3' + # propagate_at_launch: yes + # register: output + + # - assert: + # that: + # - "output.tags | length == 1" + # - output is changed + + # - name: Enable metrics collection + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # metrics_collection: yes + # register: output + + # - assert: + # that: + # - output is changed + + # - name: Enable metrics collection (check idempotency) + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # metrics_collection: yes + # register: output + + # - assert: + # that: + # - output is not changed + + # - name: Disable metrics collection + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # metrics_collection: no + # register: output + + # - assert: + # that: + # - output is changed + + # - name: Disable metrics collection (check idempotency) + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # metrics_collection: no + # register: output + + # - assert: + # that: + # - output is not changed + + # - name: kill asg + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # state: absent + # wait_timeout: 800 + # async: 400 + + # # ============================================================ + + # - name: launch asg and do not wait for instances to be deemed healthy (no ELB) + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # wait_for_instances: no + # state: present + # register: output + + # - assert: + # that: + # - "output.viable_instances == 0" + + # - name: kill asg + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # state: absent + # wait_timeout: 800 + # register: output + # retries: 3 + # until: output is succeeded + # delay: 10 + # async: 400 + + # # ============================================================ + + # - name: create asg with asg metrics enabled + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # metrics_collection: true + # launch_config_name: "{{ resource_prefix }}-lc" + # desired_capacity: 0 + # min_size: 0 + # max_size: 0 + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # state: present + # register: output + + # - assert: + # that: + # - "'Group' in output.metrics_collection.0.Metric" + + # - name: kill asg + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # state: absent + # wait_timeout: 800 + # async: 400 + + # # ============================================================ + + # - name: launch load balancer + # ec2_elb_lb: + # name: "{{ load_balancer_name }}" + # state: present + # security_group_ids: + # - "{{ sg.group_id }}" + # subnets: "{{ testing_subnet.subnet.id }}" + # connection_draining_timeout: 60 + # listeners: + # - protocol: http + # load_balancer_port: 80 + # instance_port: 80 + # health_check: + # ping_protocol: tcp + # ping_port: 80 + # ping_path: "/" + # response_timeout: 5 + # interval: 10 + # unhealthy_threshold: 4 + # healthy_threshold: 2 + # register: load_balancer + + # - name: launch asg and wait for instances to be deemed healthy (ELB) + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + # health_check_type: ELB + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # health_check_period: 300 + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # load_balancers: "{{ load_balancer_name }}" + # wait_for_instances: yes + # wait_timeout: 900 + # state: present + # register: output + + # - assert: + # that: + # - "output.viable_instances == 1" + + # # ============================================================ + + # # grow scaling group to 3 + # - name: add 2 more instances wait for instances to be deemed healthy (ELB) + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + # health_check_type: ELB + # desired_capacity: 3 + # min_size: 3 + # max_size: 5 + # health_check_period: 600 + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # load_balancers: "{{ load_balancer_name }}" + # wait_for_instances: yes + # wait_timeout: 1200 + # state: present + # register: output + + # - assert: + # that: + # - "output.viable_instances == 3" + + # # ============================================================ + + # # Test max_instance_lifetime option + # - name: enable asg max_instance_lifetime + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # max_instance_lifetime: 604801 + # register: output + + # - name: ensure max_instance_lifetime is set + # assert: + # that: + # - output.max_instance_lifetime == 604801 + + # - name: run without max_instance_lifetime + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + + # - name: ensure max_instance_lifetime not affected by defaults + # assert: + # that: + # - output.max_instance_lifetime == 604801 + + # - name: disable asg max_instance_lifetime + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + # max_instance_lifetime: 0 + # register: output + + # - name: ensure max_instance_lifetime is not set + # assert: + # that: + # - not output.max_instance_lifetime + + # # ============================================================ + + # # perform rolling replace with different launch configuration + # - name: perform rolling update to new AMI + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc-2" + # health_check_type: ELB + # desired_capacity: 3 + # min_size: 1 + # max_size: 5 + # health_check_period: 900 + # load_balancers: "{{ load_balancer_name }}" + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # wait_for_instances: yes + # replace_all_instances: yes + # wait_timeout: 1800 + # state: present + # register: output + + # # ensure that all instances have new launch config + # - assert: + # that: + # - "item.value.launch_config_name == '{{ resource_prefix }}-lc-2'" + # loop: "{{ output.instance_facts | dict2items }}" + + # # assert they are all healthy and that the rolling update resulted in the appropriate number of instances + # - assert: + # that: + # - "output.viable_instances == 3" + + # # ============================================================ + + # # perform rolling replace with the original launch configuration + # - name: perform rolling update to new AMI while removing the load balancer + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + # health_check_type: EC2 + # desired_capacity: 3 + # min_size: 1 + # max_size: 5 + # health_check_period: 900 + # load_balancers: [] + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # wait_for_instances: yes + # replace_all_instances: yes + # wait_timeout: 1800 + # state: present + # register: output + + # # ensure that all instances have new launch config + # - assert: + # that: + # - "item.value.launch_config_name == '{{ resource_prefix }}-lc'" + # loop: "{{ output.instance_facts | dict2items }}" + + # # assert they are all healthy and that the rolling update resulted in the appropriate number of instances + # # there should be the same number of instances as there were before the rolling update was performed + # - assert: + # that: + # - "output.viable_instances == 3" + + # # ============================================================ + + # # perform rolling replace with new launch configuration and lc_check:false + # - name: "perform rolling update to new AMI with lc_check: false" + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc-2" + # health_check_type: EC2 + # desired_capacity: 3 + # min_size: 1 + # max_size: 5 + # health_check_period: 900 + # load_balancers: [] + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # wait_for_instances: yes + # replace_all_instances: yes + # replace_batch_size: 3 + # lc_check: false + # wait_timeout: 1800 + # state: present + + # # Collect ec2_asg_info + # - name: get ec2_asg info + # ec2_asg_info: + # name: "{{ resource_prefix }}-asg" + # register: output + + # # Since we started with 3 instances and replace all of them. + # # We should see only 3 instances total. + # - assert: + # that: + # - output.results[0].instances | length == 3 + + # # ============================================================ + + # - name: kill asg + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # state: absent + # wait_timeout: 800 + # async: 400 + + # # Create new asg with replace_all_instances and lc_check:false + # - name: "new asg with lc_check: false" + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_config_name: "{{ resource_prefix }}-lc" + # health_check_type: EC2 + # desired_capacity: 3 + # min_size: 1 + # max_size: 5 + # health_check_period: 900 + # load_balancers: [] + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # wait_for_instances: yes + # replace_all_instances: yes + # replace_batch_size: 3 + # lc_check: false + # wait_timeout: 1800 + # state: present + + # # Collect ec2_asg_info + # - name: get ec2_asg information + # ec2_asg_info: + # name: "{{ resource_prefix }}-asg" + # register: output + + # # Get all instance_ids we saw and assert we saw number expected + # # Should only see 3 (don't replace instances we just created) + # - assert: + # that: + # - output.results[0].instances | length == 3 + + # # we need a launch template, otherwise we cannot test the mixed instance policy + # - name: create launch template for autoscaling group to test its mixed instances policy + # ec2_launch_template: + # template_name: "{{ resource_prefix }}-lt" + # image_id: "{{ ec2_ami_image }}" + # instance_type: t3.micro + # credit_specification: + # cpu_credits: standard + # network_interfaces: + # - associate_public_ip_address: yes + # delete_on_termination: yes + # device_index: 0 + # groups: + # - "{{ sg.group_id }}" + + # - name: update autoscaling group with mixed-instances policy with mixed instances types + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_template: + # launch_template_name: "{{ resource_prefix }}-lt" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # state: present + # mixed_instances_policy: + # instance_types: + # - t3.micro + # - t2.nano + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - "output.mixed_instances_policy | length == 2" + # - "output.mixed_instances_policy[0] == 't3.micro'" + # - "output.mixed_instances_policy[1] == 't2.nano'" + + # - name: update autoscaling group with mixed-instances policy with instances_distribution + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_template: + # launch_template_name: "{{ resource_prefix }}-lt" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + # state: present + # mixed_instances_policy: + # instance_types: + # - t3.micro + # - t2.nano + # instances_distribution: + # on_demand_percentage_above_base_capacity: 0 + # spot_allocation_strategy: capacity-optimized + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - "output.mixed_instances_policy_full['launch_template']['overrides'][0]['instance_type'] == 't3.micro'" + # - "output.mixed_instances_policy_full['launch_template']['overrides'][1]['instance_type'] == 't2.nano'" + # - "output.mixed_instances_policy_full['instances_distribution']['on_demand_percentage_above_base_capacity'] == 0" + # - "output.mixed_instances_policy_full['instances_distribution']['spot_allocation_strategy'] == 'capacity-optimized'" + + # # ============================================================ + + # # Target group names have max length of 32 characters + # - set_fact: + # tg1_name: "{{ (resource_prefix + '-tg1' ) | regex_search('(.{1,32})$') }}" + # tg2_name: "{{ (resource_prefix + '-tg2' ) | regex_search('(.{1,32})$') }}" + + # - name: create target group 1 + # elb_target_group: + # name: "{{ tg1_name }}" + # protocol: tcp + # port: 80 + # health_check_protocol: tcp + # health_check_port: 80 + # healthy_threshold_count: 2 + # unhealthy_threshold_count: 2 + # vpc_id: "{{ testing_vpc.vpc.id }}" + # state: present + # register: out_tg1 + + # - name: create target group 2 + # elb_target_group: + # name: "{{ tg2_name }}" + # protocol: tcp + # port: 80 + # health_check_protocol: tcp + # health_check_port: 80 + # healthy_threshold_count: 2 + # unhealthy_threshold_count: 2 + # vpc_id: "{{ testing_vpc.vpc.id }}" + # state: present + # register: out_tg2 + + # - name: update autoscaling group with tg1 + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_template: + # launch_template_name: "{{ resource_prefix }}-lt" + # target_group_arns: + # - "{{ out_tg1.target_group_arn }}" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # state: present + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - output.target_group_arns[0] == out_tg1.target_group_arn + + # - name: update autoscaling group add tg2 + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_template: + # launch_template_name: "{{ resource_prefix }}-lt" + # target_group_arns: + # - "{{ out_tg1.target_group_arn }}" + # - "{{ out_tg2.target_group_arn }}" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # state: present + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - "output.target_group_arns | length == 2" + + # - name: update autoscaling group remove tg1 + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_template: + # launch_template_name: "{{ resource_prefix }}-lt" + # target_group_arns: + # - "{{ out_tg2.target_group_arn }}" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # state: present + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - "output.target_group_arns | length == 1" + # - "output.target_group_arns[0] == out_tg2.target_group_arn" + + # - name: update autoscaling group remove tg2 and add tg1 + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_template: + # launch_template_name: "{{ resource_prefix }}-lt" + # target_group_arns: + # - "{{ out_tg1.target_group_arn }}" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # state: present + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - "output.target_group_arns | length == 1" + # - "output.target_group_arns[0] == out_tg1.target_group_arn" + + # - name: target group no change + # ec2_asg: + # name: "{{ resource_prefix }}-asg" + # launch_template: + # launch_template_name: "{{ resource_prefix }}-lt" + # target_group_arns: + # - "{{ out_tg1.target_group_arn }}" + # desired_capacity: 1 + # min_size: 1 + # max_size: 1 + # state: present + # wait_for_instances: yes + # register: output + + # - assert: + # that: + # - "output.target_group_arns | length == 1" + # - "output.target_group_arns[0] == out_tg1.target_group_arn" + # - "output.changed == false" - # ============================================================ From 58c627e3870585124ce8fc3067db0c8f65ed5d34 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Sun, 20 Feb 2022 22:55:17 -0800 Subject: [PATCH 05/12] WIP: Adding integration tests --- .../targets/ec2_asg/tasks/instance_detach.yml | 22 +++++++++---------- .../targets/ec2_asg/tasks/main.yml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml index 1c1d1e7fb57..ba6e3864ee4 100644 --- a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -48,6 +48,8 @@ instance_0: "{{ asg_info.results[0].instances[0].instance_id }}" instance_1: "{{ asg_info.results[0].instances[1].instance_id }}" instance_2: "{{ asg_info.results[0].instances[2].instance_id }}" + - set_fact: + instance_ids_list: "{{ instance_ids_list + [instance_0, instance_1, instance_2] }}" - name: Gather information about instance 0 amazon.aws.ec2_instance_info: @@ -68,7 +70,7 @@ # assert that there are 3 instances in the AutoScalingGroup - assert: that: - - "{{ instances | length }} == 3" + - "{{ asg_info.results[0].instances | length }} == 3" - "'{{ instance_0_info.instances[0].state.name }}' == 'running'" - "'{{ instance_1_info.instances[0].state.name }}' == 'running'" - "'{{ instance_2_info.instances[0].state.name }}' == 'running'" @@ -86,8 +88,8 @@ desired_capacity: 3 region: "{{ aws_region }}" detach_instances: - - '{{ instances[0] }}' - - '{{ instances[1] }}' + - '{{ instance_0 }}' + - '{{ instance_1 }}' # pause to allow completion of instance replacement - name: Pause for 1 minute @@ -106,6 +108,8 @@ instance_4: "{{ instances_detach_replace[0] }}" instance_5: "{{ instances_detach_replace[1] }}" instance_6: "{{ instances_detach_replace[2] }}" + - set_fact: + instance_ids_list: "{{ instance_ids_list + [instance_4, instance_5, instance_6] }}" - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: @@ -157,6 +161,8 @@ instances_detach_no_replace: "{{ asg_info_decrement.results[0].instances | map(attribute='instance_id') | list }}" - set_fact: instance_7: "{{ instances_detach_no_replace[0] }}" + - set_fact: + instance_ids_list: "{{ instance_ids_list + [instance_7] }}" - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: @@ -185,16 +191,10 @@ - name: terminate any instances created during this test amazon.aws.ec2_instance: instance_ids: - -"{{ item }}" + - "{{ item }}" state: absent loop: - - "{{ instance_0 }}" - - "{{ instance_1 }}" - - "{{ instance_2 }}" - - "{{ instance_4 }}" - - "{{ instance_5 }}" - - "{{ instance_6 }}" - - "{{ instance_7 }}" + - "{{ instance_ids_list }}" - name: kill asg created in this test ec2_asg: diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index b82553dfd43..4fa809614b3 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -33,7 +33,7 @@ collections: - amazon.aws vars: - instance_ids: [] + instance_ids_list: [] block: From 93b7405f4cf0785362b4f96d99ff00d496d1491a Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Sun, 20 Feb 2022 23:04:22 -0800 Subject: [PATCH 06/12] WIP: adding integration tests for detach instances feature --- .../targets/ec2_asg/tasks/instance_detach.yml | 14 +++++++------- tests/integration/targets/ec2_asg/tasks/main.yml | 2 -- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml index ba6e3864ee4..a632c46256a 100644 --- a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -48,8 +48,6 @@ instance_0: "{{ asg_info.results[0].instances[0].instance_id }}" instance_1: "{{ asg_info.results[0].instances[1].instance_id }}" instance_2: "{{ asg_info.results[0].instances[2].instance_id }}" - - set_fact: - instance_ids_list: "{{ instance_ids_list + [instance_0, instance_1, instance_2] }}" - name: Gather information about instance 0 amazon.aws.ec2_instance_info: @@ -108,8 +106,6 @@ instance_4: "{{ instances_detach_replace[0] }}" instance_5: "{{ instances_detach_replace[1] }}" instance_6: "{{ instances_detach_replace[2] }}" - - set_fact: - instance_ids_list: "{{ instance_ids_list + [instance_4, instance_5, instance_6] }}" - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: @@ -161,8 +157,6 @@ instances_detach_no_replace: "{{ asg_info_decrement.results[0].instances | map(attribute='instance_id') | list }}" - set_fact: instance_7: "{{ instances_detach_no_replace[0] }}" - - set_fact: - instance_ids_list: "{{ instance_ids_list + [instance_7] }}" - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: @@ -194,7 +188,13 @@ - "{{ item }}" state: absent loop: - - "{{ instance_ids_list }}" + - "{{ instance_0 }}" + - "{{ instance_1 }}" + - "{{ instance_2 }}" + - "{{ instance_4 }}" + - "{{ instance_5 }}" + - "{{ instance_6 }}" + - "{{ instance_7 }}" - name: kill asg created in this test ec2_asg: diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index 4fa809614b3..60d364c28ab 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -32,8 +32,6 @@ region: "{{ aws_region }}" collections: - amazon.aws - vars: - instance_ids_list: [] block: From 041fbd545a27a9adc273c6f85db0282eecb0ab34 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Mon, 21 Feb 2022 13:05:41 -0800 Subject: [PATCH 07/12] Improve integration tests sanity --- .../933-ec2_asg-detach-instances-feature.yml | 3 + .../targets/ec2_asg/tasks/instance_detach.yml | 129 +- .../targets/ec2_asg/tasks/main.yml | 1212 ++++++++--------- 3 files changed, 670 insertions(+), 674 deletions(-) create mode 100644 changelogs/fragments/933-ec2_asg-detach-instances-feature.yml diff --git a/changelogs/fragments/933-ec2_asg-detach-instances-feature.yml b/changelogs/fragments/933-ec2_asg-detach-instances-feature.yml new file mode 100644 index 00000000000..3ebb13fff78 --- /dev/null +++ b/changelogs/fragments/933-ec2_asg-detach-instances-feature.yml @@ -0,0 +1,3 @@ +minor_changes: + - ec2_asg - Added functionality to detach specific instances and/or decrement desired capacity + from ASG without terminating instances (https://github.com/ansible-collections/community.aws/pull/933). diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml index a632c46256a..54302ca3a0f 100644 --- a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -1,6 +1,6 @@ - name: Running instance detach tests block: - #------------------------------------------------------------------------------------------------------------ + #---------------------------------------------------------------------- - name: create a launch configuration ec2_lc: name: "{{ resource_prefix }}-lc-detach-test" @@ -17,8 +17,8 @@ - create_lc.failed is false - '"autoscaling:CreateLaunchConfiguration" in create_lc.resource_actions' - #------------------------------------------------------------------------------------------------------------ - - name: create a AutoScalingGroup + #---------------------------------------------------------------------- + - name: create a AutoScalingGroup to be used for instance_detach test ec2_asg: name: "{{ resource_prefix }}-asg-detach-test" launch_config_name: "{{ resource_prefix }}-lc-detach-test" @@ -38,42 +38,35 @@ - create_asg.failed is false - create_asg.instances | length == 3 - create_asg.desired_capacity == 3 + - create_asg.in_service_instances == 3 - '"autoscaling:CreateAutoScalingGroup" in create_asg.resource_actions' - # gather info about asg, get instance ids - - ec2_asg_info: + - name: gather info about asg, get instance ids + ec2_asg_info: name: "{{ resource_prefix }}-asg-detach-test" register: asg_info - set_fact: - instance_0: "{{ asg_info.results[0].instances[0].instance_id }}" - instance_1: "{{ asg_info.results[0].instances[1].instance_id }}" - instance_2: "{{ asg_info.results[0].instances[2].instance_id }}" + init_instance_1: "{{ asg_info.results[0].instances[0].instance_id }}" + init_instance_2: "{{ asg_info.results[0].instances[1].instance_id }}" + init_instance_3: "{{ asg_info.results[0].instances[2].instance_id }}" - - name: Gather information about instance 0 - amazon.aws.ec2_instance_info: - instance_ids: - - "{{ instance_0 }}" - register: instance_0_info - - name: Gather information about instance 1 - amazon.aws.ec2_instance_info: - instance_ids: - - "{{ instance_1 }}" - register: instance_1_info - - name: Gather information about instance 2 + - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: instance_ids: - - "{{ instance_2 }}" - register: instance_2_info + - "{{ init_instance_1 }}" + - "{{ init_instance_2 }}" + - "{{ init_instance_3 }}" + register: instances_info - # assert that there are 3 instances in the AutoScalingGroup + # assert that there are 3 instances running in the AutoScalingGroup - assert: that: - - "{{ asg_info.results[0].instances | length }} == 3" - - "'{{ instance_0_info.instances[0].state.name }}' == 'running'" - - "'{{ instance_1_info.instances[0].state.name }}' == 'running'" - - "'{{ instance_2_info.instances[0].state.name }}' == 'running'" + - asg_info.results[0].instances | length == 3 + - "'{{ instances_info.instances[0].state.name }}' == 'running'" + - "'{{ instances_info.instances[1].state.name }}' == 'running'" + - "'{{ instances_info.instances[2].state.name }}' == 'running'" - #------------------------------------------------------------------------------------------------------------ + #---------------------------------------------------------------------- - name: detach 2 instance from the asg and replace with other instances ec2_asg: @@ -86,47 +79,47 @@ desired_capacity: 3 region: "{{ aws_region }}" detach_instances: - - '{{ instance_0 }}' - - '{{ instance_1 }}' + - '{{ init_instance_1 }}' + - '{{ init_instance_2 }}' # pause to allow completion of instance replacement - name: Pause for 1 minute pause: minutes: 1 - # gather info about asg and make sure the instances are detached - # also make sure the instances are replaced and the count is maintained + # gather info about asg and get instance ids - ec2_asg_info: name: "{{ resource_prefix }}-asg-detach-test" register: asg_info_replaced - # create a list of instance ids from info result - set_fact: - instances_detach_replace: "{{ asg_info_replaced.results[0].instances | map(attribute='instance_id') | list }}" + instance_replace_1: "{{ asg_info_replaced.results[0].instances[0].instance_id }}" + instance_replace_2: "{{ asg_info_replaced.results[0].instances[1].instance_id }}" + instance_replace_3: "{{ asg_info_replaced.results[0].instances[2].instance_id }}" + + # create a list of instance currently attached to asg - set_fact: - instance_4: "{{ instances_detach_replace[0] }}" - instance_5: "{{ instances_detach_replace[1] }}" - instance_6: "{{ instances_detach_replace[2] }}" + asg_instance_detach_replace: "{{ asg_info_replaced.results[0].instances | map(attribute='instance_id') | list }}" - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: instance_ids: - - "{{ instance_0 }}" - - "{{ instance_1 }}" + - "{{ init_instance_1 }}" + - "{{ init_instance_2 }}" register: detached_instances_info - # assert that there are 3 still instances in the AutoScalingGroup - # assert that two specified instances are detached and still running (not terminated) + # assert that + # there are 3 still instances in the AutoScalingGroup + # two specified instances are detached and still running independently(not terminated) - assert: that: - # - asg_info_replaced.desired_capacity == 1 - # - asg_info_replaced.instances | length == 1 - - "{{ instances_detach_replace | length }} == 3" - - "'{{ instance_0 }}' not in {{ instances_detach_replace }}" - - "'{{ instance_1 }}' not in {{ instances_detach_replace }}" + - asg_info_replaced.results[0].desired_capacity == 3 + - asg_info_replaced.results[0].instances | length == 3 + - "'{{ init_instance_1 }}' not in {{ asg_instance_detach_replace }}" + - "'{{ init_instance_2 }}' not in {{ asg_instance_detach_replace }}" - "'{{ detached_instances_info.instances[0].state.name }}' == 'running'" - "'{{ detached_instances_info.instances[1].state.name }}' == 'running'" - #------------------------------------------------------------------------------------------------------------ + #---------------------------------------------------------------------- # detach 2 instances from the asg and reduce the desired capacity from 3 to 1 - name: detach 2 instance from the asg and reduce the desired capacity from 3 to 1 @@ -141,44 +134,45 @@ region: "{{ aws_region }}" decrement_desired_capacity: true detach_instances: - - '{{ instances_detach_replace[0] }}' - - '{{ instances_detach_replace[1] }}' + - '{{ instance_replace_1 }}' + - '{{ instance_replace_2 }}' - name: Pause for 1 minute to allow completion of above task pause: minutes: 1 - # gather information about asg + # gather information about asg and get instance id - ec2_asg_info: name: "{{ resource_prefix }}-asg-detach-test" register: asg_info_decrement - # create a list of instance ids from info result and set variable value to instance ID - set_fact: - instances_detach_no_replace: "{{ asg_info_decrement.results[0].instances | map(attribute='instance_id') | list }}" + instance_detach_decrement: "{{ asg_info_decrement.results[0].instances[0].instance_id }}" + # create a list of instance ids from info result and set variable value to instance ID - set_fact: - instance_7: "{{ instances_detach_no_replace[0] }}" + asg_instance_detach_decrement: "{{ asg_info_decrement.results[0].instances | map(attribute='instance_id') | list }}" - name: Gather information about recently detached instances amazon.aws.ec2_instance_info: instance_ids: - - "{{ instance_4 }}" - - "{{ instance_5 }}" + - "{{ instance_replace_1 }}" + - "{{ instance_replace_2 }}" register: detached_instances_info # assert that - # detached instances are not replaced and there is only 1 instance in the AutoScalingGroup - # desired capacity is reduced to 1 + # detached instances are not replaced and there is only 1 instance in the AutoScalingGroup + # desired capacity is reduced to 1 + # detached instances are not terminated - assert: that: - - asg_info_decrement.results[0].desired_capacity == 1 - asg_info_decrement.results[0].instances | length == 1 - - "'{{ instance_4 }}' not in {{ instances_detach_no_replace }}" - - "'{{ instance_5 }}' not in {{ instances_detach_no_replace }}" + - asg_info_decrement.results[0].desired_capacity == 1 + - "'{{ instance_replace_1 }}' not in {{ asg_instance_detach_decrement }}" + - "'{{ instance_replace_2 }}' not in {{ asg_instance_detach_decrement }}" - "'{{ detached_instances_info.instances[0].state.name }}' == 'running'" - "'{{ detached_instances_info.instances[1].state.name }}' == 'running'" - - "'{{ instance_6 }}' in {{ instances_detach_no_replace }}" + - "'{{ instance_replace_3 }}' == '{{ instance_detach_decrement }}'" - #------------------------------------------------------------------------------------------------------------ + #---------------------------------------------------------------------- always: @@ -188,13 +182,12 @@ - "{{ item }}" state: absent loop: - - "{{ instance_0 }}" - - "{{ instance_1 }}" - - "{{ instance_2 }}" - - "{{ instance_4 }}" - - "{{ instance_5 }}" - - "{{ instance_6 }}" - - "{{ instance_7 }}" + - "{{ init_instance_1 }}" + - "{{ init_instance_2 }}" + - "{{ init_instance_3 }}" + - "{{ instance_replace_1 }}" + - "{{ instance_replace_2 }}" + - "{{ instance_replace_3 }}" - name: kill asg created in this test ec2_asg: diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index 60d364c28ab..99d887d1237 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -118,612 +118,612 @@ - include_tasks: instance_detach.yml - # # ============================================================ - - # - name: launch asg and wait for instances to be deemed healthy (no ELB) - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # state: present - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - "output.viable_instances == 1" - - # - name: Tag asg - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # tags: - # - tag_a: 'value 1' - # propagate_at_launch: no - # - tag_b: 'value 2' - # propagate_at_launch: yes - # register: output - - # - assert: - # that: - # - "output.tags | length == 2" - # - output is changed - - # - name: Re-Tag asg (different order) - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # tags: - # - tag_b: 'value 2' - # propagate_at_launch: yes - # - tag_a: 'value 1' - # propagate_at_launch: no - # register: output - - # - assert: - # that: - # - "output.tags | length == 2" - # - output is not changed - - # - name: Re-Tag asg new tags - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # tags: - # - tag_c: 'value 3' - # propagate_at_launch: no - # register: output - - # - assert: - # that: - # - "output.tags | length == 1" - # - output is changed - - # - name: Re-Tag asg update propagate_at_launch - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # tags: - # - tag_c: 'value 3' - # propagate_at_launch: yes - # register: output - - # - assert: - # that: - # - "output.tags | length == 1" - # - output is changed - - # - name: Enable metrics collection - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # metrics_collection: yes - # register: output - - # - assert: - # that: - # - output is changed - - # - name: Enable metrics collection (check idempotency) - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # metrics_collection: yes - # register: output - - # - assert: - # that: - # - output is not changed - - # - name: Disable metrics collection - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # metrics_collection: no - # register: output - - # - assert: - # that: - # - output is changed - - # - name: Disable metrics collection (check idempotency) - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # metrics_collection: no - # register: output - - # - assert: - # that: - # - output is not changed - - # - name: kill asg - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # state: absent - # wait_timeout: 800 - # async: 400 - - # # ============================================================ - - # - name: launch asg and do not wait for instances to be deemed healthy (no ELB) - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # wait_for_instances: no - # state: present - # register: output - - # - assert: - # that: - # - "output.viable_instances == 0" - - # - name: kill asg - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # state: absent - # wait_timeout: 800 - # register: output - # retries: 3 - # until: output is succeeded - # delay: 10 - # async: 400 - - # # ============================================================ - - # - name: create asg with asg metrics enabled - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # metrics_collection: true - # launch_config_name: "{{ resource_prefix }}-lc" - # desired_capacity: 0 - # min_size: 0 - # max_size: 0 - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # state: present - # register: output - - # - assert: - # that: - # - "'Group' in output.metrics_collection.0.Metric" - - # - name: kill asg - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # state: absent - # wait_timeout: 800 - # async: 400 - - # # ============================================================ - - # - name: launch load balancer - # ec2_elb_lb: - # name: "{{ load_balancer_name }}" - # state: present - # security_group_ids: - # - "{{ sg.group_id }}" - # subnets: "{{ testing_subnet.subnet.id }}" - # connection_draining_timeout: 60 - # listeners: - # - protocol: http - # load_balancer_port: 80 - # instance_port: 80 - # health_check: - # ping_protocol: tcp - # ping_port: 80 - # ping_path: "/" - # response_timeout: 5 - # interval: 10 - # unhealthy_threshold: 4 - # healthy_threshold: 2 - # register: load_balancer - - # - name: launch asg and wait for instances to be deemed healthy (ELB) - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - # health_check_type: ELB - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # health_check_period: 300 - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # load_balancers: "{{ load_balancer_name }}" - # wait_for_instances: yes - # wait_timeout: 900 - # state: present - # register: output - - # - assert: - # that: - # - "output.viable_instances == 1" - - # # ============================================================ - - # # grow scaling group to 3 - # - name: add 2 more instances wait for instances to be deemed healthy (ELB) - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - # health_check_type: ELB - # desired_capacity: 3 - # min_size: 3 - # max_size: 5 - # health_check_period: 600 - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # load_balancers: "{{ load_balancer_name }}" - # wait_for_instances: yes - # wait_timeout: 1200 - # state: present - # register: output - - # - assert: - # that: - # - "output.viable_instances == 3" - - # # ============================================================ - - # # Test max_instance_lifetime option - # - name: enable asg max_instance_lifetime - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # max_instance_lifetime: 604801 - # register: output - - # - name: ensure max_instance_lifetime is set - # assert: - # that: - # - output.max_instance_lifetime == 604801 - - # - name: run without max_instance_lifetime - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - - # - name: ensure max_instance_lifetime not affected by defaults - # assert: - # that: - # - output.max_instance_lifetime == 604801 - - # - name: disable asg max_instance_lifetime - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - # max_instance_lifetime: 0 - # register: output - - # - name: ensure max_instance_lifetime is not set - # assert: - # that: - # - not output.max_instance_lifetime - - # # ============================================================ - - # # perform rolling replace with different launch configuration - # - name: perform rolling update to new AMI - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc-2" - # health_check_type: ELB - # desired_capacity: 3 - # min_size: 1 - # max_size: 5 - # health_check_period: 900 - # load_balancers: "{{ load_balancer_name }}" - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # wait_for_instances: yes - # replace_all_instances: yes - # wait_timeout: 1800 - # state: present - # register: output - - # # ensure that all instances have new launch config - # - assert: - # that: - # - "item.value.launch_config_name == '{{ resource_prefix }}-lc-2'" - # loop: "{{ output.instance_facts | dict2items }}" - - # # assert they are all healthy and that the rolling update resulted in the appropriate number of instances - # - assert: - # that: - # - "output.viable_instances == 3" - - # # ============================================================ - - # # perform rolling replace with the original launch configuration - # - name: perform rolling update to new AMI while removing the load balancer - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - # health_check_type: EC2 - # desired_capacity: 3 - # min_size: 1 - # max_size: 5 - # health_check_period: 900 - # load_balancers: [] - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # wait_for_instances: yes - # replace_all_instances: yes - # wait_timeout: 1800 - # state: present - # register: output - - # # ensure that all instances have new launch config - # - assert: - # that: - # - "item.value.launch_config_name == '{{ resource_prefix }}-lc'" - # loop: "{{ output.instance_facts | dict2items }}" - - # # assert they are all healthy and that the rolling update resulted in the appropriate number of instances - # # there should be the same number of instances as there were before the rolling update was performed - # - assert: - # that: - # - "output.viable_instances == 3" - - # # ============================================================ - - # # perform rolling replace with new launch configuration and lc_check:false - # - name: "perform rolling update to new AMI with lc_check: false" - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc-2" - # health_check_type: EC2 - # desired_capacity: 3 - # min_size: 1 - # max_size: 5 - # health_check_period: 900 - # load_balancers: [] - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # wait_for_instances: yes - # replace_all_instances: yes - # replace_batch_size: 3 - # lc_check: false - # wait_timeout: 1800 - # state: present - - # # Collect ec2_asg_info - # - name: get ec2_asg info - # ec2_asg_info: - # name: "{{ resource_prefix }}-asg" - # register: output - - # # Since we started with 3 instances and replace all of them. - # # We should see only 3 instances total. - # - assert: - # that: - # - output.results[0].instances | length == 3 - - # # ============================================================ - - # - name: kill asg - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # state: absent - # wait_timeout: 800 - # async: 400 - - # # Create new asg with replace_all_instances and lc_check:false - # - name: "new asg with lc_check: false" - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_config_name: "{{ resource_prefix }}-lc" - # health_check_type: EC2 - # desired_capacity: 3 - # min_size: 1 - # max_size: 5 - # health_check_period: 900 - # load_balancers: [] - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # wait_for_instances: yes - # replace_all_instances: yes - # replace_batch_size: 3 - # lc_check: false - # wait_timeout: 1800 - # state: present - - # # Collect ec2_asg_info - # - name: get ec2_asg information - # ec2_asg_info: - # name: "{{ resource_prefix }}-asg" - # register: output - - # # Get all instance_ids we saw and assert we saw number expected - # # Should only see 3 (don't replace instances we just created) - # - assert: - # that: - # - output.results[0].instances | length == 3 - - # # we need a launch template, otherwise we cannot test the mixed instance policy - # - name: create launch template for autoscaling group to test its mixed instances policy - # ec2_launch_template: - # template_name: "{{ resource_prefix }}-lt" - # image_id: "{{ ec2_ami_image }}" - # instance_type: t3.micro - # credit_specification: - # cpu_credits: standard - # network_interfaces: - # - associate_public_ip_address: yes - # delete_on_termination: yes - # device_index: 0 - # groups: - # - "{{ sg.group_id }}" - - # - name: update autoscaling group with mixed-instances policy with mixed instances types - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_template: - # launch_template_name: "{{ resource_prefix }}-lt" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # state: present - # mixed_instances_policy: - # instance_types: - # - t3.micro - # - t2.nano - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - "output.mixed_instances_policy | length == 2" - # - "output.mixed_instances_policy[0] == 't3.micro'" - # - "output.mixed_instances_policy[1] == 't2.nano'" - - # - name: update autoscaling group with mixed-instances policy with instances_distribution - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_template: - # launch_template_name: "{{ resource_prefix }}-lt" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" - # state: present - # mixed_instances_policy: - # instance_types: - # - t3.micro - # - t2.nano - # instances_distribution: - # on_demand_percentage_above_base_capacity: 0 - # spot_allocation_strategy: capacity-optimized - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - "output.mixed_instances_policy_full['launch_template']['overrides'][0]['instance_type'] == 't3.micro'" - # - "output.mixed_instances_policy_full['launch_template']['overrides'][1]['instance_type'] == 't2.nano'" - # - "output.mixed_instances_policy_full['instances_distribution']['on_demand_percentage_above_base_capacity'] == 0" - # - "output.mixed_instances_policy_full['instances_distribution']['spot_allocation_strategy'] == 'capacity-optimized'" - - # # ============================================================ - - # # Target group names have max length of 32 characters - # - set_fact: - # tg1_name: "{{ (resource_prefix + '-tg1' ) | regex_search('(.{1,32})$') }}" - # tg2_name: "{{ (resource_prefix + '-tg2' ) | regex_search('(.{1,32})$') }}" - - # - name: create target group 1 - # elb_target_group: - # name: "{{ tg1_name }}" - # protocol: tcp - # port: 80 - # health_check_protocol: tcp - # health_check_port: 80 - # healthy_threshold_count: 2 - # unhealthy_threshold_count: 2 - # vpc_id: "{{ testing_vpc.vpc.id }}" - # state: present - # register: out_tg1 - - # - name: create target group 2 - # elb_target_group: - # name: "{{ tg2_name }}" - # protocol: tcp - # port: 80 - # health_check_protocol: tcp - # health_check_port: 80 - # healthy_threshold_count: 2 - # unhealthy_threshold_count: 2 - # vpc_id: "{{ testing_vpc.vpc.id }}" - # state: present - # register: out_tg2 - - # - name: update autoscaling group with tg1 - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_template: - # launch_template_name: "{{ resource_prefix }}-lt" - # target_group_arns: - # - "{{ out_tg1.target_group_arn }}" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # state: present - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - output.target_group_arns[0] == out_tg1.target_group_arn - - # - name: update autoscaling group add tg2 - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_template: - # launch_template_name: "{{ resource_prefix }}-lt" - # target_group_arns: - # - "{{ out_tg1.target_group_arn }}" - # - "{{ out_tg2.target_group_arn }}" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # state: present - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - "output.target_group_arns | length == 2" - - # - name: update autoscaling group remove tg1 - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_template: - # launch_template_name: "{{ resource_prefix }}-lt" - # target_group_arns: - # - "{{ out_tg2.target_group_arn }}" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # state: present - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - "output.target_group_arns | length == 1" - # - "output.target_group_arns[0] == out_tg2.target_group_arn" - - # - name: update autoscaling group remove tg2 and add tg1 - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_template: - # launch_template_name: "{{ resource_prefix }}-lt" - # target_group_arns: - # - "{{ out_tg1.target_group_arn }}" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # state: present - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - "output.target_group_arns | length == 1" - # - "output.target_group_arns[0] == out_tg1.target_group_arn" - - # - name: target group no change - # ec2_asg: - # name: "{{ resource_prefix }}-asg" - # launch_template: - # launch_template_name: "{{ resource_prefix }}-lt" - # target_group_arns: - # - "{{ out_tg1.target_group_arn }}" - # desired_capacity: 1 - # min_size: 1 - # max_size: 1 - # state: present - # wait_for_instances: yes - # register: output - - # - assert: - # that: - # - "output.target_group_arns | length == 1" - # - "output.target_group_arns[0] == out_tg1.target_group_arn" - # - "output.changed == false" + # ============================================================ + + - name: launch asg and wait for instances to be deemed healthy (no ELB) + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + desired_capacity: 1 + min_size: 1 + max_size: 1 + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + state: present + wait_for_instances: yes + register: output + + - assert: + that: + - "output.viable_instances == 1" + + - name: Tag asg + ec2_asg: + name: "{{ resource_prefix }}-asg" + tags: + - tag_a: 'value 1' + propagate_at_launch: no + - tag_b: 'value 2' + propagate_at_launch: yes + register: output + + - assert: + that: + - "output.tags | length == 2" + - output is changed + + - name: Re-Tag asg (different order) + ec2_asg: + name: "{{ resource_prefix }}-asg" + tags: + - tag_b: 'value 2' + propagate_at_launch: yes + - tag_a: 'value 1' + propagate_at_launch: no + register: output + + - assert: + that: + - "output.tags | length == 2" + - output is not changed + + - name: Re-Tag asg new tags + ec2_asg: + name: "{{ resource_prefix }}-asg" + tags: + - tag_c: 'value 3' + propagate_at_launch: no + register: output + + - assert: + that: + - "output.tags | length == 1" + - output is changed + + - name: Re-Tag asg update propagate_at_launch + ec2_asg: + name: "{{ resource_prefix }}-asg" + tags: + - tag_c: 'value 3' + propagate_at_launch: yes + register: output + + - assert: + that: + - "output.tags | length == 1" + - output is changed + + - name: Enable metrics collection + ec2_asg: + name: "{{ resource_prefix }}-asg" + metrics_collection: yes + register: output + + - assert: + that: + - output is changed + + - name: Enable metrics collection (check idempotency) + ec2_asg: + name: "{{ resource_prefix }}-asg" + metrics_collection: yes + register: output + + - assert: + that: + - output is not changed + + - name: Disable metrics collection + ec2_asg: + name: "{{ resource_prefix }}-asg" + metrics_collection: no + register: output + + - assert: + that: + - output is changed + + - name: Disable metrics collection (check idempotency) + ec2_asg: + name: "{{ resource_prefix }}-asg" + metrics_collection: no + register: output + + - assert: + that: + - output is not changed + + - name: kill asg + ec2_asg: + name: "{{ resource_prefix }}-asg" + state: absent + wait_timeout: 800 + async: 400 + + # ============================================================ + + - name: launch asg and do not wait for instances to be deemed healthy (no ELB) + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + desired_capacity: 1 + min_size: 1 + max_size: 1 + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + wait_for_instances: no + state: present + register: output + + - assert: + that: + - "output.viable_instances == 0" + + - name: kill asg + ec2_asg: + name: "{{ resource_prefix }}-asg" + state: absent + wait_timeout: 800 + register: output + retries: 3 + until: output is succeeded + delay: 10 + async: 400 + + # ============================================================ + + - name: create asg with asg metrics enabled + ec2_asg: + name: "{{ resource_prefix }}-asg" + metrics_collection: true + launch_config_name: "{{ resource_prefix }}-lc" + desired_capacity: 0 + min_size: 0 + max_size: 0 + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + state: present + register: output + + - assert: + that: + - "'Group' in output.metrics_collection.0.Metric" + + - name: kill asg + ec2_asg: + name: "{{ resource_prefix }}-asg" + state: absent + wait_timeout: 800 + async: 400 + + # ============================================================ + + - name: launch load balancer + ec2_elb_lb: + name: "{{ load_balancer_name }}" + state: present + security_group_ids: + - "{{ sg.group_id }}" + subnets: "{{ testing_subnet.subnet.id }}" + connection_draining_timeout: 60 + listeners: + - protocol: http + load_balancer_port: 80 + instance_port: 80 + health_check: + ping_protocol: tcp + ping_port: 80 + ping_path: "/" + response_timeout: 5 + interval: 10 + unhealthy_threshold: 4 + healthy_threshold: 2 + register: load_balancer + + - name: launch asg and wait for instances to be deemed healthy (ELB) + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + health_check_type: ELB + desired_capacity: 1 + min_size: 1 + max_size: 1 + health_check_period: 300 + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + load_balancers: "{{ load_balancer_name }}" + wait_for_instances: yes + wait_timeout: 900 + state: present + register: output + + - assert: + that: + - "output.viable_instances == 1" + + # ============================================================ + + # grow scaling group to 3 + - name: add 2 more instances wait for instances to be deemed healthy (ELB) + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + health_check_type: ELB + desired_capacity: 3 + min_size: 3 + max_size: 5 + health_check_period: 600 + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + load_balancers: "{{ load_balancer_name }}" + wait_for_instances: yes + wait_timeout: 1200 + state: present + register: output + + - assert: + that: + - "output.viable_instances == 3" + + # ============================================================ + + # Test max_instance_lifetime option + - name: enable asg max_instance_lifetime + ec2_asg: + name: "{{ resource_prefix }}-asg" + max_instance_lifetime: 604801 + register: output + + - name: ensure max_instance_lifetime is set + assert: + that: + - output.max_instance_lifetime == 604801 + + - name: run without max_instance_lifetime + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + + - name: ensure max_instance_lifetime not affected by defaults + assert: + that: + - output.max_instance_lifetime == 604801 + + - name: disable asg max_instance_lifetime + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + max_instance_lifetime: 0 + register: output + + - name: ensure max_instance_lifetime is not set + assert: + that: + - not output.max_instance_lifetime + + # ============================================================ + + # perform rolling replace with different launch configuration + - name: perform rolling update to new AMI + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc-2" + health_check_type: ELB + desired_capacity: 3 + min_size: 1 + max_size: 5 + health_check_period: 900 + load_balancers: "{{ load_balancer_name }}" + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + wait_for_instances: yes + replace_all_instances: yes + wait_timeout: 1800 + state: present + register: output + + # ensure that all instances have new launch config + - assert: + that: + - "item.value.launch_config_name == '{{ resource_prefix }}-lc-2'" + loop: "{{ output.instance_facts | dict2items }}" + + # assert they are all healthy and that the rolling update resulted in the appropriate number of instances + - assert: + that: + - "output.viable_instances == 3" + + # ============================================================ + + # perform rolling replace with the original launch configuration + - name: perform rolling update to new AMI while removing the load balancer + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + health_check_type: EC2 + desired_capacity: 3 + min_size: 1 + max_size: 5 + health_check_period: 900 + load_balancers: [] + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + wait_for_instances: yes + replace_all_instances: yes + wait_timeout: 1800 + state: present + register: output + + # ensure that all instances have new launch config + - assert: + that: + - "item.value.launch_config_name == '{{ resource_prefix }}-lc'" + loop: "{{ output.instance_facts | dict2items }}" + + # assert they are all healthy and that the rolling update resulted in the appropriate number of instances + # there should be the same number of instances as there were before the rolling update was performed + - assert: + that: + - "output.viable_instances == 3" + + # ============================================================ + + # perform rolling replace with new launch configuration and lc_check:false + - name: "perform rolling update to new AMI with lc_check: false" + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc-2" + health_check_type: EC2 + desired_capacity: 3 + min_size: 1 + max_size: 5 + health_check_period: 900 + load_balancers: [] + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + wait_for_instances: yes + replace_all_instances: yes + replace_batch_size: 3 + lc_check: false + wait_timeout: 1800 + state: present + + # Collect ec2_asg_info + - name: get ec2_asg info + ec2_asg_info: + name: "{{ resource_prefix }}-asg" + register: output + + # Since we started with 3 instances and replace all of them. + # We should see only 3 instances total. + - assert: + that: + - output.results[0].instances | length == 3 + + # ============================================================ + + - name: kill asg + ec2_asg: + name: "{{ resource_prefix }}-asg" + state: absent + wait_timeout: 800 + async: 400 + + # Create new asg with replace_all_instances and lc_check:false + - name: "new asg with lc_check: false" + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_config_name: "{{ resource_prefix }}-lc" + health_check_type: EC2 + desired_capacity: 3 + min_size: 1 + max_size: 5 + health_check_period: 900 + load_balancers: [] + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + wait_for_instances: yes + replace_all_instances: yes + replace_batch_size: 3 + lc_check: false + wait_timeout: 1800 + state: present + + # Collect ec2_asg_info + - name: get ec2_asg information + ec2_asg_info: + name: "{{ resource_prefix }}-asg" + register: output + + # Get all instance_ids we saw and assert we saw number expected + # Should only see 3 (don't replace instances we just created) + - assert: + that: + - output.results[0].instances | length == 3 + + # we need a launch template, otherwise we cannot test the mixed instance policy + - name: create launch template for autoscaling group to test its mixed instances policy + ec2_launch_template: + template_name: "{{ resource_prefix }}-lt" + image_id: "{{ ec2_ami_image }}" + instance_type: t3.micro + credit_specification: + cpu_credits: standard + network_interfaces: + - associate_public_ip_address: yes + delete_on_termination: yes + device_index: 0 + groups: + - "{{ sg.group_id }}" + + - name: update autoscaling group with mixed-instances policy with mixed instances types + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_template: + launch_template_name: "{{ resource_prefix }}-lt" + desired_capacity: 1 + min_size: 1 + max_size: 1 + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + state: present + mixed_instances_policy: + instance_types: + - t3.micro + - t2.nano + wait_for_instances: yes + register: output + + - assert: + that: + - "output.mixed_instances_policy | length == 2" + - "output.mixed_instances_policy[0] == 't3.micro'" + - "output.mixed_instances_policy[1] == 't2.nano'" + + - name: update autoscaling group with mixed-instances policy with instances_distribution + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_template: + launch_template_name: "{{ resource_prefix }}-lt" + desired_capacity: 1 + min_size: 1 + max_size: 1 + vpc_zone_identifier: "{{ testing_subnet.subnet.id }}" + state: present + mixed_instances_policy: + instance_types: + - t3.micro + - t2.nano + instances_distribution: + on_demand_percentage_above_base_capacity: 0 + spot_allocation_strategy: capacity-optimized + wait_for_instances: yes + register: output + + - assert: + that: + - "output.mixed_instances_policy_full['launch_template']['overrides'][0]['instance_type'] == 't3.micro'" + - "output.mixed_instances_policy_full['launch_template']['overrides'][1]['instance_type'] == 't2.nano'" + - "output.mixed_instances_policy_full['instances_distribution']['on_demand_percentage_above_base_capacity'] == 0" + - "output.mixed_instances_policy_full['instances_distribution']['spot_allocation_strategy'] == 'capacity-optimized'" + + # ============================================================ + + # Target group names have max length of 32 characters + - set_fact: + tg1_name: "{{ (resource_prefix + '-tg1' ) | regex_search('(.{1,32})$') }}" + tg2_name: "{{ (resource_prefix + '-tg2' ) | regex_search('(.{1,32})$') }}" + + - name: create target group 1 + elb_target_group: + name: "{{ tg1_name }}" + protocol: tcp + port: 80 + health_check_protocol: tcp + health_check_port: 80 + healthy_threshold_count: 2 + unhealthy_threshold_count: 2 + vpc_id: "{{ testing_vpc.vpc.id }}" + state: present + register: out_tg1 + + - name: create target group 2 + elb_target_group: + name: "{{ tg2_name }}" + protocol: tcp + port: 80 + health_check_protocol: tcp + health_check_port: 80 + healthy_threshold_count: 2 + unhealthy_threshold_count: 2 + vpc_id: "{{ testing_vpc.vpc.id }}" + state: present + register: out_tg2 + + - name: update autoscaling group with tg1 + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_template: + launch_template_name: "{{ resource_prefix }}-lt" + target_group_arns: + - "{{ out_tg1.target_group_arn }}" + desired_capacity: 1 + min_size: 1 + max_size: 1 + state: present + wait_for_instances: yes + register: output + + - assert: + that: + - output.target_group_arns[0] == out_tg1.target_group_arn + + - name: update autoscaling group add tg2 + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_template: + launch_template_name: "{{ resource_prefix }}-lt" + target_group_arns: + - "{{ out_tg1.target_group_arn }}" + - "{{ out_tg2.target_group_arn }}" + desired_capacity: 1 + min_size: 1 + max_size: 1 + state: present + wait_for_instances: yes + register: output + + - assert: + that: + - "output.target_group_arns | length == 2" + + - name: update autoscaling group remove tg1 + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_template: + launch_template_name: "{{ resource_prefix }}-lt" + target_group_arns: + - "{{ out_tg2.target_group_arn }}" + desired_capacity: 1 + min_size: 1 + max_size: 1 + state: present + wait_for_instances: yes + register: output + + - assert: + that: + - "output.target_group_arns | length == 1" + - "output.target_group_arns[0] == out_tg2.target_group_arn" + + - name: update autoscaling group remove tg2 and add tg1 + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_template: + launch_template_name: "{{ resource_prefix }}-lt" + target_group_arns: + - "{{ out_tg1.target_group_arn }}" + desired_capacity: 1 + min_size: 1 + max_size: 1 + state: present + wait_for_instances: yes + register: output + + - assert: + that: + - "output.target_group_arns | length == 1" + - "output.target_group_arns[0] == out_tg1.target_group_arn" + + - name: target group no change + ec2_asg: + name: "{{ resource_prefix }}-asg" + launch_template: + launch_template_name: "{{ resource_prefix }}-lt" + target_group_arns: + - "{{ out_tg1.target_group_arn }}" + desired_capacity: 1 + min_size: 1 + max_size: 1 + state: present + wait_for_instances: yes + register: output + + - assert: + that: + - "output.target_group_arns | length == 1" + - "output.target_group_arns[0] == out_tg1.target_group_arn" + - "output.changed == false" # ============================================================ From 3600e026631af961e6c2cf13654dbf26143081f8 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Mon, 21 Feb 2022 13:20:26 -0800 Subject: [PATCH 08/12] Minor: add NewLine at EOF --- tests/integration/targets/ec2_asg/tasks/instance_detach.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml index 54302ca3a0f..d397c266089 100644 --- a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -205,4 +205,4 @@ register: removed until: removed is not failed ignore_errors: yes - retries: 10 \ No newline at end of file + retries: 10 From ec712d70d8bc97381af647b5382d46d0b8ed7c4f Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Mon, 21 Feb 2022 16:58:58 -0800 Subject: [PATCH 09/12] Handle detaching instances which are not in ASG --- plugins/modules/ec2_asg.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 504680b367f..2d222f1ee18 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -1553,24 +1553,24 @@ def detach(connection): props = get_properties(as_group) instances = props['instances'] - # check if provided instance exists in asg + # check if provided instance exists in asg, create list of instances to detach which exist in asg + instances_to_detach = [] for instance_id in detach_instances: - if instance_id not in instances: - module.fail_json(msg="Provided instance ID: {0} does not belong to the AutoScalingGroup: {1}".format( - instance_id, group_name)) + if instance_id in instances: + instances_to_detach.append(instance_id) # check if setting decrement_desired_capacity will make desired_capacity smaller # than the currently set minimum size in ASG configuration if decrement_desired_capacity: - decremented_desired_capacity = len(instances) - len(detach_instances) + decremented_desired_capacity = len(instances) - len(instances_to_detach) if min_size and min_size > decremented_desired_capacity: module.fail_json( msg="Detaching instance(s) with 'decrement_desired_capacity' flag set reduces number of instances below min_size,\ please update AutoScalingGroup Sizes properly.") - if detach_instances: + if instances_to_detach: try: - detach_asg_instances(connection, detach_instances, group_name, decrement_desired_capacity) + detach_asg_instances(connection, instances_to_detach, group_name, decrement_desired_capacity) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to describe launch configurations") From 5c1bbbabc3c8c0c19fac7a809f316238f7d62cef Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Tue, 22 Feb 2022 10:53:34 -0800 Subject: [PATCH 10/12] "Minor fix: improve error output message" --- plugins/modules/ec2_asg.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 2d222f1ee18..f42c0e9101c 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -1565,14 +1565,14 @@ def detach(connection): decremented_desired_capacity = len(instances) - len(instances_to_detach) if min_size and min_size > decremented_desired_capacity: module.fail_json( - msg="Detaching instance(s) with 'decrement_desired_capacity' flag set reduces number of instances below min_size,\ - please update AutoScalingGroup Sizes properly.") + msg="Detaching instance(s) with 'decrement_desired_capacity' flag set reduces number of instances to {0}\ + which is below current min_size {1}, please update AutoScalingGroup Sizes properly.".format(decremented_desired_capacity, min_size)) if instances_to_detach: try: detach_asg_instances(connection, instances_to_detach, group_name, decrement_desired_capacity) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - module.fail_json_aws(e, msg="Failed to describe launch configurations") + module.fail_json_aws(e, msg="Failed to detach instances from AutoScaling Group") asg_properties = get_properties(as_group) return True, asg_properties From 91ae697f9cc0c2d6fcd716ee847ca6392491d4eb Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Thu, 24 Feb 2022 15:27:12 -0800 Subject: [PATCH 11/12] Fix launch_config not found error in task --- tests/integration/targets/ec2_asg/tasks/instance_detach.yml | 4 ++-- tests/integration/targets/ec2_asg/tasks/main.yml | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml index d397c266089..da574c2ebf2 100644 --- a/tests/integration/targets/ec2_asg/tasks/instance_detach.yml +++ b/tests/integration/targets/ec2_asg/tasks/instance_detach.yml @@ -85,7 +85,7 @@ # pause to allow completion of instance replacement - name: Pause for 1 minute pause: - minutes: 1 + seconds: 30 # gather info about asg and get instance ids - ec2_asg_info: @@ -139,7 +139,7 @@ - name: Pause for 1 minute to allow completion of above task pause: - minutes: 1 + seconds: 30 # gather information about asg and get instance id - ec2_asg_info: diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index 99d887d1237..68d2f9175a2 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -97,6 +97,8 @@ cidr_ip: 0.0.0.0/0 register: sg + - include_tasks: instance_detach.yml + - name: ensure launch configs exist ec2_lc: name: "{{ item }}" @@ -116,8 +118,6 @@ - "{{ resource_prefix }}-lc" - "{{ resource_prefix }}-lc-2" - - include_tasks: instance_detach.yml - # ============================================================ - name: launch asg and wait for instances to be deemed healthy (no ELB) @@ -725,7 +725,6 @@ - "output.target_group_arns[0] == out_tg1.target_group_arn" - "output.changed == false" - # ============================================================ always: From 3f30eee7d0a5f090f4d9e2a42f0fab3c0010d07c Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Fri, 25 Feb 2022 08:52:32 -0800 Subject: [PATCH 12/12] Minor fix in docs --- plugins/modules/ec2_asg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index f42c0e9101c..8dc7cd783f2 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -185,8 +185,8 @@ detach_instances: description: - Removes one or more instances from the specified AutoScalingGroup. - - If I(decrement_desired_capacity) flag is not set, new instance(s) are lauched to replace the detached instance(s). - - If a Classic Load Balancer attached to the AutoScalingGroup, the instances are also deregistered from the load balancer. + - If I(decrement_desired_capacity) flag is not set, new instance(s) are launched to replace the detached instance(s). + - If a Classic Load Balancer is attached to the AutoScalingGroup, the instances are also deregistered from the load balancer. - If there are target groups attached to the AutoScalingGroup, the instances are also deregistered from the target groups. type: list elements: str