2020-09-17 10:42:19 +02:00
/ *
2020-09-22 12:13:00 +02:00
Copyright 2020 Docker Compose CLI authors
2020-09-17 10:42:19 +02:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package ecs
import (
"fmt"
2020-09-17 12:24:11 +02:00
"math"
2020-09-17 10:42:19 +02:00
"strconv"
"github.com/compose-spec/compose-go/types"
"github.com/docker/go-units"
)
type machine struct {
id string
cpus float64
memory types . UnitBytes
gpus int64
}
type family [ ] machine
2020-09-07 11:20:41 +02:00
var gpufamily = family {
2020-09-17 10:42:19 +02:00
{
2020-09-07 11:20:41 +02:00
id : "g4dn.xlarge" ,
cpus : 4 ,
memory : 16 * units . GiB ,
gpus : 1 ,
} ,
{
id : "g4dn.2xlarge" ,
2020-09-17 10:42:19 +02:00
cpus : 8 ,
2020-09-07 11:20:41 +02:00
memory : 32 * units . GiB ,
gpus : 1 ,
} ,
{
id : "g4dn.4xlarge" ,
cpus : 16 ,
2020-09-17 10:42:19 +02:00
memory : 64 * units . GiB ,
2020-09-07 11:20:41 +02:00
gpus : 1 ,
2020-09-17 10:42:19 +02:00
} ,
{
2020-09-07 11:20:41 +02:00
id : "g4dn.8xlarge" ,
2020-09-17 10:42:19 +02:00
cpus : 32 ,
2020-09-07 11:20:41 +02:00
memory : 128 * units . GiB ,
gpus : 1 ,
} ,
{
id : "g4dn.12xlarge" ,
cpus : 48 ,
memory : 192 * units . GiB ,
2020-09-17 10:42:19 +02:00
gpus : 4 ,
} ,
{
2020-09-07 11:20:41 +02:00
id : "g4dn.16xlarge" ,
2020-09-17 10:42:19 +02:00
cpus : 64 ,
2020-09-07 11:20:41 +02:00
memory : 256 * units . GiB ,
gpus : 1 ,
} ,
{
id : "g4dn.metal" ,
cpus : 96 ,
memory : 384 * units . GiB ,
2020-09-17 10:42:19 +02:00
gpus : 8 ,
} ,
}
type filterFn func ( machine ) bool
func ( f family ) filter ( fn filterFn ) family {
var filtered family
for _ , machine := range f {
if fn ( machine ) {
filtered = append ( filtered , machine )
}
}
return filtered
}
func ( f family ) firstOrError ( msg string , args ... interface { } ) ( machine , error ) {
if len ( f ) == 0 {
return machine { } , fmt . Errorf ( msg , args ... )
}
return f [ 0 ] , nil
}
func guessMachineType ( project * types . Project ) ( string , error ) {
2020-09-17 12:24:11 +02:00
// we select a machine type to match all gpus-bound services requirements
2020-09-17 10:42:19 +02:00
// once https://github.com/aws/containers-roadmap/issues/631 is implemented we can define dedicated CapacityProviders per service.
2020-09-17 12:24:11 +02:00
requirements , err := getResourceRequirements ( project )
2020-09-17 10:42:19 +02:00
if err != nil {
return "" , err
}
2020-09-07 11:20:41 +02:00
instanceType , err := gpufamily .
2020-09-17 10:42:19 +02:00
filter ( func ( m machine ) bool {
2020-09-23 12:18:42 +02:00
return m . memory > requirements . memory // actual memory available for ECS tasks < total machine memory
2020-09-17 10:42:19 +02:00
} ) .
filter ( func ( m machine ) bool {
2020-09-17 12:24:11 +02:00
return m . cpus >= requirements . cpus
2020-09-17 10:42:19 +02:00
} ) .
filter ( func ( m machine ) bool {
2020-09-17 12:24:11 +02:00
return m . gpus >= requirements . gpus
2020-09-17 10:42:19 +02:00
} ) .
2020-09-07 11:20:41 +02:00
firstOrError ( "none of the Amazon EC2 G4 instance types meet the requirements for memory:%d cpu:%f gpus:%d" , requirements . memory , requirements . cpus , requirements . gpus )
2020-09-17 10:42:19 +02:00
if err != nil {
return "" , err
}
return instanceType . id , nil
}
2020-09-17 12:24:11 +02:00
type resourceRequirements struct {
memory types . UnitBytes
cpus float64
gpus int64
}
func getResourceRequirements ( project * types . Project ) ( * resourceRequirements , error ) {
return toResourceRequirementsSlice ( project ) .
filter ( func ( requirements * resourceRequirements ) bool {
2021-01-05 16:40:52 +01:00
return requirements != nil && requirements . gpus != 0
2020-09-17 12:24:11 +02:00
} ) .
max ( )
}
type eitherRequirementsOrError struct {
requirements [ ] * resourceRequirements
err error
}
func toResourceRequirementsSlice ( project * types . Project ) eitherRequirementsOrError {
var requirements [ ] * resourceRequirements
2020-09-17 10:42:19 +02:00
for _ , service := range project . Services {
2020-09-17 12:24:11 +02:00
r , err := toResourceRequirements ( service )
if err != nil {
return eitherRequirementsOrError { nil , err }
2020-09-17 10:42:19 +02:00
}
2020-09-17 12:24:11 +02:00
requirements = append ( requirements , r )
}
return eitherRequirementsOrError { requirements , nil }
}
2020-09-17 10:42:19 +02:00
2020-09-17 12:24:11 +02:00
func ( r eitherRequirementsOrError ) filter ( fn func ( * resourceRequirements ) bool ) eitherRequirementsOrError {
if r . err != nil {
return r
}
var requirements [ ] * resourceRequirements
for _ , req := range r . requirements {
if fn ( req ) {
requirements = append ( requirements , req )
2020-09-17 10:42:19 +02:00
}
2020-09-17 12:24:11 +02:00
}
return eitherRequirementsOrError { requirements , nil }
}
2020-09-17 10:42:19 +02:00
2020-09-17 12:24:11 +02:00
func toResourceRequirements ( service types . ServiceConfig ) ( * resourceRequirements , error ) {
if service . Deploy == nil {
return nil , nil
}
reservations := service . Deploy . Resources . Reservations
if reservations == nil {
return nil , nil
}
var requiredGPUs int64
for _ , r := range reservations . GenericResources {
if r . DiscreteResourceSpec . Kind == "gpus" {
requiredGPUs = r . DiscreteResourceSpec . Value
break
2020-09-17 10:42:19 +02:00
}
2020-09-17 12:24:11 +02:00
}
2021-01-05 16:40:52 +01:00
for _ , r := range reservations . Devices {
requiresGpus := false
for _ , c := range r . Capabilities {
if c == "gpu" {
requiresGpus = true
break
}
}
if requiresGpus {
requiredGPUs = r . Count
if requiredGPUs <= 0 {
requiredGPUs = 1
}
break
}
}
2020-09-17 12:24:11 +02:00
var nanocpu float64
if reservations . NanoCPUs != "" {
v , err := strconv . ParseFloat ( reservations . NanoCPUs , 64 )
if err != nil {
return nil , err
2020-09-17 10:42:19 +02:00
}
2020-09-17 12:24:11 +02:00
nanocpu = v
}
return & resourceRequirements {
memory : reservations . MemoryBytes ,
cpus : nanocpu ,
gpus : requiredGPUs ,
} , nil
}
func ( r resourceRequirements ) combine ( o * resourceRequirements ) resourceRequirements {
if o == nil {
return r
}
return resourceRequirements {
memory : maxUnitBytes ( r . memory , o . memory ) ,
cpus : math . Max ( r . cpus , o . cpus ) ,
gpus : maxInt64 ( r . gpus , o . gpus ) ,
}
}
func ( r eitherRequirementsOrError ) max ( ) ( * resourceRequirements , error ) {
if r . err != nil {
return nil , r . err
}
min := resourceRequirements { }
for _ , req := range r . requirements {
min = min . combine ( req )
}
return & min , nil
}
func maxInt64 ( a , b int64 ) int64 {
if a > b {
return a
}
return b
}
func maxUnitBytes ( a , b types . UnitBytes ) types . UnitBytes {
if a > b {
return a
2020-09-17 10:42:19 +02:00
}
2020-09-17 12:24:11 +02:00
return b
2020-09-17 10:42:19 +02:00
}