Skip to content

Commit

Permalink
Merge pull request getmoto#1463 from spulec/feature/add-default-spot-…
Browse files Browse the repository at this point in the history
…price-to-fleet-request

Make SpotPrice optional when requesting a spot fleet
  • Loading branch information
JackDanger committed Feb 2, 2018
2 parents c4e895c + 5f61950 commit 553d074
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 4 deletions.
5 changes: 3 additions & 2 deletions moto/ec2/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2943,7 +2943,7 @@ def create_from_cloudformation_json(cls, resource_name, cloudformation_json, reg
'Properties']['SpotFleetRequestConfigData']
ec2_backend = ec2_backends[region_name]

spot_price = properties['SpotPrice']
spot_price = properties.get('SpotPrice')
target_capacity = properties['TargetCapacity']
iam_fleet_role = properties['IamFleetRole']
allocation_strategy = properties['AllocationStrategy']
Expand Down Expand Up @@ -2977,7 +2977,8 @@ def get_launch_spec_counts(self, weight_to_add):
launch_spec_index += 1
else: # lowestPrice
cheapest_spec = sorted(
self.launch_specs, key=lambda spec: float(spec.spot_price))[0]
# FIXME: change `+inf` to the on demand price scaled to weighted capacity when it's not present
self.launch_specs, key=lambda spec: float(spec.spot_price or '+inf'))[0]
weight_so_far = weight_to_add + (weight_to_add % cheapest_spec.weighted_capacity)
weight_map[cheapest_spec] = int(
weight_so_far // cheapest_spec.weighted_capacity)
Expand Down
6 changes: 5 additions & 1 deletion moto/ec2/responses/spot_fleets.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def modify_spot_fleet_request(self):

def request_spot_fleet(self):
spot_config = self._get_dict_param("SpotFleetRequestConfig.")
spot_price = spot_config['spot_price']
spot_price = spot_config.get('spot_price')
target_capacity = spot_config['target_capacity']
iam_fleet_role = spot_config['iam_fleet_role']
allocation_strategy = spot_config['allocation_strategy']
Expand Down Expand Up @@ -78,7 +78,9 @@ def request_spot_fleet(self):
<spotFleetRequestId>{{ request.id }}</spotFleetRequestId>
<spotFleetRequestState>{{ request.state }}</spotFleetRequestState>
<spotFleetRequestConfig>
{% if request.spot_price %}
<spotPrice>{{ request.spot_price }}</spotPrice>
{% endif %}
<targetCapacity>{{ request.target_capacity }}</targetCapacity>
<iamFleetRole>{{ request.iam_fleet_role }}</iamFleetRole>
<allocationStrategy>{{ request.allocation_strategy }}</allocationStrategy>
Expand All @@ -93,7 +95,9 @@ def request_spot_fleet(self):
<iamInstanceProfile><arn>{{ launch_spec.iam_instance_profile }}</arn></iamInstanceProfile>
<keyName>{{ launch_spec.key_name }}</keyName>
<monitoring><enabled>{{ launch_spec.monitoring }}</enabled></monitoring>
{% if launch_spec.spot_price %}
<spotPrice>{{ launch_spec.spot_price }}</spotPrice>
{% endif %}
<userData>{{ launch_spec.user_data }}</userData>
<weightedCapacity>{{ launch_spec.weighted_capacity }}</weightedCapacity>
<groupSet>
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ freezegun
flask
boto>=2.45.0
boto3>=1.4.4
botocore>=1.5.77
botocore>=1.8.36
six>=1.9
prompt-toolkit==1.0.14
click==6.7
Expand Down
72 changes: 72 additions & 0 deletions tests/test_cloudformation/test_cloudformation_stack_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,78 @@ def test_stack_spot_fleet():
launch_spec['WeightedCapacity'].should.equal(2.0)


@mock_cloudformation()
@mock_ec2()
def test_stack_spot_fleet_should_figure_out_default_price():
conn = boto3.client('ec2', 'us-east-1')

vpc = conn.create_vpc(CidrBlock="10.0.0.0/8")['Vpc']
subnet = conn.create_subnet(
VpcId=vpc['VpcId'], CidrBlock='10.0.0.0/16', AvailabilityZone='us-east-1a')['Subnet']
subnet_id = subnet['SubnetId']

spot_fleet_template = {
'Resources': {
"SpotFleet1": {
"Type": "AWS::EC2::SpotFleet",
"Properties": {
"SpotFleetRequestConfigData": {
"IamFleetRole": "arn:aws:iam::123456789012:role/fleet",
"TargetCapacity": 6,
"AllocationStrategy": "diversified",
"LaunchSpecifications": [
{
"EbsOptimized": "false",
"InstanceType": 't2.small',
"ImageId": "ami-1234",
"SubnetId": subnet_id,
"WeightedCapacity": "2",
},
{
"EbsOptimized": "true",
"InstanceType": 't2.large',
"ImageId": "ami-1234",
"Monitoring": {"Enabled": "true"},
"SecurityGroups": [{"GroupId": "sg-123"}],
"SubnetId": subnet_id,
"IamInstanceProfile": {"Arn": "arn:aws:iam::123456789012:role/fleet"},
"WeightedCapacity": "4",
}
]
}
}
}
}
}
spot_fleet_template_json = json.dumps(spot_fleet_template)

cf_conn = boto3.client('cloudformation', 'us-east-1')
stack_id = cf_conn.create_stack(
StackName='test_stack',
TemplateBody=spot_fleet_template_json,
)['StackId']

stack_resources = cf_conn.list_stack_resources(StackName=stack_id)
stack_resources['StackResourceSummaries'].should.have.length_of(1)
spot_fleet_id = stack_resources[
'StackResourceSummaries'][0]['PhysicalResourceId']

spot_fleet_requests = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs']
len(spot_fleet_requests).should.equal(1)
spot_fleet_request = spot_fleet_requests[0]
spot_fleet_request['SpotFleetRequestState'].should.equal("active")
spot_fleet_config = spot_fleet_request['SpotFleetRequestConfig']

assert 'SpotPrice' not in spot_fleet_config
len(spot_fleet_config['LaunchSpecifications']).should.equal(2)
launch_spec1 = spot_fleet_config['LaunchSpecifications'][0]
launch_spec2 = spot_fleet_config['LaunchSpecifications'][1]

assert 'SpotPrice' not in launch_spec1
assert 'SpotPrice' not in launch_spec2


@mock_ec2
@mock_elbv2
@mock_cloudformation
Expand Down
27 changes: 27 additions & 0 deletions tests/test_ec2/test_spot_fleet.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,30 @@ def test_modify_spot_fleet_request_down_no_terminate_after_custom_terminate():
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'][0]['SpotFleetRequestConfig']
spot_fleet_config['TargetCapacity'].should.equal(1)
spot_fleet_config['FulfilledCapacity'].should.equal(2.0)


@mock_ec2
def test_create_spot_fleet_without_spot_price():
conn = boto3.client("ec2", region_name='us-west-2')
subnet_id = get_subnet_id(conn)

# remove prices to force a fallback to ondemand price
spot_config_without_price = spot_config(subnet_id)
del spot_config_without_price['SpotPrice']
for spec in spot_config_without_price['LaunchSpecifications']:
del spec['SpotPrice']

spot_fleet_id = conn.request_spot_fleet(SpotFleetRequestConfig=spot_config_without_price)['SpotFleetRequestId']
spot_fleet_requests = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs']
len(spot_fleet_requests).should.equal(1)
spot_fleet_request = spot_fleet_requests[0]
spot_fleet_config = spot_fleet_request['SpotFleetRequestConfig']

len(spot_fleet_config['LaunchSpecifications']).should.equal(2)
launch_spec1 = spot_fleet_config['LaunchSpecifications'][0]
launch_spec2 = spot_fleet_config['LaunchSpecifications'][1]

# AWS will figure out the price
assert 'SpotPrice' not in launch_spec1
assert 'SpotPrice' not in launch_spec2

0 comments on commit 553d074

Please sign in to comment.