Chunk taskArns into groups of 100
If the ECS cluster has > 100 tasks, passing them to ecs.DescribeTasksRequest() will result in the AWS API returning errors. This patch breaks them into chunks of at most 100, and calls DescribeTasks for each chunk. We also return early in case ListTasks returns no values; this prevents DescribeTasks from throwing HTTP errors.
This commit is contained in:
parent
ec7ba15955
commit
c876462eb0
2 changed files with 78 additions and 8 deletions
|
@ -206,14 +206,29 @@ func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ec
|
||||||
taskArns = append(taskArns, req.Data.(*ecs.ListTasksOutput).TaskArns...)
|
taskArns = append(taskArns, req.Data.(*ecs.ListTasksOutput).TaskArns...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Early return: if we can't list tasks we have nothing to
|
||||||
|
// describe below - likely empty cluster/permissions are bad. This
|
||||||
|
// stops the AWS API from returning a 401 when you DescribeTasks
|
||||||
|
// with no input.
|
||||||
|
if len(taskArns) == 0 {
|
||||||
|
return []ecsInstance{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkedTaskArns := provider.chunkedTaskArns(taskArns)
|
||||||
|
var tasks []*ecs.Task
|
||||||
|
|
||||||
|
for _, arns := range chunkedTaskArns {
|
||||||
req, taskResp := client.ecs.DescribeTasksRequest(&ecs.DescribeTasksInput{
|
req, taskResp := client.ecs.DescribeTasksRequest(&ecs.DescribeTasksInput{
|
||||||
Tasks: taskArns,
|
Tasks: arns,
|
||||||
Cluster: &provider.Cluster,
|
Cluster: &provider.Cluster,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := wrapAws(ctx, req); err != nil {
|
if err := wrapAws(ctx, req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tasks = append(tasks, taskResp.Tasks...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
containerInstanceArns := make([]*string, 0)
|
containerInstanceArns := make([]*string, 0)
|
||||||
byContainerInstance := make(map[string]int)
|
byContainerInstance := make(map[string]int)
|
||||||
|
@ -221,7 +236,7 @@ func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ec
|
||||||
taskDefinitionArns := make([]*string, 0)
|
taskDefinitionArns := make([]*string, 0)
|
||||||
byTaskDefinition := make(map[string]int)
|
byTaskDefinition := make(map[string]int)
|
||||||
|
|
||||||
for _, task := range taskResp.Tasks {
|
for _, task := range tasks {
|
||||||
if _, found := byContainerInstance[*task.ContainerInstanceArn]; !found {
|
if _, found := byContainerInstance[*task.ContainerInstanceArn]; !found {
|
||||||
byContainerInstance[*task.ContainerInstanceArn] = len(containerInstanceArns)
|
byContainerInstance[*task.ContainerInstanceArn] = len(containerInstanceArns)
|
||||||
containerInstanceArns = append(containerInstanceArns, task.ContainerInstanceArn)
|
containerInstanceArns = append(containerInstanceArns, task.ContainerInstanceArn)
|
||||||
|
@ -243,7 +258,7 @@ func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ec
|
||||||
}
|
}
|
||||||
|
|
||||||
var instances []ecsInstance
|
var instances []ecsInstance
|
||||||
for _, task := range taskResp.Tasks {
|
for _, task := range tasks {
|
||||||
|
|
||||||
machineIdx := byContainerInstance[*task.ContainerInstanceArn]
|
machineIdx := byContainerInstance[*task.ContainerInstanceArn]
|
||||||
taskDefIdx := byTaskDefinition[*task.TaskDefinitionArn]
|
taskDefIdx := byTaskDefinition[*task.TaskDefinitionArn]
|
||||||
|
@ -398,6 +413,22 @@ func (provider *ECS) getFrontendRule(i ecsInstance) string {
|
||||||
return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + provider.Domain
|
return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ECS expects no more than 100 parameters be passed to a DescribeTask call; thus, pack
|
||||||
|
// each string into an array capped at 100 elements
|
||||||
|
func (provider *ECS) chunkedTaskArns(tasks []*string) [][]*string {
|
||||||
|
var chunkedTasks [][]*string
|
||||||
|
for i := 0; i < len(tasks); i += 100 {
|
||||||
|
sliceEnd := -1
|
||||||
|
if i+100 < len(tasks) {
|
||||||
|
sliceEnd = i + 100
|
||||||
|
} else {
|
||||||
|
sliceEnd = len(tasks)
|
||||||
|
}
|
||||||
|
chunkedTasks = append(chunkedTasks, tasks[i:sliceEnd])
|
||||||
|
}
|
||||||
|
return chunkedTasks
|
||||||
|
}
|
||||||
|
|
||||||
func (i ecsInstance) Protocol() string {
|
func (i ecsInstance) Protocol() string {
|
||||||
if label := i.label("traefik.protocol"); label != "" {
|
if label := i.label("traefik.protocol"); label != "" {
|
||||||
return label
|
return label
|
||||||
|
|
|
@ -308,3 +308,42 @@ func TestFilterInstance(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskChunking(t *testing.T) {
|
||||||
|
provider := &ECS{}
|
||||||
|
|
||||||
|
testval := "a"
|
||||||
|
cases := []struct {
|
||||||
|
count int
|
||||||
|
expectedLengths []int
|
||||||
|
}{
|
||||||
|
{0, []int(nil)},
|
||||||
|
{1, []int{1}},
|
||||||
|
{99, []int{99}},
|
||||||
|
{100, []int{100}},
|
||||||
|
{101, []int{100, 1}},
|
||||||
|
{199, []int{100, 99}},
|
||||||
|
{200, []int{100, 100}},
|
||||||
|
{201, []int{100, 100, 1}},
|
||||||
|
{555, []int{100, 100, 100, 100, 100, 55}},
|
||||||
|
{1001, []int{100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
var tasks []*string
|
||||||
|
for v := 0; v < c.count; v++ {
|
||||||
|
tasks = append(tasks, &testval)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := provider.chunkedTaskArns(tasks)
|
||||||
|
var outCount []int
|
||||||
|
|
||||||
|
for _, el := range out {
|
||||||
|
outCount = append(outCount, len(el))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(outCount, c.expectedLengths) {
|
||||||
|
t.Errorf("Chunking %d elements, expected %#v, got %#v", c.count, c.expectedLengths, outCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue