From b97f7092693de5c507d8e5e09db2b5ebf69584d8 Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Wed, 16 Sep 2020 12:27:05 -0400 Subject: [PATCH] Fully support mixed instance policy Previously, setting instances_distribution was not supported. instances_distribution should be supported, to allow users to enable spot instances within their mixed instance ASGs. Note: The type and significance of the mixed_instance_policy has changed. It now captures all of the mixed_instance_policy configuration parameters, rather than just a list of instance types. Fixes #231 --- plugins/modules/ec2_asg.py | 103 ++++++++++++++++-- .../targets/ec2_asg/tasks/main.yml | 14 ++- 2 files changed, 105 insertions(+), 12 deletions(-) diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 568b0fca2ca..136c9c5b4a8 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -93,6 +93,50 @@ - A list of instance_types. type: list elements: str + required: false + instances_distribution: + description: + - Specifies the distribution of On-Demand Instances and Spot Instances, the maximum price to pay for Spot Instances, and how the Auto Scaling group allocates instance types to fulfill On-Demand and Spot capacity. + - 'See also U(https://docs.aws.amazon.com/autoscaling/ec2/APIReference/API_InstancesDistribution.html)' + required: false + type: dict + suboptions: + on_demand_allocation_strategy: + description: + - Indicates how to allocate instance types to fulfill On-Demand capacity. + type: str + required: false + on_demand_base_capacity: + description: + - The minimum amount of the Auto Scaling group's capacity that must be fulfilled by On-Demand Instances. This base portion is provisioned first as your group scales. + - Default if not set is 0. If you leave it set to 0, On-Demand Instances are launched as a percentage of the Auto Scaling group's desired capacity, per the OnDemandPercentageAboveBaseCapacity setting. + type: int + required: false + on_demand_percentage_above_base_capacity: + description: + - Controls the percentages of On-Demand Instances and Spot Instances for your additional capacity beyond OnDemandBaseCapacity. + - Default if not set is 100. If you leave it set to 100, the percentages are 100% for On-Demand Instances and 0% for Spot Instances. + - Valid range: 0 to 100 + type: int + required: false + spot_allocation_strategy: + description: + - Indicates how to allocate instances across Spot Instance pools. + type: str + required: false + spot_instance_pools: + description: + - The number of Spot Instance pools across which to allocate your Spot Instances. The Spot pools are determined from the different instance types in the Overrides array of LaunchTemplate. Default if not set is 2. + - Used only when the Spot allocation strategy is lowest-price. + - Valid Range: Minimum value of 1. Maximum value of 20. + type: int + required: false + spot_max_price: + description: + - The maximum price per unit hour that you are willing to pay for a Spot Instance. If you leave the value of this parameter blank (which is the default), the maximum Spot price is set at the On-Demand price. + - To remove a value that you previously set, include the parameter but leave the value blank. + type: str + required: false type: dict placement_group: description: @@ -339,6 +383,9 @@ - t3a.large - t3.large - t2.large + instances_distribution: + on_demand_percentage_above_base_capacity: 0 + spot_allocation_strategy: capacity-optimized min_size: 1 max_size: 10 desired_capacity: 5 @@ -448,10 +495,32 @@ type: int sample: 1 mixed_instance_policy: - description: Returns the list of instance types if a mixed instance policy is set. + description: Returns the dictionary representation of the mixed instance policy. returned: success - type: list - sample: ["t3.micro", "t3a.micro"] + type: dict + sample: { + "InstancesDistribution": { + "OnDemandAllocationStrategy": "prioritized", + "OnDemandBaseCapacity": 0, + "OnDemandPercentageAboveBaseCapacity": 0, + "SpotAllocationStrategy": "capacity-optimized" + }, + "LaunchTemplate": { + "LaunchTemplateSpecification": { + "LaunchTemplateId": "lt-53c2425cffa544c23", + "LaunchTemplateName": "random-LaunchTemplate", + "Version": "2" + }, + "Overrides": [ + { + "InstanceType": "m5.xlarge" + }, + { + "InstanceType": "m5a.xlarge" + }, + ] + } + } pending_instances: description: Number of instances in pending state returned: success @@ -532,7 +601,8 @@ from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ( AWSRetry, - camel_dict_to_snake_dict + camel_dict_to_snake_dict, + snake_dict_to_camel_dict, ) try: @@ -742,9 +812,7 @@ def get_properties(autoscaling_group): properties['termination_policies'] = autoscaling_group.get('TerminationPolicies') properties['target_group_arns'] = autoscaling_group.get('TargetGroupARNs') 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'] = [x['InstanceType'] for x in raw_mixed_instance_object.get('LaunchTemplate').get('Overrides')] + properties['mixed_instances_policy'] = autoscaling_group.get('MixedInstancesPolicy') metrics = autoscaling_group.get('EnabledMetrics') if metrics: @@ -795,6 +863,7 @@ def get_launch_object(connection, ec2_connection): if mixed_instances_policy: instance_types = mixed_instances_policy.get('instance_types', []) + instances_distribution = mixed_instances_policy.get('instances_distribution', {}) policy = { 'LaunchTemplate': { 'LaunchTemplateSpecification': launch_object['LaunchTemplate'] @@ -805,6 +874,14 @@ def get_launch_object(connection, ec2_connection): for instance_type in instance_types: instance_type_dict = {'InstanceType': instance_type} policy['LaunchTemplate']['Overrides'].append(instance_type_dict) + if instances_distribution: + instances_distribution_params = { + key.capitalize():value # To Pascal-case. + for key, value + in instances_distribution.items() + if value is not None + } + policy['InstancesDistribution'] = snake_dict_to_camel_dict(instances_distribution_params) launch_object['MixedInstancesPolicy'] = policy return launch_object @@ -1700,6 +1777,18 @@ def main(): type='list', elements='str' ), + instances_distribution=dict( + type='dict', + default=None, + options=dict( + on_demand_allocation_strategy = dict(type='str'), + on_demand_base_capacity = dict(type='int'), + on_demand_percentage_above_base_capacity = dict(type='int'), + spot_allocation_strategy = dict(type='str'), + spot_instance_pools = dict(type='int'), + spot_max_price = dict(type='str'), + ) + ) ) ), placement_group=dict(type='str'), diff --git a/tests/integration/targets/ec2_asg/tasks/main.yml b/tests/integration/targets/ec2_asg/tasks/main.yml index aa53e9688ea..7dc0a67a6af 100644 --- a/tests/integration/targets/ec2_asg/tasks/main.yml +++ b/tests/integration/targets/ec2_asg/tasks/main.yml @@ -629,7 +629,7 @@ until: status is finished retries: 200 delay: 15 - + # we need a launch template, otherwise we cannot test the mixed instance policy - name: create launch template for autoscaling group to test its mixed instance policy ec2_launch_template: @@ -648,7 +648,7 @@ - name: update autoscaling group with mixed-instance policy ec2_asg: name: "{{ resource_prefix }}-asg" - launch_template: + launch_template: launch_template_name: "{{ resource_prefix }}-lt" desired_capacity: 1 min_size: 1 @@ -659,14 +659,18 @@ instance_types: - t3.micro - t3a.micro + 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 | length == 2" - - "output.mixed_instances_policy[0] == 't3.micro'" - - "output.mixed_instances_policy[1] == 't3a.micro'" + - "output.mixed_instances_policy['LaunchTemplate']['Overrides'][0]['InstanceType'] == 't3.micro'" + - "output.mixed_instances_policy['LaunchTemplate']['Overrides'][1]['InstanceType'] == 't3a.micro'" + - "output.mixed_instances_policy['InstancesDistribution']['OnDemandPercentageAboveBaseCapacity'] == 0" + - "output.mixed_instances_policy['InstancesDistribution']['SpotallocationStrategy'] == 'capacity-optimized'" # ============================================================