Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to provide instance requirements options to ASG module #831

Closed
4 changes: 4 additions & 0 deletions changelogs/fragments/831-ec2_asg.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
minor_changes:
- ec2_asg - Allow defining instance requirements in the mixed instances policy for flexible instance picking by AWS
breaking_changes:
- ec2_asg - The return value of mixed_instances_policy does now return the actual mixed instance policy instead of just a list of instance types (that might not always defined in such a policy)
stefanhorning marked this conversation as resolved.
Show resolved Hide resolved
47 changes: 38 additions & 9 deletions plugins/modules/ec2_asg.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@
- 'See also U(https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscaling-autoscalinggroup-mixedinstancespolicy.html)'
required: false
suboptions:
instance_requirements:
description:
- A set of params describing the specs instances should have
type: dict
required: false
version_added: 3.2.0
stefanhorning marked this conversation as resolved.
Show resolved Hide resolved
instance_types:
description:
- A list of instance_types.
Expand Down Expand Up @@ -431,6 +437,25 @@
tags:
- environment: production
propagate_at_launch: no

# Another example with Launch Template using mixed instance policy and only instance requirements (not specificing instance types)

- community.aws.ec2_asg:
name: special_mixed
launch_template:
launch_template_name: 'lt-example'
mixed_instances_policy:
instance_requirements:
v_cpu_count: { min: 5, max: 16 }
memory_mi_b: { min: 20480, max: 112640 }
instances_distribution:
on_demand_percentage_above_base_capacity: 0
spot_allocation_strategy: capacity-optimized
min_size: 1
max_size: 10
desired_capacity: 5
vpc_zone_identifier: [ 'subnet-abcd1234', 'subnet-1a2b3c4d' ]

'''

RETURN = r'''
Expand Down Expand Up @@ -533,11 +558,6 @@
type: int
sample: 1
mixed_instances_policy:
description: Returns the list of instance types if a mixed instances policy is set.
returned: success
type: list
sample: ["t3.micro", "t3a.micro"]
mixed_instances_policy_full:
description: Returns the full dictionary representation of the mixed instances policy if a mixed instances policy is set.
returned: success
type: dict
Expand Down Expand Up @@ -864,8 +884,9 @@ def get_properties(autoscaling_group):
properties['vpc_zone_identifier'] = autoscaling_group.get('VPCZoneIdentifier')
raw_mixed_instance_object = autoscaling_group.get('MixedInstancesPolicy')
if raw_mixed_instance_object:
properties['mixed_instances_policy_full'] = camel_dict_to_snake_dict(raw_mixed_instance_object)
properties['mixed_instances_policy'] = [x['InstanceType'] for x in raw_mixed_instance_object.get('LaunchTemplate').get('Overrides')]
properties['mixed_instances_policy'] = camel_dict_to_snake_dict(raw_mixed_instance_object)
stefanhorning marked this conversation as resolved.
Show resolved Hide resolved
# for backwards compatibility
properties['mixed_instances_full'] = properties['mixed_instances_policy']

metrics = autoscaling_group.get('EnabledMetrics')
if metrics:
Expand Down Expand Up @@ -915,17 +936,21 @@ def get_launch_object(connection, ec2_connection):

if mixed_instances_policy:
instance_types = mixed_instances_policy.get('instance_types', [])
instance_requirements = mixed_instances_policy.get('instance_requirements', {})
instances_distribution = mixed_instances_policy.get('instances_distribution', {})
policy = {
'LaunchTemplate': {
'LaunchTemplateSpecification': launch_object['LaunchTemplate']
'LaunchTemplateSpecification': launch_object['LaunchTemplate'],
'Overrides': []
}
}
if instance_types:
policy['LaunchTemplate']['Overrides'] = []
for instance_type in instance_types:
instance_type_dict = {'InstanceType': instance_type}
policy['LaunchTemplate']['Overrides'].append(instance_type_dict)
if instance_requirements:
instance_requirements_dict = {'InstanceRequirements': snake_dict_to_camel_dict(instance_requirements, capitalize_first=True)}
policy['LaunchTemplate']['Overrides'].append(instance_requirements_dict)
if instances_distribution:
instances_distribution_params = scrub_none_parameters(instances_distribution)
policy['InstancesDistribution'] = snake_dict_to_camel_dict(instances_distribution_params, capitalize_first=True)
Expand Down Expand Up @@ -1834,6 +1859,10 @@ def main():
type='list',
elements='str'
),
instance_requirements=dict(
type='dict',
default=None
),
instances_distribution=dict(
type='dict',
default=None,
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/targets/ec2_asg/roles/ec2_asg/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# meta file for ec2_asg

dependencies:
- role: setup_botocore_pip
vars:
boto3_version: "1.20.27"
botocore_version: "1.23.27"
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
that:
- "result.msg == 'missing required arguments: name'"


- name: ensure launch configs exist
ec2_lc:
name: "{{ item }}"
Expand Down Expand Up @@ -485,9 +484,9 @@

- assert:
that:
- "output.mixed_instances_policy | length == 2"
- "output.mixed_instances_policy[0] == 't3.micro'"
- "output.mixed_instances_policy[1] == 't2.nano'"
- "output.mixed_instances_policy.launch_template.overrides | length == 2"
- "output.mixed_instances_policy.launch_template.overrides[0].instance_type == 't3.micro'"
- "output.mixed_instances_policy.launch_template.overrides[1].instance_type == 't2.nano'"

- name: update autoscaling group with mixed-instances policy with instances_distribution
ec2_asg:
Expand All @@ -511,10 +510,40 @@

- 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'"
- "output.mixed_instances_policy.launch_template.overrides[0].instance_type == 't3.micro'"
- "output.mixed_instances_policy.launch_template.overrides[1].instance_type == 't2.nano'"
- "output.mixed_instances_policy.instances_distribution.on_demand_percentage_above_base_capacity == 0"
- "output.mixed_instances_policy.instances_distribution.spot_allocation_strategy == 'capacity-optimized'"

- name: update autoscaling group with mixed-instances policy with instances_requirements
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_requirements:
Copy link
Contributor

@mandar242 mandar242 Apr 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also seems that as InstanceRequirements was added in 1.19.5 of boto3 , could you also add boto3 version used by integration tests for this module?

so it would look something like these:

https://github.com/ansible-collections/community.aws/blob/main/tests/integration/targets/ec2_asg_scheduled_action/meta/main.yml

https://github.com/ansible-collections/community.aws/blob/main/tests/integration/targets/ec2_asg_scheduled_action/tasks/main.yml#L15

This should resolve CI failure for this task.

v_cpu_count: { min: 2, max: 8 }
memory_mi_b: { min: 1024, max: 4096 }
instances_distribution:
on_demand_allocation_strategy: lowest-price
on_demand_percentage_above_base_capacity: 0
spot_allocation_strategy: lowest-price
spot_instance_pools: 15 # only available when spot_allocation_strategy is 'lowest-price
wait_for_instances: yes
register: output

- assert:
that:
- output is changed
- "output.mixed_instances_policy.launch_template.overrides[0].instance_requirements == { memory_mi_b: { min: 1024, max: 4096 }, v_cpu_count: { min: 2, max: 8 } }"
- "output.mixed_instances_policy.instances_distribution.on_demand_percentage_above_base_capacity == 0"
- "output.mixed_instances_policy.instances_distribution.spot_allocation_strategy == 'lowest-price'"


# ============================================================

Expand Down
47 changes: 28 additions & 19 deletions tests/integration/targets/ec2_asg/roles/ec2_asg/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,32 @@
collections:
- community.aws
block:
- debug:
msg: "{{ inventory_hostname }} start: {{ lookup('pipe','date') }}"
- include_tasks: '{{ inventory_hostname }}.yml'
- debug:
msg: "{{ inventory_hostname }} finish: {{ lookup('pipe','date') }}"

always:
- set_fact:
_role_complete: True
- vars:
completed_hosts: '{{ ansible_play_hosts_all | map("extract", hostvars, "_role_complete") | list | select("defined") | list | length }}'
hosts_in_play: '{{ ansible_play_hosts_all | length }}'
debug:
msg: "{{ completed_hosts }} of {{ hosts_in_play }} complete"
- include_tasks: env_cleanup.yml
- name: Run tests in virtualenv to newer botocore version
vars:
completed_hosts: '{{ ansible_play_hosts_all | map("extract", hostvars, "_role_complete") | list | select("defined") | list | length }}'
hosts_in_play: '{{ ansible_play_hosts_all | length }}'
when:
- completed_hosts == hosts_in_play
ansible_python_interpreter: "{{ botocore_virtualenv_interpreter }}"
block:

- debug:
msg: "{{ inventory_hostname }} start: {{ lookup('pipe','date') }}"

- include_tasks: '{{ inventory_hostname }}.yml'

- debug:
msg: "{{ inventory_hostname }} finish: {{ lookup('pipe','date') }}"

always:
- set_fact:
_role_complete: True

- vars:
completed_hosts: '{{ ansible_play_hosts_all | map("extract", hostvars, "_role_complete") | list | select("defined") | list | length }}'
hosts_in_play: '{{ ansible_play_hosts_all | length }}'
debug:
msg: "{{ completed_hosts }} of {{ hosts_in_play }} complete"

- include_tasks: env_cleanup.yml
vars:
completed_hosts: '{{ ansible_play_hosts_all | map("extract", hostvars, "_role_complete") | list | select("defined") | list | length }}'
hosts_in_play: '{{ ansible_play_hosts_all | length }}'
when:
- completed_hosts == hosts_in_play