diff --git a/plugins/modules/ec2_asg.py b/plugins/modules/ec2_asg.py index 152918b6d6c..4231d07b691 100644 --- a/plugins/modules/ec2_asg.py +++ b/plugins/modules/ec2_asg.py @@ -93,6 +93,60 @@ - 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 +393,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 +505,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 @@ -526,6 +605,7 @@ ''' import time +import traceback try: import botocore @@ -537,6 +617,7 @@ from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import snake_dict_to_camel_dict ASG_ATTRIBUTES = ('AvailabilityZones', 'DefaultCooldown', 'DesiredCapacity', 'HealthCheckGracePeriod', 'HealthCheckType', 'LaunchConfigurationName', @@ -740,9 +821,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: @@ -792,6 +871,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'] @@ -802,6 +882,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 = dict( + (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 @@ -1661,6 +1749,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'" # ============================================================