EC2: Add ec2 fleets basic api (#5367)
This commit is contained in:
parent
2d6f04d01c
commit
f743567789
@ -1597,7 +1597,7 @@
|
|||||||
- [X] create_default_vpc
|
- [X] create_default_vpc
|
||||||
- [X] create_dhcp_options
|
- [X] create_dhcp_options
|
||||||
- [X] create_egress_only_internet_gateway
|
- [X] create_egress_only_internet_gateway
|
||||||
- [ ] create_fleet
|
- [X] create_fleet
|
||||||
- [X] create_flow_logs
|
- [X] create_flow_logs
|
||||||
- [ ] create_fpga_image
|
- [ ] create_fpga_image
|
||||||
- [X] create_image
|
- [X] create_image
|
||||||
@ -1665,7 +1665,7 @@
|
|||||||
- [X] delete_customer_gateway
|
- [X] delete_customer_gateway
|
||||||
- [ ] delete_dhcp_options
|
- [ ] delete_dhcp_options
|
||||||
- [X] delete_egress_only_internet_gateway
|
- [X] delete_egress_only_internet_gateway
|
||||||
- [ ] delete_fleets
|
- [X] delete_fleets
|
||||||
- [X] delete_flow_logs
|
- [X] delete_flow_logs
|
||||||
- [ ] delete_fpga_image
|
- [ ] delete_fpga_image
|
||||||
- [ ] delete_instance_event_window
|
- [ ] delete_instance_event_window
|
||||||
@ -1757,8 +1757,8 @@
|
|||||||
- [ ] describe_fast_launch_images
|
- [ ] describe_fast_launch_images
|
||||||
- [ ] describe_fast_snapshot_restores
|
- [ ] describe_fast_snapshot_restores
|
||||||
- [ ] describe_fleet_history
|
- [ ] describe_fleet_history
|
||||||
- [ ] describe_fleet_instances
|
- [X] describe_fleet_instances
|
||||||
- [ ] describe_fleets
|
- [X] describe_fleets
|
||||||
- [X] describe_flow_logs
|
- [X] describe_flow_logs
|
||||||
- [ ] describe_fpga_image_attribute
|
- [ ] describe_fpga_image_attribute
|
||||||
- [ ] describe_fpga_images
|
- [ ] describe_fpga_images
|
||||||
|
@ -85,7 +85,7 @@ ec2
|
|||||||
- [X] create_default_vpc
|
- [X] create_default_vpc
|
||||||
- [X] create_dhcp_options
|
- [X] create_dhcp_options
|
||||||
- [X] create_egress_only_internet_gateway
|
- [X] create_egress_only_internet_gateway
|
||||||
- [ ] create_fleet
|
- [X] create_fleet
|
||||||
- [X] create_flow_logs
|
- [X] create_flow_logs
|
||||||
- [ ] create_fpga_image
|
- [ ] create_fpga_image
|
||||||
- [X] create_image
|
- [X] create_image
|
||||||
@ -157,7 +157,7 @@ ec2
|
|||||||
- [X] delete_customer_gateway
|
- [X] delete_customer_gateway
|
||||||
- [ ] delete_dhcp_options
|
- [ ] delete_dhcp_options
|
||||||
- [X] delete_egress_only_internet_gateway
|
- [X] delete_egress_only_internet_gateway
|
||||||
- [ ] delete_fleets
|
- [X] delete_fleets
|
||||||
- [X] delete_flow_logs
|
- [X] delete_flow_logs
|
||||||
- [ ] delete_fpga_image
|
- [ ] delete_fpga_image
|
||||||
- [ ] delete_instance_event_window
|
- [ ] delete_instance_event_window
|
||||||
@ -253,8 +253,8 @@ ec2
|
|||||||
- [ ] describe_fast_launch_images
|
- [ ] describe_fast_launch_images
|
||||||
- [ ] describe_fast_snapshot_restores
|
- [ ] describe_fast_snapshot_restores
|
||||||
- [ ] describe_fleet_history
|
- [ ] describe_fleet_history
|
||||||
- [ ] describe_fleet_instances
|
- [X] describe_fleet_instances
|
||||||
- [ ] describe_fleets
|
- [X] describe_fleets
|
||||||
- [X] describe_flow_logs
|
- [X] describe_flow_logs
|
||||||
- [ ] describe_fpga_image_attribute
|
- [ ] describe_fpga_image_attribute
|
||||||
- [ ] describe_fpga_images
|
- [ ] describe_fpga_images
|
||||||
|
@ -14,6 +14,7 @@ from .dhcp_options import DHCPOptionsSetBackend
|
|||||||
from .elastic_block_store import EBSBackend
|
from .elastic_block_store import EBSBackend
|
||||||
from .elastic_ip_addresses import ElasticAddressBackend
|
from .elastic_ip_addresses import ElasticAddressBackend
|
||||||
from .elastic_network_interfaces import NetworkInterfaceBackend
|
from .elastic_network_interfaces import NetworkInterfaceBackend
|
||||||
|
from .fleets import FleetsBackend
|
||||||
from .flow_logs import FlowLogsBackend
|
from .flow_logs import FlowLogsBackend
|
||||||
from .key_pairs import KeyPairBackend
|
from .key_pairs import KeyPairBackend
|
||||||
from .launch_templates import LaunchTemplateBackend
|
from .launch_templates import LaunchTemplateBackend
|
||||||
@ -124,6 +125,7 @@ class EC2Backend(
|
|||||||
LaunchTemplateBackend,
|
LaunchTemplateBackend,
|
||||||
IamInstanceProfileAssociationBackend,
|
IamInstanceProfileAssociationBackend,
|
||||||
CarrierGatewayBackend,
|
CarrierGatewayBackend,
|
||||||
|
FleetsBackend,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Implementation of the AWS EC2 endpoint.
|
Implementation of the AWS EC2 endpoint.
|
||||||
|
313
moto/ec2/models/fleets.py
Normal file
313
moto/ec2/models/fleets.py
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from moto.ec2.models.spot_requests import SpotFleetLaunchSpec
|
||||||
|
|
||||||
|
from .core import TaggedEC2Resource
|
||||||
|
from ..utils import (
|
||||||
|
random_fleet_id,
|
||||||
|
convert_tag_spec,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Fleet(TaggedEC2Resource):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
ec2_backend,
|
||||||
|
fleet_id,
|
||||||
|
on_demand_options,
|
||||||
|
spot_options,
|
||||||
|
target_capacity_specification,
|
||||||
|
launch_template_configs,
|
||||||
|
excess_capacity_termination_policy,
|
||||||
|
replace_unhealthy_instances,
|
||||||
|
terminate_instances_with_expiration,
|
||||||
|
fleet_type,
|
||||||
|
valid_from,
|
||||||
|
valid_until,
|
||||||
|
tag_specifications,
|
||||||
|
):
|
||||||
|
|
||||||
|
self.ec2_backend = ec2_backend
|
||||||
|
self.id = fleet_id
|
||||||
|
self.spot_options = spot_options
|
||||||
|
self.on_demand_options = on_demand_options
|
||||||
|
self.target_capacity_specification = target_capacity_specification
|
||||||
|
self.launch_template_configs = launch_template_configs
|
||||||
|
self.excess_capacity_termination_policy = (
|
||||||
|
excess_capacity_termination_policy or "termination"
|
||||||
|
)
|
||||||
|
self.replace_unhealthy_instances = replace_unhealthy_instances
|
||||||
|
self.terminate_instances_with_expiration = terminate_instances_with_expiration
|
||||||
|
self.fleet_type = fleet_type
|
||||||
|
self.valid_from = valid_from
|
||||||
|
self.valid_until = valid_until
|
||||||
|
tag_map = convert_tag_spec(tag_specifications).get("fleet", {})
|
||||||
|
self.add_tags(tag_map)
|
||||||
|
self.tags = self.get_tags()
|
||||||
|
|
||||||
|
self.state = "active"
|
||||||
|
self.fulfilled_capacity = 0.0
|
||||||
|
self.fulfilled_on_demand_capacity = 0.0
|
||||||
|
self.fulfilled_spot_capacity = 0.0
|
||||||
|
|
||||||
|
self.launch_specs = []
|
||||||
|
|
||||||
|
launch_specs_from_config = []
|
||||||
|
for config in launch_template_configs or []:
|
||||||
|
spec = config["LaunchTemplateSpecification"]
|
||||||
|
if "LaunchTemplateId" in spec:
|
||||||
|
launch_template = self.ec2_backend.get_launch_template(
|
||||||
|
template_id=spec["LaunchTemplateId"]
|
||||||
|
)
|
||||||
|
elif "LaunchTemplateName" in spec:
|
||||||
|
launch_template = self.ec2_backend.get_launch_template_by_name(
|
||||||
|
name=spec["LaunchTemplateName"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
launch_template_data = launch_template.latest_version().data
|
||||||
|
new_launch_template = launch_template_data.copy()
|
||||||
|
if config.get("Overrides"):
|
||||||
|
for override in config["Overrides"]:
|
||||||
|
new_launch_template.update(override)
|
||||||
|
launch_specs_from_config.append(new_launch_template)
|
||||||
|
|
||||||
|
for spec in launch_specs_from_config:
|
||||||
|
tag_spec_set = spec.get("TagSpecification", [])
|
||||||
|
tags = convert_tag_spec(tag_spec_set)
|
||||||
|
self.launch_specs.append(
|
||||||
|
SpotFleetLaunchSpec(
|
||||||
|
ebs_optimized=spec.get("EbsOptimized"),
|
||||||
|
group_set=spec.get("GroupSet", []),
|
||||||
|
iam_instance_profile=spec.get("IamInstanceProfile"),
|
||||||
|
image_id=spec["ImageId"],
|
||||||
|
instance_type=spec["InstanceType"],
|
||||||
|
key_name=spec.get("KeyName"),
|
||||||
|
monitoring=spec.get("Monitoring"),
|
||||||
|
spot_price=spec.get("SpotPrice"),
|
||||||
|
subnet_id=spec.get("SubnetId"),
|
||||||
|
tag_specifications=tags,
|
||||||
|
user_data=spec.get("UserData"),
|
||||||
|
weighted_capacity=spec.get("WeightedCapacity", 1),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.spot_requests = []
|
||||||
|
self.on_demand_instances = []
|
||||||
|
default_capacity = (
|
||||||
|
target_capacity_specification.get("DefaultTargetCapacityType")
|
||||||
|
or "on-demand"
|
||||||
|
)
|
||||||
|
self.target_capacity = int(
|
||||||
|
target_capacity_specification.get("TotalTargetCapacity")
|
||||||
|
)
|
||||||
|
self.spot_target_capacity = int(
|
||||||
|
target_capacity_specification.get("SpotTargetCapacity")
|
||||||
|
)
|
||||||
|
if self.spot_target_capacity > 0:
|
||||||
|
self.create_spot_requests(self.spot_target_capacity)
|
||||||
|
self.on_demand_target_capacity = int(
|
||||||
|
target_capacity_specification.get("OnDemandTargetCapacity")
|
||||||
|
)
|
||||||
|
if self.on_demand_target_capacity > 0:
|
||||||
|
self.create_on_demand_requests(self.on_demand_target_capacity)
|
||||||
|
|
||||||
|
remaining_capacity = self.target_capacity - self.fulfilled_capacity
|
||||||
|
if remaining_capacity > 0:
|
||||||
|
if default_capacity == "on-demand":
|
||||||
|
self.create_on_demand_requests(remaining_capacity)
|
||||||
|
elif default_capacity == "spot":
|
||||||
|
self.create_spot_requests(remaining_capacity)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def physical_resource_id(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def create_spot_requests(self, weight_to_add):
|
||||||
|
weight_map, added_weight = self.get_launch_spec_counts(weight_to_add)
|
||||||
|
for launch_spec, count in weight_map.items():
|
||||||
|
requests = self.ec2_backend.request_spot_instances(
|
||||||
|
price=launch_spec.spot_price,
|
||||||
|
image_id=launch_spec.image_id,
|
||||||
|
count=count,
|
||||||
|
spot_instance_type="persistent",
|
||||||
|
valid_from=None,
|
||||||
|
valid_until=None,
|
||||||
|
launch_group=None,
|
||||||
|
availability_zone_group=None,
|
||||||
|
key_name=launch_spec.key_name,
|
||||||
|
security_groups=launch_spec.group_set,
|
||||||
|
user_data=launch_spec.user_data,
|
||||||
|
instance_type=launch_spec.instance_type,
|
||||||
|
placement=None,
|
||||||
|
kernel_id=None,
|
||||||
|
ramdisk_id=None,
|
||||||
|
monitoring_enabled=launch_spec.monitoring,
|
||||||
|
subnet_id=launch_spec.subnet_id,
|
||||||
|
spot_fleet_id=self.id,
|
||||||
|
tags=launch_spec.tag_specifications,
|
||||||
|
)
|
||||||
|
self.spot_requests.extend(requests)
|
||||||
|
self.fulfilled_capacity += added_weight
|
||||||
|
return self.spot_requests
|
||||||
|
|
||||||
|
def create_on_demand_requests(self, weight_to_add):
|
||||||
|
weight_map, added_weight = self.get_launch_spec_counts(weight_to_add)
|
||||||
|
for launch_spec, count in weight_map.items():
|
||||||
|
reservation = self.ec2_backend.add_instances(
|
||||||
|
image_id=launch_spec.image_id,
|
||||||
|
count=count,
|
||||||
|
instance_type=launch_spec.instance_type,
|
||||||
|
is_instance_type_default=False,
|
||||||
|
key_name=launch_spec.key_name,
|
||||||
|
security_group_names=launch_spec.group_set,
|
||||||
|
user_data=launch_spec.user_data,
|
||||||
|
placement=None,
|
||||||
|
kernel_id=None,
|
||||||
|
ramdisk_id=None,
|
||||||
|
monitoring_enabled=launch_spec.monitoring,
|
||||||
|
subnet_id=launch_spec.subnet_id,
|
||||||
|
fleet_id=self.id,
|
||||||
|
tags=launch_spec.tag_specifications,
|
||||||
|
)
|
||||||
|
|
||||||
|
# get the instance from the reservation
|
||||||
|
instance = reservation.instances[0]
|
||||||
|
self.on_demand_instances.append(
|
||||||
|
{
|
||||||
|
"id": reservation.id,
|
||||||
|
"instance": instance,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.fulfilled_capacity += added_weight
|
||||||
|
return self.on_demand_instances
|
||||||
|
|
||||||
|
def get_launch_spec_counts(self, weight_to_add):
|
||||||
|
weight_map = defaultdict(int)
|
||||||
|
|
||||||
|
weight_so_far = 0
|
||||||
|
if (
|
||||||
|
self.spot_options
|
||||||
|
and self.spot_options["AllocationStrategy"] == "diversified"
|
||||||
|
):
|
||||||
|
launch_spec_index = 0
|
||||||
|
while True:
|
||||||
|
launch_spec = self.launch_specs[
|
||||||
|
launch_spec_index % len(self.launch_specs)
|
||||||
|
]
|
||||||
|
weight_map[launch_spec] += 1
|
||||||
|
weight_so_far += launch_spec.weighted_capacity
|
||||||
|
if weight_so_far >= weight_to_add:
|
||||||
|
break
|
||||||
|
launch_spec_index += 1
|
||||||
|
else: # lowestPrice
|
||||||
|
cheapest_spec = sorted(
|
||||||
|
# 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
|
||||||
|
)
|
||||||
|
|
||||||
|
return weight_map, weight_so_far
|
||||||
|
|
||||||
|
def terminate_instances(self):
|
||||||
|
instance_ids = []
|
||||||
|
new_fulfilled_capacity = self.fulfilled_capacity
|
||||||
|
for req in self.spot_requests + self.on_demand_instances:
|
||||||
|
instance = None
|
||||||
|
try:
|
||||||
|
instance = req.instance
|
||||||
|
except AttributeError:
|
||||||
|
instance = req["instance"]
|
||||||
|
|
||||||
|
if instance.state == "terminated":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# stop when we hit the target capacity
|
||||||
|
if new_fulfilled_capacity <= self.target_capacity:
|
||||||
|
break
|
||||||
|
|
||||||
|
instance_ids.append(instance.id)
|
||||||
|
new_fulfilled_capacity -= 1
|
||||||
|
|
||||||
|
self.spot_requests = [
|
||||||
|
req for req in self.spot_requests if req.instance.id not in instance_ids
|
||||||
|
]
|
||||||
|
self.on_demand_instances = [
|
||||||
|
req
|
||||||
|
for req in self.on_demand_instances
|
||||||
|
if req["instance"].id not in instance_ids
|
||||||
|
]
|
||||||
|
self.ec2_backend.terminate_instances(instance_ids)
|
||||||
|
|
||||||
|
|
||||||
|
class FleetsBackend:
|
||||||
|
def __init__(self):
|
||||||
|
self.fleets = {}
|
||||||
|
|
||||||
|
def create_fleet(
|
||||||
|
self,
|
||||||
|
on_demand_options,
|
||||||
|
spot_options,
|
||||||
|
target_capacity_specification,
|
||||||
|
launch_template_configs,
|
||||||
|
excess_capacity_termination_policy,
|
||||||
|
replace_unhealthy_instances,
|
||||||
|
terminate_instances_with_expiration,
|
||||||
|
fleet_type,
|
||||||
|
valid_from,
|
||||||
|
valid_until,
|
||||||
|
tag_specifications,
|
||||||
|
):
|
||||||
|
|
||||||
|
fleet_id = random_fleet_id()
|
||||||
|
fleet = Fleet(
|
||||||
|
self,
|
||||||
|
fleet_id,
|
||||||
|
on_demand_options,
|
||||||
|
spot_options,
|
||||||
|
target_capacity_specification,
|
||||||
|
launch_template_configs,
|
||||||
|
excess_capacity_termination_policy,
|
||||||
|
replace_unhealthy_instances,
|
||||||
|
terminate_instances_with_expiration,
|
||||||
|
fleet_type,
|
||||||
|
valid_from,
|
||||||
|
valid_until,
|
||||||
|
tag_specifications,
|
||||||
|
)
|
||||||
|
self.fleets[fleet_id] = fleet
|
||||||
|
return fleet
|
||||||
|
|
||||||
|
def get_fleet(self, fleet_id):
|
||||||
|
return self.fleets.get(fleet_id)
|
||||||
|
|
||||||
|
def describe_fleet_instances(self, fleet_id):
|
||||||
|
fleet = self.get_fleet(fleet_id)
|
||||||
|
if not fleet:
|
||||||
|
return []
|
||||||
|
return fleet.spot_requests + fleet.on_demand_instances
|
||||||
|
|
||||||
|
def describe_fleets(self, fleet_ids):
|
||||||
|
fleets = self.fleets.values()
|
||||||
|
|
||||||
|
if fleet_ids:
|
||||||
|
fleets = [fleet for fleet in fleets if fleet.id in fleet_ids]
|
||||||
|
|
||||||
|
return fleets
|
||||||
|
|
||||||
|
def delete_fleets(self, fleet_ids, terminate_instances):
|
||||||
|
fleets = []
|
||||||
|
for fleet_id in fleet_ids:
|
||||||
|
fleet = self.fleets[fleet_id]
|
||||||
|
if terminate_instances:
|
||||||
|
fleet.target_capacity = 0
|
||||||
|
fleet.terminate_instances()
|
||||||
|
fleets.append(fleet)
|
||||||
|
fleet.state = "deleted"
|
||||||
|
return fleets
|
@ -7,6 +7,7 @@ from moto import settings
|
|||||||
from moto.core import get_account_id
|
from moto.core import get_account_id
|
||||||
from moto.core import CloudFormationModel
|
from moto.core import CloudFormationModel
|
||||||
from moto.core.utils import camelcase_to_underscores
|
from moto.core.utils import camelcase_to_underscores
|
||||||
|
from moto.ec2.models.fleets import Fleet
|
||||||
from moto.ec2.models.instance_types import (
|
from moto.ec2.models.instance_types import (
|
||||||
INSTANCE_TYPE_OFFERINGS,
|
INSTANCE_TYPE_OFFERINGS,
|
||||||
InstanceTypeOfferingBackend,
|
InstanceTypeOfferingBackend,
|
||||||
@ -114,6 +115,7 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
|||||||
)
|
)
|
||||||
self.sriov_net_support = "simple"
|
self.sriov_net_support = "simple"
|
||||||
self._spot_fleet_id = kwargs.get("spot_fleet_id", None)
|
self._spot_fleet_id = kwargs.get("spot_fleet_id", None)
|
||||||
|
self._fleet_id = kwargs.get("fleet_id", None)
|
||||||
self.associate_public_ip = kwargs.get("associate_public_ip", False)
|
self.associate_public_ip = kwargs.get("associate_public_ip", False)
|
||||||
if in_ec2_classic:
|
if in_ec2_classic:
|
||||||
# If we are in EC2-Classic, autoassign a public IP
|
# If we are in EC2-Classic, autoassign a public IP
|
||||||
@ -367,18 +369,28 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
|
|||||||
|
|
||||||
self.teardown_defaults()
|
self.teardown_defaults()
|
||||||
|
|
||||||
if self._spot_fleet_id:
|
if self._spot_fleet_id or self._fleet_id:
|
||||||
spot_fleet = self.ec2_backend.get_spot_fleet_request(self._spot_fleet_id)
|
fleet = self.ec2_backend.get_spot_fleet_request(self._spot_fleet_id)
|
||||||
for spec in spot_fleet.launch_specs:
|
if not fleet:
|
||||||
|
fleet = self.ec2_backend.get_fleet(
|
||||||
|
self._spot_fleet_id
|
||||||
|
) or self.ec2_backend.get_fleet(self._fleet_id)
|
||||||
|
for spec in fleet.launch_specs:
|
||||||
if (
|
if (
|
||||||
spec.instance_type == self.instance_type
|
spec.instance_type == self.instance_type
|
||||||
and spec.subnet_id == self.subnet_id
|
and spec.subnet_id == self.subnet_id
|
||||||
):
|
):
|
||||||
break
|
break
|
||||||
spot_fleet.fulfilled_capacity -= spec.weighted_capacity
|
fleet.fulfilled_capacity -= spec.weighted_capacity
|
||||||
spot_fleet.spot_requests = [
|
fleet.spot_requests = [
|
||||||
req for req in spot_fleet.spot_requests if req.instance != self
|
req for req in fleet.spot_requests if req.instance != self
|
||||||
]
|
]
|
||||||
|
if isinstance(fleet, Fleet):
|
||||||
|
fleet.on_demand_instances = [
|
||||||
|
inst
|
||||||
|
for inst in fleet.on_demand_instances
|
||||||
|
if inst["instance"] != self
|
||||||
|
]
|
||||||
|
|
||||||
self._state.name = "terminated"
|
self._state.name = "terminated"
|
||||||
self._state.code = 48
|
self._state.code = 48
|
||||||
|
@ -7,6 +7,7 @@ from .dhcp_options import DHCPOptions
|
|||||||
from .elastic_block_store import ElasticBlockStore
|
from .elastic_block_store import ElasticBlockStore
|
||||||
from .elastic_ip_addresses import ElasticIPAddresses
|
from .elastic_ip_addresses import ElasticIPAddresses
|
||||||
from .elastic_network_interfaces import ElasticNetworkInterfaces
|
from .elastic_network_interfaces import ElasticNetworkInterfaces
|
||||||
|
from .fleets import Fleets
|
||||||
from .general import General
|
from .general import General
|
||||||
from .instances import InstanceResponse
|
from .instances import InstanceResponse
|
||||||
from .internet_gateways import InternetGateways
|
from .internet_gateways import InternetGateways
|
||||||
@ -52,6 +53,7 @@ class EC2Response(
|
|||||||
ElasticBlockStore,
|
ElasticBlockStore,
|
||||||
ElasticIPAddresses,
|
ElasticIPAddresses,
|
||||||
ElasticNetworkInterfaces,
|
ElasticNetworkInterfaces,
|
||||||
|
Fleets,
|
||||||
General,
|
General,
|
||||||
InstanceResponse,
|
InstanceResponse,
|
||||||
InternetGateways,
|
InternetGateways,
|
||||||
|
417
moto/ec2/responses/fleets.py
Normal file
417
moto/ec2/responses/fleets.py
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
from moto.core.responses import BaseResponse
|
||||||
|
|
||||||
|
|
||||||
|
class Fleets(BaseResponse):
|
||||||
|
def delete_fleets(self):
|
||||||
|
fleet_ids = self._get_multi_param("FleetId.")
|
||||||
|
terminate_instances = self._get_param("TerminateInstances")
|
||||||
|
fleets = self.ec2_backend.delete_fleets(fleet_ids, terminate_instances)
|
||||||
|
template = self.response_template(DELETE_FLEETS_TEMPLATE)
|
||||||
|
return template.render(fleets=fleets)
|
||||||
|
|
||||||
|
def describe_fleet_instances(self):
|
||||||
|
fleet_id = self._get_param("FleetId")
|
||||||
|
|
||||||
|
instances = self.ec2_backend.describe_fleet_instances(fleet_id)
|
||||||
|
template = self.response_template(DESCRIBE_FLEET_INSTANCES_TEMPLATE)
|
||||||
|
return template.render(fleet_id=fleet_id, instances=instances)
|
||||||
|
|
||||||
|
def describe_fleets(self):
|
||||||
|
fleet_ids = self._get_multi_param("FleetId.")
|
||||||
|
|
||||||
|
requests = self.ec2_backend.describe_fleets(fleet_ids)
|
||||||
|
template = self.response_template(DESCRIBE_FLEETS_TEMPLATE)
|
||||||
|
rend = template.render(requests=requests)
|
||||||
|
return rend
|
||||||
|
|
||||||
|
def create_fleet(self):
|
||||||
|
on_demand_options = self._get_multi_param_dict("OnDemandOptions")
|
||||||
|
spot_options = self._get_multi_param_dict("SpotOptions")
|
||||||
|
target_capacity_specification = self._get_multi_param_dict(
|
||||||
|
"TargetCapacitySpecification"
|
||||||
|
)
|
||||||
|
launch_template_configs = self._get_multi_param("LaunchTemplateConfigs")
|
||||||
|
|
||||||
|
excess_capacity_termination_policy = self._get_param(
|
||||||
|
"ExcessCapacityTerminationPolicy"
|
||||||
|
)
|
||||||
|
replace_unhealthy_instances = self._get_param("ReplaceUnhealthyInstances")
|
||||||
|
terminate_instances_with_expiration = self._get_param(
|
||||||
|
"TerminateInstancesWithExpiration", if_none=True
|
||||||
|
)
|
||||||
|
fleet_type = self._get_param("Type", if_none="maintain")
|
||||||
|
valid_from = self._get_param("ValidFrom")
|
||||||
|
valid_until = self._get_param("ValidUntil")
|
||||||
|
|
||||||
|
tag_specifications = self._get_multi_param("TagSpecification")
|
||||||
|
|
||||||
|
request = self.ec2_backend.create_fleet(
|
||||||
|
on_demand_options=on_demand_options,
|
||||||
|
spot_options=spot_options,
|
||||||
|
target_capacity_specification=target_capacity_specification,
|
||||||
|
launch_template_configs=launch_template_configs,
|
||||||
|
excess_capacity_termination_policy=excess_capacity_termination_policy,
|
||||||
|
replace_unhealthy_instances=replace_unhealthy_instances,
|
||||||
|
terminate_instances_with_expiration=terminate_instances_with_expiration,
|
||||||
|
fleet_type=fleet_type,
|
||||||
|
valid_from=valid_from,
|
||||||
|
valid_until=valid_until,
|
||||||
|
tag_specifications=tag_specifications,
|
||||||
|
)
|
||||||
|
|
||||||
|
template = self.response_template(CREATE_FLEET_TEMPLATE)
|
||||||
|
return template.render(request=request)
|
||||||
|
|
||||||
|
|
||||||
|
CREATE_FLEET_TEMPLATE = """<CreateFleetResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
||||||
|
<requestId>60262cc5-2bd4-4c8d-98ed-example</requestId>
|
||||||
|
<fleetId>{{ request.id }}</fleetId>
|
||||||
|
{% if request.fleet_type == "instant" %}
|
||||||
|
<fleetInstanceSet>
|
||||||
|
{% for instance in request.on_demand_instances %}
|
||||||
|
<item>
|
||||||
|
<instanceType>{{ instance["instance"].instance_type }}</instanceType>
|
||||||
|
<lifecycle>on-demand</lifecycle>
|
||||||
|
<instanceIds>
|
||||||
|
<item>{{ instance["instance"].id }}</item>
|
||||||
|
</instanceIds>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
{% for instance in request.spot_requests %}
|
||||||
|
<item>
|
||||||
|
<instanceType>{{ instance.instance.instance_type }}</instanceType>
|
||||||
|
<lifecycle>spot</lifecycle>
|
||||||
|
<instanceIds>
|
||||||
|
<item>{{ instance.instance.id }}</item>
|
||||||
|
</instanceIds>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</fleetInstanceSet>
|
||||||
|
{% endif %}
|
||||||
|
</CreateFleetResponse>"""
|
||||||
|
|
||||||
|
DESCRIBE_FLEETS_TEMPLATE = """<DescribeFleetsResponse xmlns="http://ec2.amazonaws.com/doc/2016-09-15/">
|
||||||
|
<requestId>4d68a6cc-8f2e-4be1-b425-example</requestId>
|
||||||
|
<fleetSet>
|
||||||
|
{% for request in requests %}
|
||||||
|
<item>
|
||||||
|
<fleetId>{{ request.id }}</fleetId>
|
||||||
|
<fleetState>{{ request.state }}</fleetState>
|
||||||
|
<excessCapacityTerminationPolicy>{{ request.excess_capacity_termination_policy }}</excessCapacityTerminationPolicy>
|
||||||
|
<fulfilledCapacity>{{ request.fulfilled_capacity }}</fulfilledCapacity>
|
||||||
|
<fulfilledOnDemandCapacity>{{ request.fulfilled_on_demand_capacity }}</fulfilledOnDemandCapacity>
|
||||||
|
<launchTemplateConfigs>
|
||||||
|
{% for config in request.launch_template_configs %}
|
||||||
|
<item>
|
||||||
|
<launchTemplateSpecification>
|
||||||
|
<launchTemplateId>{{ config.LaunchTemplateSpecification.LaunchTemplateId }}</launchTemplateId>
|
||||||
|
<version>{{ config.LaunchTemplateSpecification.Version }}</version>
|
||||||
|
</launchTemplateSpecification>
|
||||||
|
{% if config.Overrides %}
|
||||||
|
<overrides>
|
||||||
|
{% for override in config.Overrides %}
|
||||||
|
<item>
|
||||||
|
{% if override.AvailabilityZone %}
|
||||||
|
<availabilityZone>{{ override.AvailabilityZone }}</availabilityZone>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceType %}
|
||||||
|
<instanceType>{{ override.InstanceType }}</instanceType>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements %}
|
||||||
|
<instanceRequirements>
|
||||||
|
{% if override.InstanceRequirements.AcceleratorCount %}
|
||||||
|
<acceleratorCount>
|
||||||
|
{% if override.InstanceRequirements.AcceleratorCount.Max %}
|
||||||
|
<max>{{ override.InstanceRequirements.AcceleratorCount.Max }}</max>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.AcceleratorCount.Min %}
|
||||||
|
<min>{{ override.InstanceRequirements.AcceleratorCount.Min }}</min>
|
||||||
|
{% endif %}
|
||||||
|
</acceleratorCount>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.AcceleratorManufacturer %}
|
||||||
|
<acceleratorManufacturerSet>
|
||||||
|
{% for manufacturer in override.InstanceRequirements.AcceleratorManufacturer %}
|
||||||
|
<item>{{ manufacturer }}</item>
|
||||||
|
{% endfor %}
|
||||||
|
</acceleratorManufacturerSet>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.AcceleratorName %}
|
||||||
|
<acceleratorNameSet>
|
||||||
|
{% for name in override.InstanceRequirements.AcceleratorName %}
|
||||||
|
<item>{{ name }}</item>
|
||||||
|
{% endfor %}
|
||||||
|
</acceleratorNameSet>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.AcceleratorTotalMemoryMiB %}
|
||||||
|
<acceleratorTotalMemoryMiB>
|
||||||
|
{% if override.InstanceRequirements.AcceleratorTotalMemoryMiB.Max %}
|
||||||
|
<max>{{ override.InstanceRequirements.AcceleratorTotalMemoryMiB.Max }}</max>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.AcceleratorTotalMemoryMiB.Min %}
|
||||||
|
<min>{{ override.InstanceRequirements.AcceleratorTotalMemoryMiB.Min }}</min>
|
||||||
|
{% endif %}
|
||||||
|
</acceleratorTotalMemoryMiB>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.AcceleratorType %}
|
||||||
|
<acceleratorTypeSet>
|
||||||
|
{% for type in override.InstanceRequirements.AcceleratorType %}
|
||||||
|
<item>{{ type }}</item>
|
||||||
|
{% endfor %}
|
||||||
|
</acceleratorTypeSet>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.BareMetal %}
|
||||||
|
<bareMetal>{{ override.InstanceRequirements.BareMetal }}</bareMetal>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.BaselineEbsBandwidthMbps %}
|
||||||
|
<baselineEbsBandwidthMbps>
|
||||||
|
{% if override.InstanceRequirements.BaselineEbsBandwidthMbps.Min %}
|
||||||
|
<min>{{ override.InstanceRequirements.BaselineEbsBandwidthMbps.Min }}</min>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.BaselineEbsBandwidthMbps.Max %}
|
||||||
|
<max>{{ override.InstanceRequirements.BaselineEbsBandwidthMbps.Max }}</max>
|
||||||
|
{% endif %}
|
||||||
|
</baselineEbsBandwidthMbps>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.BurstablePerformance %}
|
||||||
|
<burstablePerformance>{{ override.InstanceRequirements.BurstablePerformance }}</burstablePerformance>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.CpuManufacturer %}
|
||||||
|
<cpuManufacturerSet>
|
||||||
|
{% for manufacturer in override.InstanceRequirements.CpuManufacturer %}
|
||||||
|
<item>{{ manufacturer }}</item>
|
||||||
|
{% endfor %}
|
||||||
|
</cpuManufacturerSet>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.ExcludedInstanceType %}
|
||||||
|
<excludedInstanceTypeSet>
|
||||||
|
{% for type in override.InstanceRequirements.ExcludedInstanceType %}
|
||||||
|
<item>{{ type }}</item>
|
||||||
|
{% endfor %}
|
||||||
|
</excludedInstanceTypeSet>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.InstanceGeneration %}
|
||||||
|
<instanceGenerationSet>
|
||||||
|
{% for generation in override.InstanceRequirements.InstanceGeneration %}
|
||||||
|
<item>{{ generation }}</item>
|
||||||
|
{% endfor %}
|
||||||
|
</instanceGenerationSet>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.LocalStorage %}
|
||||||
|
<localStorage>{{ override.InstanceRequirements.LocalStorage }}</localStorage>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.LocalStorageType %}
|
||||||
|
<localStorageTypeSet>
|
||||||
|
{% for type in override.InstanceRequirements.LocalStorageType %}
|
||||||
|
<item>{{ type }}</item>
|
||||||
|
{% endfor %}
|
||||||
|
</localStorageTypeSet>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.MemoryGiBPerVCpu %}
|
||||||
|
<memoryGiBPerVCpu>
|
||||||
|
<min>{{ override.InstanceRequirements.MemoryGiBPerVCpu.Min }}</min>
|
||||||
|
<max>{{ override.InstanceRequirements.MemoryGiBPerVCpu.Max }}</max>
|
||||||
|
</memoryGiBPerVCpu>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.MemoryMiB %}
|
||||||
|
<memoryMiB>
|
||||||
|
{% if override.InstanceRequirements.MemoryMiB.Min %}
|
||||||
|
<min>{{ override.InstanceRequirements.MemoryMiB.Min }}</min>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.MemoryMiB.Max %}
|
||||||
|
<max>{{ override.InstanceRequirements.MemoryMiB.Max }}</max>
|
||||||
|
{% endif %}
|
||||||
|
</memoryMiB>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.NetworkInterfaceCount %}
|
||||||
|
<networkInterfaceCount>
|
||||||
|
{% if override.InstanceRequirements.NetworkInterfaceCount.Max %}
|
||||||
|
<max>{{ override.InstanceRequirements.NetworkInterfaceCount.Max }}</max>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.NetworkInterfaceCount.Min %}
|
||||||
|
<min>{{ override.InstanceRequirements.NetworkInterfaceCount.Min }}</min>
|
||||||
|
{% endif %}
|
||||||
|
</networkInterfaceCount>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.OnDemandMaxPricePercentageOverLowestPrice %}
|
||||||
|
<onDemandMaxPricePercentageOverLowestPrice>{{ override.InstanceRequirements.OnDemandMaxPricePercentageOverLowestPrice }}</onDemandMaxPricePercentageOverLowestPrice>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.RequireHibernateSupport %}
|
||||||
|
<requireHibernateSupport>{{ override.InstanceRequirements.RequireHibernateSupport }}</requireHibernateSupport>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.SpotMaxPricePercentageOverLowestPrice %}
|
||||||
|
<spotMaxPricePercentageOverLowestPrice>{{ override.InstanceRequirements.SpotMaxPricePercentageOverLowestPrice }}</spotMaxPricePercentageOverLowestPrice>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.TotalLocalStorageGB %}
|
||||||
|
<totalLocalStorageGB>
|
||||||
|
{% if override.InstanceRequirements.TotalLocalStorageGB.Min %}
|
||||||
|
<min>{{ override.InstanceRequirements.TotalLocalStorageGB.Min }}</min>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.TotalLocalStorageGB.Max %}
|
||||||
|
<max>{{ override.InstanceRequirements.TotalLocalStorageGB.Max }}</max>
|
||||||
|
{% endif %}
|
||||||
|
</totalLocalStorageGB>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.VCpuCount %}
|
||||||
|
<vCpuCount>
|
||||||
|
{% if override.InstanceRequirements.VCpuCount.Min %}
|
||||||
|
<min>{{ override.InstanceRequirements.VCpuCount.Min }}</min>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.InstanceRequirements.VCpuCount.Max %}
|
||||||
|
<max>{{ override.InstanceRequirements.VCpuCount.Max }}</max>
|
||||||
|
{% endif %}
|
||||||
|
</vCpuCount>
|
||||||
|
{% endif %}
|
||||||
|
</instanceRequirements>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.MaxPrice %}
|
||||||
|
<maxPrice>{{ override.MaxPrice }}</maxPrice>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.Placement %}
|
||||||
|
<placement>
|
||||||
|
{% if override.Placement.GroupName %}
|
||||||
|
<groupName>{{ override.Placement.GroupName }}</groupName>
|
||||||
|
{% endif %}
|
||||||
|
</placement>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.Priority %}
|
||||||
|
<priority>{{ override.Priority }}</priority>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.SubnetId %}
|
||||||
|
<subnetId>{{ override.SubnetId }}</subnetId>
|
||||||
|
{% endif %}
|
||||||
|
{% if override.WeightedCapacity %}
|
||||||
|
<weightedCapacity>{{ override.WeightedCapacity }}</weightedCapacity>
|
||||||
|
{% endif %}
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</overrides>
|
||||||
|
{% endif %}
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</launchTemplateConfigs>
|
||||||
|
<targetCapacitySpecification>
|
||||||
|
<totalTargetCapacity>{{ request.target_capacity }}</totalTargetCapacity>
|
||||||
|
{% if request.on_demand_target_capacity %}
|
||||||
|
<onDemandTargetCapacity>{{ request.on_demand_target_capacity }}</onDemandTargetCapacity>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_target_capacity %}
|
||||||
|
<spotTargetCapacity>{{ request.spot_target_capacity }}</spotTargetCapacity>
|
||||||
|
{% endif %}
|
||||||
|
<defaultTargetCapacityType>{{ request.target_capacity_specification.DefaultTargetCapacityType }}</defaultTargetCapacityType>
|
||||||
|
</targetCapacitySpecification>
|
||||||
|
{% if request.spot_options %}
|
||||||
|
<spotOptions>
|
||||||
|
{% if request.spot_options.AllocationStrategy %}
|
||||||
|
<allocationStrategy>{{ request.spot_options.AllocationStrategy }}</allocationStrategy>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.InstanceInterruptionBehavior %}
|
||||||
|
<instanceInterruptionBehavior>{{ request.spot_options.InstanceInterruptionBehavior }}</instanceInterruptionBehavior>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.InstancePoolsToUseCount %}
|
||||||
|
<instancePoolsToUseCount>{{ request.spot_options.InstancePoolsToUseCount }}</instancePoolsToUseCount>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.MaintenanceStrategies %}
|
||||||
|
<maintenanceStrategies>
|
||||||
|
{% if request.spot_options.MaintenanceStrategies.CapacityRebalance %}
|
||||||
|
<capacityRebalance>
|
||||||
|
{% if request.spot_options.MaintenanceStrategies.CapacityRebalance.ReplacementStrategy %}
|
||||||
|
<replacementStrategy>{{ request.spot_options.MaintenanceStrategies.CapacityRebalance.ReplacementStrategy }}</replacementStrategy>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.MaintenanceStrategies.CapacityRebalance.TerminationDelay %}
|
||||||
|
<terminationDelay>{{ request.spot_options.MaintenanceStrategies.CapacityRebalance.TerminationDelay }}</terminationDelay>
|
||||||
|
{% endif %}
|
||||||
|
</capacityRebalance>
|
||||||
|
{% endif %}
|
||||||
|
</maintenanceStrategies>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.MaxTotalPrice %}
|
||||||
|
<maxTotalPrice>{{ request.spot_options.MaxTotalPrice }}</maxTotalPrice>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.MinTargetCapacity %}
|
||||||
|
<minTargetCapacity>{{ request.spot_options.MinTargetCapacity }}</minTargetCapacity>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.SingleAvailabilityZone %}
|
||||||
|
<singleAvailabilityZone>{{ request.spot_options.SingleAvailabilityZone }}</singleAvailabilityZone>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.spot_options.SingleInstanceType %}
|
||||||
|
<singleInstanceType>{{ request.spot_options.SingleInstanceType }}</singleInstanceType>
|
||||||
|
{% endif %}
|
||||||
|
</spotOptions>
|
||||||
|
{% endif %}
|
||||||
|
<!-- {'AllocationStrategy': 'lowest-price', 'MaxTotalPrice': '50', 'MinTargetCapacity': 1, 'SingleAvailabilityZone': True, 'SingleInstanceType': True} -->
|
||||||
|
{% if request.on_demand_options %}
|
||||||
|
<onDemandOptions>
|
||||||
|
{% if request.on_demand_options.AllocationStrategy %}
|
||||||
|
<allocationStrategy>{{ request.on_demand_options.AllocationStrategy }}</allocationStrategy>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.on_demand_options.MaxTotalPrice %}
|
||||||
|
<maxTotalPrice>{{ request.on_demand_options.MaxTotalPrice }}</maxTotalPrice>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.on_demand_options.MinTargetCapacity %}
|
||||||
|
<minTargetCapacity>{{ request.on_demand_options.MinTargetCapacity }}</minTargetCapacity>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.on_demand_options.SingleAvailabilityZone %}
|
||||||
|
<singleAvailabilityZone>{{ request.on_demand_options.SingleAvailabilityZone }}</singleAvailabilityZone>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.on_demand_options.SingleInstanceType %}
|
||||||
|
<singleInstanceType>{{ request.on_demand_options.SingleInstanceType }}</singleInstanceType>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.on_demand_options.CapacityReservationOptions %}
|
||||||
|
<capacityReservationOptions>
|
||||||
|
{% if request.on_demand_options.CapacityReservationOptions.UsageStrategy %}
|
||||||
|
<usageStrategy>{{ request.on_demand_options.CapacityReservationOptions.UsageStrategy }}</usageStrategy>
|
||||||
|
{% endif %}
|
||||||
|
</capacityReservationOptions>
|
||||||
|
{% endif %}
|
||||||
|
</onDemandOptions>
|
||||||
|
{% endif %}
|
||||||
|
<terminateInstancesWithExpiration>{{ request.terminate_instances_with_expiration }}</terminateInstancesWithExpiration>
|
||||||
|
<type>{{ request.fleet_type }}</type>
|
||||||
|
<validFrom>{{ request.valid_from }}</validFrom>
|
||||||
|
<validUntil>{{ request.valid_until }}</validUntil>
|
||||||
|
<replaceUnhealthyInstances>{{ request.replace_unhealthy_instances }}</replaceUnhealthyInstances>
|
||||||
|
<tagSet>
|
||||||
|
{% for tag in request.tags %}
|
||||||
|
<item>
|
||||||
|
<key>{{ tag.key }}</key>
|
||||||
|
<value>{{ tag.value }}</value>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</tagSet>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</fleetSet>
|
||||||
|
</DescribeFleetsResponse>"""
|
||||||
|
|
||||||
|
DESCRIBE_FLEET_INSTANCES_TEMPLATE = """<DescribeFleetInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2016-09-15/">
|
||||||
|
<requestId>cfb09950-45e2-472d-a6a9-example</requestId>
|
||||||
|
<fleetId>{{ fleet_id }}</fleetId>
|
||||||
|
<activeInstanceSet>
|
||||||
|
{% for i in instances %}
|
||||||
|
<item>
|
||||||
|
<instanceId>{{ i.instance.id }}</instanceId>
|
||||||
|
{% if i.id %}
|
||||||
|
<spotInstanceRequestId>{{ i.id }}</spotInstanceRequestId>
|
||||||
|
{% endif %}
|
||||||
|
<instanceType>{{ i.instance.instance_type }}</instanceType>
|
||||||
|
<instanceHealth>healthy</instanceHealth>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</activeInstanceSet>
|
||||||
|
</DescribeFleetInstancesResponse>
|
||||||
|
"""
|
||||||
|
|
||||||
|
DELETE_FLEETS_TEMPLATE = """<DeleteFleetResponse xmlns="http://ec2.amazonaws.com/doc/2016-09-15/">
|
||||||
|
<requestId>e12d2fe5-6503-4b4b-911c-example</requestId>
|
||||||
|
<unsuccessfulFleetDeletionSet/>
|
||||||
|
<successfulFleetDeletionSet>
|
||||||
|
{% for fleet in fleets %}
|
||||||
|
<item>
|
||||||
|
<fleetId>{{ fleet.id }}</fleetId>
|
||||||
|
<currentFleetState>{{ fleet.state }}</currentFleetState>
|
||||||
|
<previousFleetState>active</previousFleetState>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</successfulFleetDeletionSet>
|
||||||
|
</DeleteFleetResponse>"""
|
@ -19,6 +19,7 @@ EC2_RESOURCE_TO_PREFIX = {
|
|||||||
"transit-gateway-route-table": "tgw-rtb",
|
"transit-gateway-route-table": "tgw-rtb",
|
||||||
"transit-gateway-attachment": "tgw-attach",
|
"transit-gateway-attachment": "tgw-attach",
|
||||||
"dhcp-options": "dopt",
|
"dhcp-options": "dopt",
|
||||||
|
"fleet": "fleet",
|
||||||
"flow-logs": "fl",
|
"flow-logs": "fl",
|
||||||
"image": "ami",
|
"image": "ami",
|
||||||
"instance": "i",
|
"instance": "i",
|
||||||
@ -90,6 +91,10 @@ def random_security_group_rule_id():
|
|||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["security-group-rule"], size=17)
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["security-group-rule"], size=17)
|
||||||
|
|
||||||
|
|
||||||
|
def random_fleet_id():
|
||||||
|
return f"fleet-{random_resource_id(size=8)}-{random_resource_id(size=4)}-{random_resource_id(size=4)}-{random_resource_id(size=4)}-{random_resource_id(size=12)}"
|
||||||
|
|
||||||
|
|
||||||
def random_flow_log_id():
|
def random_flow_log_id():
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["flow-logs"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["flow-logs"])
|
||||||
|
|
||||||
|
663
tests/test_ec2/test_fleets.py
Normal file
663
tests/test_ec2/test_fleets.py
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
import boto3
|
||||||
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from moto import mock_ec2
|
||||||
|
from tests import EXAMPLE_AMI_ID
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
|
||||||
|
def get_subnet_id(conn):
|
||||||
|
vpc = conn.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
|
||||||
|
subnet = conn.create_subnet(
|
||||||
|
VpcId=vpc["VpcId"], CidrBlock="10.0.0.0/16", AvailabilityZone="us-east-1a"
|
||||||
|
)["Subnet"]
|
||||||
|
subnet_id = subnet["SubnetId"]
|
||||||
|
return subnet_id
|
||||||
|
|
||||||
|
|
||||||
|
def get_launch_template(conn, instance_type="t2.micro"):
|
||||||
|
launch_template = conn.create_launch_template(
|
||||||
|
LaunchTemplateName="test" + str(uuid4()),
|
||||||
|
LaunchTemplateData={
|
||||||
|
"ImageId": EXAMPLE_AMI_ID,
|
||||||
|
"InstanceType": instance_type,
|
||||||
|
"KeyName": "test",
|
||||||
|
"SecurityGroups": ["sg-123456"],
|
||||||
|
"DisableApiTermination": False,
|
||||||
|
"TagSpecifications": [
|
||||||
|
{
|
||||||
|
"ResourceType": "instance",
|
||||||
|
"Tags": [
|
||||||
|
{"Key": "test", "Value": "value"},
|
||||||
|
{"Key": "Name", "Value": "test"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)["LaunchTemplate"]
|
||||||
|
launch_template_id = launch_template["LaunchTemplateId"]
|
||||||
|
launch_template_name = launch_template["LaunchTemplateName"]
|
||||||
|
return launch_template_id, launch_template_name
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_spot_fleet_with_lowest_price():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-2")
|
||||||
|
launch_template_id, _ = get_launch_template(conn)
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
ExcessCapacityTerminationPolicy="terminate",
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 0,
|
||||||
|
"SpotTargetCapacity": 1,
|
||||||
|
"TotalTargetCapacity": 1,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "lowest-price",
|
||||||
|
},
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
fleet_id.should.be.a(str)
|
||||||
|
fleet_id.should.have.length_of(42)
|
||||||
|
|
||||||
|
fleets_res = conn.describe_fleets(FleetIds=[fleet_id])
|
||||||
|
fleets_res.should.have.key("Fleets")
|
||||||
|
fleets = fleets_res["Fleets"]
|
||||||
|
|
||||||
|
fleet = fleets[0]
|
||||||
|
fleet["FleetState"].should.equal("active")
|
||||||
|
fleet_config = fleet["LaunchTemplateConfigs"][0]
|
||||||
|
|
||||||
|
launch_template_spec = fleet_config["LaunchTemplateSpecification"]
|
||||||
|
|
||||||
|
launch_template_spec["LaunchTemplateId"].should.equal(launch_template_id)
|
||||||
|
launch_template_spec["Version"].should.equal("1")
|
||||||
|
|
||||||
|
instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
instances = instance_res["ActiveInstances"]
|
||||||
|
len(instances).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_on_demand_fleet():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-2")
|
||||||
|
launch_template_id, _ = get_launch_template(conn)
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
ExcessCapacityTerminationPolicy="terminate",
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "on-demand",
|
||||||
|
"OnDemandTargetCapacity": 1,
|
||||||
|
"SpotTargetCapacity": 0,
|
||||||
|
"TotalTargetCapacity": 1,
|
||||||
|
},
|
||||||
|
OnDemandOptions={
|
||||||
|
"AllocationStrategy": "lowestPrice",
|
||||||
|
},
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
fleet_id.should.be.a(str)
|
||||||
|
fleet_id.should.have.length_of(42)
|
||||||
|
|
||||||
|
fleets_res = conn.describe_fleets(FleetIds=[fleet_id])
|
||||||
|
fleets_res.should.have.key("Fleets")
|
||||||
|
fleets = fleets_res["Fleets"]
|
||||||
|
|
||||||
|
fleet = fleets[0]
|
||||||
|
fleet["FleetState"].should.equal("active")
|
||||||
|
fleet_config = fleet["LaunchTemplateConfigs"][0]
|
||||||
|
|
||||||
|
launch_template_spec = fleet_config["LaunchTemplateSpecification"]
|
||||||
|
|
||||||
|
launch_template_spec["LaunchTemplateId"].should.equal(launch_template_id)
|
||||||
|
launch_template_spec["Version"].should.equal("1")
|
||||||
|
|
||||||
|
instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
instances = instance_res["ActiveInstances"]
|
||||||
|
len(instances).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_diversified_spot_fleet():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-2")
|
||||||
|
launch_template_id_1, _ = get_launch_template(conn, instance_type="t2.small")
|
||||||
|
launch_template_id_2, _ = get_launch_template(conn, instance_type="t2.large")
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
ExcessCapacityTerminationPolicy="terminate",
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id_1,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id_2,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 0,
|
||||||
|
"SpotTargetCapacity": 2,
|
||||||
|
"TotalTargetCapacity": 2,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "diversified",
|
||||||
|
},
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
|
||||||
|
instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
instances = instance_res["ActiveInstances"]
|
||||||
|
len(instances).should.equal(2)
|
||||||
|
instance_types = set([instance["InstanceType"] for instance in instances])
|
||||||
|
instance_types.should.equal(set(["t2.small", "t2.large"]))
|
||||||
|
instances[0]["InstanceId"].should.contain("i-")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"spot_allocation_strategy",
|
||||||
|
[
|
||||||
|
"diversified",
|
||||||
|
"lowest-price",
|
||||||
|
"capacity-optimized",
|
||||||
|
"capacity-optimized-prioritized",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"on_demand_allocation_strategy",
|
||||||
|
["lowestPrice", "prioritized"],
|
||||||
|
)
|
||||||
|
def test_request_fleet_using_launch_template_config__name(
|
||||||
|
spot_allocation_strategy, on_demand_allocation_strategy
|
||||||
|
):
|
||||||
|
|
||||||
|
conn = boto3.client("ec2", region_name="us-east-2")
|
||||||
|
|
||||||
|
_, launch_template_name = get_launch_template(conn, instance_type="t2.medium")
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateName": launch_template_name,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 1,
|
||||||
|
"SpotTargetCapacity": 2,
|
||||||
|
"TotalTargetCapacity": 3,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": spot_allocation_strategy,
|
||||||
|
},
|
||||||
|
OnDemandOptions={
|
||||||
|
"AllocationStrategy": on_demand_allocation_strategy,
|
||||||
|
},
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
|
||||||
|
instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
instances = instance_res["ActiveInstances"]
|
||||||
|
len(instances).should.equal(3)
|
||||||
|
instance_types = set([instance["InstanceType"] for instance in instances])
|
||||||
|
instance_types.should.equal(set(["t2.medium"]))
|
||||||
|
instances[0]["InstanceId"].should.contain("i-")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_fleet_request_with_tags():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-2")
|
||||||
|
|
||||||
|
launch_template_id, _ = get_launch_template(conn)
|
||||||
|
tags = [
|
||||||
|
{"Key": "Name", "Value": "test-fleet"},
|
||||||
|
{"Key": "Another", "Value": "tag"},
|
||||||
|
]
|
||||||
|
tags_instance = [
|
||||||
|
{"Key": "test", "Value": "value"},
|
||||||
|
{"Key": "Name", "Value": "test"},
|
||||||
|
]
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
DryRun=False,
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "lowestPrice",
|
||||||
|
"InstanceInterruptionBehavior": "terminate",
|
||||||
|
},
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 1,
|
||||||
|
"SpotTargetCapacity": 2,
|
||||||
|
"TotalTargetCapacity": 3,
|
||||||
|
},
|
||||||
|
Type="request",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
TagSpecifications=[
|
||||||
|
{
|
||||||
|
"ResourceType": "fleet",
|
||||||
|
"Tags": tags,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
fleets = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"]
|
||||||
|
|
||||||
|
fleets[0]["Tags"].should.equal(tags)
|
||||||
|
|
||||||
|
instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
instances = conn.describe_instances(
|
||||||
|
InstanceIds=[i["InstanceId"] for i in instance_res["ActiveInstances"]]
|
||||||
|
)
|
||||||
|
for instance in instances["Reservations"][0]["Instances"]:
|
||||||
|
for tag in tags_instance:
|
||||||
|
instance["Tags"].should.contain(tag)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_fleet_using_launch_template_config__overrides():
|
||||||
|
|
||||||
|
conn = boto3.client("ec2", region_name="us-east-2")
|
||||||
|
subnet_id = get_subnet_id(conn)
|
||||||
|
|
||||||
|
template_id, _ = get_launch_template(conn, instance_type="t2.medium")
|
||||||
|
|
||||||
|
template_config_overrides = [
|
||||||
|
{
|
||||||
|
"InstanceType": "t2.nano",
|
||||||
|
"SubnetId": subnet_id,
|
||||||
|
"AvailabilityZone": "us-west-1",
|
||||||
|
"WeightedCapacity": 2,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
"Overrides": template_config_overrides,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 1,
|
||||||
|
"SpotTargetCapacity": 0,
|
||||||
|
"TotalTargetCapacity": 1,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "lowest-price",
|
||||||
|
"InstanceInterruptionBehavior": "terminate",
|
||||||
|
},
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
|
||||||
|
instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
instances = instance_res["ActiveInstances"]
|
||||||
|
instances.should.have.length_of(1)
|
||||||
|
instances[0].should.have.key("InstanceType").equals("t2.nano")
|
||||||
|
|
||||||
|
instance = conn.describe_instances(
|
||||||
|
InstanceIds=[i["InstanceId"] for i in instances]
|
||||||
|
)["Reservations"][0]["Instances"][0]
|
||||||
|
instance.should.have.key("SubnetId").equals(subnet_id)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_delete_fleet():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-2")
|
||||||
|
|
||||||
|
launch_template_id, _ = get_launch_template(conn)
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 1,
|
||||||
|
"SpotTargetCapacity": 2,
|
||||||
|
"TotalTargetCapacity": 3,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "lowestPrice",
|
||||||
|
"InstanceInterruptionBehavior": "terminate",
|
||||||
|
},
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
|
||||||
|
delete_fleet_out = conn.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True)
|
||||||
|
|
||||||
|
delete_fleet_out["SuccessfulFleetDeletions"].should.have.length_of(1)
|
||||||
|
delete_fleet_out["SuccessfulFleetDeletions"][0]["FleetId"].should.equal(fleet_id)
|
||||||
|
delete_fleet_out["SuccessfulFleetDeletions"][0]["CurrentFleetState"].should.equal(
|
||||||
|
"deleted"
|
||||||
|
)
|
||||||
|
|
||||||
|
fleets = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"]
|
||||||
|
len(fleets).should.equal(1)
|
||||||
|
|
||||||
|
target_capacity_specification = fleets[0]["TargetCapacitySpecification"]
|
||||||
|
target_capacity_specification.should.have.key("TotalTargetCapacity").equals(0)
|
||||||
|
fleets[0]["FleetState"].should.equal("deleted")
|
||||||
|
|
||||||
|
# Instances should be terminated
|
||||||
|
instance_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
instances = instance_res["ActiveInstances"]
|
||||||
|
len(instances).should.equal(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_describe_fleet_instences_api():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-1")
|
||||||
|
|
||||||
|
launch_template_id, _ = get_launch_template(conn)
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 1,
|
||||||
|
"SpotTargetCapacity": 2,
|
||||||
|
"TotalTargetCapacity": 3,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "lowestPrice",
|
||||||
|
"InstanceInterruptionBehavior": "terminate",
|
||||||
|
},
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
fleet_res = conn.describe_fleet_instances(FleetId=fleet_id)
|
||||||
|
|
||||||
|
fleet_res["FleetId"].should.equal(fleet_id)
|
||||||
|
fleet_res["ActiveInstances"].should.have.length_of(3)
|
||||||
|
|
||||||
|
instance_ids = [i["InstanceId"] for i in fleet_res["ActiveInstances"]]
|
||||||
|
for instance_id in instance_ids:
|
||||||
|
instance_id.startswith("i-").should.be.true
|
||||||
|
|
||||||
|
instance_types = [i["InstanceType"] for i in fleet_res["ActiveInstances"]]
|
||||||
|
instance_types.should.equal(["t2.micro", "t2.micro", "t2.micro"])
|
||||||
|
|
||||||
|
instance_healths = [i["InstanceHealth"] for i in fleet_res["ActiveInstances"]]
|
||||||
|
instance_healths.should.equal(["healthy", "healthy", "healthy"])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_fleet_api():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-1")
|
||||||
|
|
||||||
|
launch_template_id, _ = get_launch_template(conn)
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
LaunchTemplateConfigs=[
|
||||||
|
{
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "spot",
|
||||||
|
"OnDemandTargetCapacity": 1,
|
||||||
|
"SpotTargetCapacity": 2,
|
||||||
|
"TotalTargetCapacity": 3,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "lowestPrice",
|
||||||
|
"InstanceInterruptionBehavior": "terminate",
|
||||||
|
},
|
||||||
|
Type="instant",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
fleet_res.should.have.key("FleetId")
|
||||||
|
fleet_res["FleetId"].startswith("fleet-").should.be.true
|
||||||
|
|
||||||
|
fleet_res.should.have.key("Instances")
|
||||||
|
fleet_res["Instances"].should.have.length_of(3)
|
||||||
|
|
||||||
|
instance_ids = [i["InstanceIds"] for i in fleet_res["Instances"]]
|
||||||
|
for instance_id in instance_ids:
|
||||||
|
instance_id[0].startswith("i-").should.be.true
|
||||||
|
|
||||||
|
instance_types = [i["InstanceType"] for i in fleet_res["Instances"]]
|
||||||
|
instance_types.should.equal(["t2.micro", "t2.micro", "t2.micro"])
|
||||||
|
|
||||||
|
lifecycle = [i["Lifecycle"] for i in fleet_res["Instances"]]
|
||||||
|
lifecycle.should.contain("spot")
|
||||||
|
lifecycle.should.contain("on-demand")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_fleet_api_response():
|
||||||
|
conn = boto3.client("ec2", region_name="us-west-2")
|
||||||
|
subnet_id = get_subnet_id(conn)
|
||||||
|
|
||||||
|
launch_template_id, _ = get_launch_template(conn)
|
||||||
|
|
||||||
|
lt_config = {
|
||||||
|
"LaunchTemplateSpecification": {
|
||||||
|
"LaunchTemplateId": launch_template_id,
|
||||||
|
"Version": "1",
|
||||||
|
},
|
||||||
|
"Overrides": [
|
||||||
|
{
|
||||||
|
"AvailabilityZone": "us-west-1a",
|
||||||
|
"InstanceRequirements": {
|
||||||
|
"AcceleratorCount": {
|
||||||
|
"Max": 10,
|
||||||
|
"Min": 1,
|
||||||
|
},
|
||||||
|
"AcceleratorManufacturers": ["nvidia"],
|
||||||
|
"AcceleratorNames": ["t4"],
|
||||||
|
"AcceleratorTotalMemoryMiB": {
|
||||||
|
"Max": 20972,
|
||||||
|
"Min": 1,
|
||||||
|
},
|
||||||
|
"AcceleratorTypes": ["gpu"],
|
||||||
|
"BareMetal": "included",
|
||||||
|
"BaselineEbsBandwidthMbps": {
|
||||||
|
"Max": 10000,
|
||||||
|
"Min": 125,
|
||||||
|
},
|
||||||
|
"BurstablePerformance": "included",
|
||||||
|
"CpuManufacturers": ["amd", "intel"],
|
||||||
|
"ExcludedInstanceTypes": ["m5.8xlarge"],
|
||||||
|
"InstanceGenerations": ["current"],
|
||||||
|
"LocalStorage": "included",
|
||||||
|
"LocalStorageTypes": ["ssd"],
|
||||||
|
"MemoryGiBPerVCpu": {
|
||||||
|
"Min": 1,
|
||||||
|
"Max": 160,
|
||||||
|
},
|
||||||
|
"MemoryMiB": {
|
||||||
|
"Min": 2048,
|
||||||
|
"Max": 40960,
|
||||||
|
},
|
||||||
|
"NetworkInterfaceCount": {
|
||||||
|
"Max": 1,
|
||||||
|
"Min": 1,
|
||||||
|
},
|
||||||
|
"OnDemandMaxPricePercentageOverLowestPrice": 99999,
|
||||||
|
"RequireHibernateSupport": True,
|
||||||
|
"SpotMaxPricePercentageOverLowestPrice": 99999,
|
||||||
|
"TotalLocalStorageGB": {
|
||||||
|
"Min": 100,
|
||||||
|
"Max": 10000,
|
||||||
|
},
|
||||||
|
"VCpuCount": {
|
||||||
|
"Min": 2,
|
||||||
|
"Max": 160,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"MaxPrice": "0.5",
|
||||||
|
"Priority": 2,
|
||||||
|
"SubnetId": subnet_id,
|
||||||
|
"WeightedCapacity": 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
fleet_res = conn.create_fleet(
|
||||||
|
ExcessCapacityTerminationPolicy="no-termination",
|
||||||
|
LaunchTemplateConfigs=[lt_config],
|
||||||
|
TargetCapacitySpecification={
|
||||||
|
"DefaultTargetCapacityType": "on-demand",
|
||||||
|
"OnDemandTargetCapacity": 10,
|
||||||
|
"SpotTargetCapacity": 10,
|
||||||
|
"TotalTargetCapacity": 30,
|
||||||
|
},
|
||||||
|
SpotOptions={
|
||||||
|
"AllocationStrategy": "lowest-price",
|
||||||
|
"InstanceInterruptionBehavior": "terminate",
|
||||||
|
"InstancePoolsToUseCount": 1,
|
||||||
|
"MaintenanceStrategies": {
|
||||||
|
"CapacityRebalance": {
|
||||||
|
"ReplacementStrategy": "launch-before-terminate",
|
||||||
|
"TerminationDelay": 120,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"MaxTotalPrice": "50",
|
||||||
|
"MinTargetCapacity": 1,
|
||||||
|
"SingleAvailabilityZone": True,
|
||||||
|
"SingleInstanceType": True,
|
||||||
|
},
|
||||||
|
OnDemandOptions={
|
||||||
|
"AllocationStrategy": "lowest-price",
|
||||||
|
"MaxTotalPrice": "50",
|
||||||
|
"MinTargetCapacity": 1,
|
||||||
|
"SingleAvailabilityZone": True,
|
||||||
|
"SingleInstanceType": True,
|
||||||
|
},
|
||||||
|
ReplaceUnhealthyInstances=True,
|
||||||
|
TerminateInstancesWithExpiration=True,
|
||||||
|
Type="maintain",
|
||||||
|
ValidFrom="2020-01-01T00:00:00Z",
|
||||||
|
ValidUntil="2020-12-31T00:00:00Z",
|
||||||
|
)
|
||||||
|
fleet_id = fleet_res["FleetId"]
|
||||||
|
|
||||||
|
fleet_res = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"]
|
||||||
|
fleet_res.should.have.length_of(1)
|
||||||
|
fleet_res[0].should.have.key("FleetId").equals(fleet_id)
|
||||||
|
fleet_res[0].should.have.key("ExcessCapacityTerminationPolicy").equals(
|
||||||
|
"no-termination"
|
||||||
|
)
|
||||||
|
fleet_res[0].should.have.key("LaunchTemplateConfigs").equals([lt_config])
|
||||||
|
fleet_res[0].should.have.key("TargetCapacitySpecification").equals(
|
||||||
|
{
|
||||||
|
"DefaultTargetCapacityType": "on-demand",
|
||||||
|
"OnDemandTargetCapacity": 10,
|
||||||
|
"SpotTargetCapacity": 10,
|
||||||
|
"TotalTargetCapacity": 30,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fleet_res[0].should.have.key("SpotOptions").equals(
|
||||||
|
{
|
||||||
|
"AllocationStrategy": "lowest-price",
|
||||||
|
"InstanceInterruptionBehavior": "terminate",
|
||||||
|
"InstancePoolsToUseCount": 1,
|
||||||
|
"MaintenanceStrategies": {
|
||||||
|
"CapacityRebalance": {
|
||||||
|
"ReplacementStrategy": "launch-before-terminate",
|
||||||
|
"TerminationDelay": 120,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"MaxTotalPrice": "50",
|
||||||
|
"MinTargetCapacity": 1,
|
||||||
|
"SingleAvailabilityZone": True,
|
||||||
|
"SingleInstanceType": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fleet_res[0].should.have.key("OnDemandOptions").equals(
|
||||||
|
{
|
||||||
|
"AllocationStrategy": "lowest-price",
|
||||||
|
"MaxTotalPrice": "50",
|
||||||
|
"MinTargetCapacity": 1,
|
||||||
|
"SingleAvailabilityZone": True,
|
||||||
|
"SingleInstanceType": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fleet_res[0].should.have.key("ReplaceUnhealthyInstances").equals(True)
|
||||||
|
fleet_res[0].should.have.key("TerminateInstancesWithExpiration").equals(True)
|
||||||
|
fleet_res[0].should.have.key("Type").equals("maintain")
|
||||||
|
fleet_res[0].should.have.key("ValidFrom")
|
||||||
|
fleet_res[0]["ValidFrom"].isoformat().should.equal("2020-01-01T00:00:00+00:00")
|
||||||
|
fleet_res[0].should.have.key("ValidUntil")
|
||||||
|
fleet_res[0]["ValidUntil"].isoformat().should.equal("2020-12-31T00:00:00+00:00")
|
Loading…
Reference in New Issue
Block a user