diff --git a/ecs_composex/ecs/service_compute/__init__.py b/ecs_composex/ecs/service_compute/__init__.py index d05ca903..5b7c41da 100644 --- a/ecs_composex/ecs/service_compute/__init__.py +++ b/ecs_composex/ecs/service_compute/__init__.py @@ -93,32 +93,58 @@ def set_update_capacity_providers(self) -> None: def set_update_launch_type(self) -> None: """ - Goes over all the services and verifies if one of them is set to use EXTERNAL mode. - If so, overrides for all + Defines all the possible launch types defined on the services of the task into `launch_types` + If this is the first time we run the function, and we don't have either self.launch_type nor `launch_types` + we default to Fargate + Considering that `EXTERNAL` > `EC2` > `FARGATE` in priority, we stop at that launch_type as soon as any service + has it set in its properties. + If launch_type wasn't set, we stop at the highest priority type + If launch_type was already set, we evaluate it a new service might have a different launch_type, higher priority + If we are already all set, debug log it. + Otherwise, if somehow we didn't get into any of these conditions, pick default => Fargate """ - launch_modes = [ + launch_types = [ _svc.launch_type for _svc in self.family.ordered_services if _svc.launch_type ] - if not self.launch_type and not launch_modes: + if not self.launch_type and not launch_types: LOG.info( "services.{} - No Launch Type defined. Using default: {}".format( self.family.name, LAUNCH_TYPE.Default ) ) - LOG.debug( - f"{self.family.name} - Using default Launch Type - {LAUNCH_TYPE.Default}" - ) self.launch_type = LAUNCH_TYPE.Default return - modes_priority_ordered = ["EXTERNAL", "EC2", "FARGATE"] - for mode in modes_priority_ordered: - if mode in launch_modes and self.launch_type != mode: + + launch_types_priority_ordered = ["EXTERNAL", "EC2", "FARGATE"] + for _launch_type in launch_types_priority_ordered: + type_in_family_launch_types: bool = _launch_type in launch_types + type_is_defined_launch_type: bool = self.launch_type == _launch_type + + if not self.launch_type and type_in_family_launch_types: LOG.info( - f"{self.family.name} - At least one service defined for {mode}. Overriding for all" + f"{self.family.name} - Init LaunchType {_launch_type}, requested by at least one service." ) - self.launch_type = mode + self.launch_type = _launch_type + break + elif ( + self.launch_type + and type_in_family_launch_types + and not type_is_defined_launch_type + ): + LOG.info( + f"{self.family.name} - A service in family requires {_launch_type} instead of {self.launch_type}." + " Overriding." + ) + self.launch_type = _launch_type + break + elif ( + self.launch_type + and type_in_family_launch_types + and type_is_defined_launch_type + ): + LOG.debug(f"{self.family.name} is already set to {_launch_type}") break else: LOG.debug( diff --git a/ecs_composex/ecs_cluster/ecs_family_helpers.py b/ecs_composex/ecs_cluster/ecs_family_helpers.py index 18bf591c..e3bf997d 100644 --- a/ecs_composex/ecs_cluster/ecs_family_helpers.py +++ b/ecs_composex/ecs_cluster/ecs_family_helpers.py @@ -99,14 +99,33 @@ def validate_compute_configuration_for_task( ) -def set_launch_type_from_cluster_and_service(family: ComposeFamily) -> None: - if all( - provider.CapacityProvider in ["FARGATE", "FARGATE_SPOT"] - for provider in family.service_compute.ecs_capacity_providers - ): - LOG.debug( - f"{family.name} - Cluster and Service use Fargate only. Setting to FARGATE_PROVIDERS" +def set_launch_type_from_cluster_and_service( + family: ComposeFamily, cluster: EcsCluster +) -> None: + """ + Sets the launch type based on the service and capacity providers + + If all the capacity providers of the service are FARGATE, we use `FARGATE_PROVIDERS` which removes `LaunchType` from + ECS Service definition + Otherwise, we use the capacity providers set which use AutoScaling. + """ + family_providers: list = [ + cap.CapacityProvider for cap in family.service_compute.ecs_capacity_providers + ] + family_uses_fargate_only = all( + provider in FARGATE_PROVIDERS for provider in family_providers + ) + cluster_uses_fargate_only = all( + provider in FARGATE_PROVIDERS for provider in cluster.capacity_providers + ) + if not all(provider in cluster.capacity_providers for provider in family_providers): + raise AttributeError( + "Family {} tries to use providers not available in the cluster. " + "Wants: {}. Available: {}".format( + family.name, family_providers, cluster.capacity_providers + ) ) + if family_uses_fargate_only and cluster_uses_fargate_only: family.service_compute.launch_type = "FARGATE_PROVIDERS" else: family.service_compute.launch_type = "SERVICE_MODE" @@ -122,6 +141,11 @@ def set_launch_type_from_cluster_and_service(family: ComposeFamily) -> None: def set_launch_type_from_cluster_only( family: ComposeFamily, cluster: EcsCluster ) -> None: + """ + When the family x-ecs has not set CapacityProviders, we rely on the ECS Cluster definition. + If all the capacity providers defined on the Cluster are FARGATE related, use `FARGATE_PROVIDERS` + Otherwise, use the ECS Cluster defined capacity providers based on the Cluster strategy. + """ if any( provider in ["FARGATE", "FARGATE_SPOT"] for provider in cluster.default_strategy_providers @@ -142,17 +166,20 @@ def set_launch_type_from_cluster_only( family.service_compute.launch_type = "CLUSTER_MODE" -def set_service_launch_type(family, cluster) -> None: +def set_service_launch_type(family: ComposeFamily, cluster) -> None: """ Sets the LaunchType value for the ECS Service + If the LaunchType is EXTERNAL or EC2, we ignore Capacity Providers altogether. """ - if ( - family.service_compute.launch_type == "EXTERNAL" - or family.service_compute.launch_type == "EC2" - ): + if family.service_compute.launch_type in ["EXTERNAL", "EC2"]: + LOG.debug( + "services.{} uses {}. Skipping".format( + family.name, family.service_compute.launch_type + ) + ) return if family.service_compute.ecs_capacity_providers and cluster.capacity_providers: - set_launch_type_from_cluster_and_service(family) + set_launch_type_from_cluster_and_service(family, cluster) elif ( not family.service_compute.ecs_capacity_providers and cluster.capacity_providers ):