runservice: check if executor is alive before scheduling tasks

Check that the last update time is less than 1 minute (currently hardcoded)
This commit is contained in:
Simone Gotti 2019-06-12 18:12:37 +02:00
parent 11ae921364
commit a53e14b4e8
4 changed files with 141 additions and 2 deletions

View File

@ -21,6 +21,7 @@ import (
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
"time"
"github.com/sorintlab/agola/internal/etcd" "github.com/sorintlab/agola/internal/etcd"
"github.com/sorintlab/agola/internal/objectstorage" "github.com/sorintlab/agola/internal/objectstorage"
@ -57,6 +58,9 @@ func (h *ExecutorStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
return return
} }
// set last status update time
executor.LastStatusUpdateTime = time.Now()
if _, err := store.PutExecutor(ctx, h.e, executor); err != nil { if _, err := store.PutExecutor(ctx, h.e, executor); err != nil {
http.Error(w, "", http.StatusInternalServerError) http.Error(w, "", http.StatusInternalServerError)
return return

View File

@ -43,6 +43,8 @@ import (
const ( const (
cacheCleanerInterval = 1 * 24 * time.Hour cacheCleanerInterval = 1 * 24 * time.Hour
defaultExecutorNotAliveInterval = 60 * time.Second
) )
var level = zap.NewAtomicLevelAt(zapcore.InfoLevel) var level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
@ -273,7 +275,15 @@ func (s *Runservice) chooseExecutor(ctx context.Context, rct *types.RunConfigTas
if err != nil { if err != nil {
return nil, err return nil, err
} }
return chooseExecutor(executors, rct), nil
}
func chooseExecutor(executors []*types.Executor, rct *types.RunConfigTask) *types.Executor {
for _, e := range executors { for _, e := range executors {
if e.LastStatusUpdateTime.Add(defaultExecutorNotAliveInterval).Before(time.Now()) {
continue
}
// if arch is not defined use any executor arch // if arch is not defined use any executor arch
if rct.Runtime.Arch != "" { if rct.Runtime.Arch != "" {
hasArch := false hasArch := false
@ -286,15 +296,17 @@ func (s *Runservice) chooseExecutor(ctx context.Context, rct *types.RunConfigTas
continue continue
} }
} }
if e.ActiveTasksLimit != 0 { if e.ActiveTasksLimit != 0 {
if e.ActiveTasks >= e.ActiveTasksLimit { if e.ActiveTasks >= e.ActiveTasksLimit {
continue continue
} }
} }
return e, nil return e
} }
return nil, nil
return nil
} }
type parentsByLevelName []*types.RunConfigTask type parentsByLevelName []*types.RunConfigTask

View File

@ -18,8 +18,10 @@ import (
"context" "context"
"sort" "sort"
"testing" "testing"
"time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/sorintlab/agola/internal/common"
"github.com/sorintlab/agola/internal/services/runservice/types" "github.com/sorintlab/agola/internal/services/runservice/types"
) )
@ -539,3 +541,106 @@ func TestGetTasksToRun(t *testing.T) {
}) })
} }
} }
func TestChooseExecutor(t *testing.T) {
executorOK := &types.Executor{
ID: "executorOK",
Archs: []common.Arch{common.ArchAMD64},
ActiveTasksLimit: 2,
ActiveTasks: 0,
LastStatusUpdateTime: time.Now(),
}
executorNoFreeTaskSlots := func() *types.Executor {
e := executorOK.DeepCopy()
e.ID = "executorNoFreeTasksSlots"
e.ActiveTasks = 2
return e
}()
executorNotAlive := func() *types.Executor {
e := executorOK.DeepCopy()
e.ID = "executorNotAlive"
e.LastStatusUpdateTime = time.Now().Add(-120 * time.Second)
return e
}()
executorOKMultipleArchs := func() *types.Executor {
e := executorOK.DeepCopy()
e.ID = "executorOKMultipleArchs"
e.Archs = []common.Arch{common.ArchAMD64, common.ArchARM64}
return e
}()
// Only primary and the required variables for this test are set
rct := &types.RunConfigTask{
ID: "task01",
Name: "task01",
Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
Arch: common.ArchAMD64,
},
}
tests := []struct {
name string
executors []*types.Executor
rct *types.RunConfigTask
out *types.Executor
err error
}{
{
name: "test single executor ok",
executors: []*types.Executor{executorOK},
// Only primary and the required variables for this test are set
rct: rct,
out: executorOK,
},
{
name: "test single executor without free task slots",
executors: []*types.Executor{executorNoFreeTaskSlots},
// Only primary and the required variables for this test are set
rct: rct,
out: nil,
},
{
name: "test single executor not alive",
executors: []*types.Executor{executorNotAlive},
rct: rct,
out: nil,
},
{
name: "test single executor with different arch",
executors: func() []*types.Executor {
e := executorOK.DeepCopy()
e.Archs = []common.Arch{common.ArchARM64}
return []*types.Executor{e}
}(),
rct: rct,
out: nil,
},
{
name: "test single executor with multiple archs and one matches the task required arch",
executors: []*types.Executor{executorOKMultipleArchs},
rct: rct,
out: executorOKMultipleArchs,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := chooseExecutor(tt.executors, tt.rct)
if e == nil && tt.out == nil {
return
}
if e == nil && tt.out != nil {
t.Fatalf("expected executor with id: %s, go no executor selected", tt.out.ID)
}
if e != nil && tt.out == nil {
t.Fatalf("expected no executor selected, got executor with id: %s", e.ID)
}
if e != tt.out {
t.Fatalf("wrong executor ID, expected %s, got: %s", tt.out.ID, e.ID)
}
})
}
}

View File

@ -333,6 +333,14 @@ type RunConfigTask struct {
DockerRegistriesAuth map[string]DockerRegistryAuth `json:"docker_registries_auth"` DockerRegistriesAuth map[string]DockerRegistryAuth `json:"docker_registries_auth"`
} }
func (rct *RunConfigTask) DeepCopy() *RunConfigTask {
nrct, err := copystructure.Copy(rct)
if err != nil {
panic(err)
}
return nrct.(*RunConfigTask)
}
type RunConfigTaskDependCondition string type RunConfigTaskDependCondition string
const ( const (
@ -611,10 +619,20 @@ type Executor struct {
// SiblingExecutors are all the executors in the ExecutorGroup // SiblingExecutors are all the executors in the ExecutorGroup
SiblingsExecutors []string `json:"siblings_executors,omitempty"` SiblingsExecutors []string `json:"siblings_executors,omitempty"`
LastStatusUpdateTime time.Time `json:"last_status_update_time,omitempty"`
// internal values not saved // internal values not saved
Revision int64 `json:"-"` Revision int64 `json:"-"`
} }
func (e *Executor) DeepCopy() *Executor {
ne, err := copystructure.Copy(e)
if err != nil {
panic(err)
}
return ne.(*Executor)
}
type RunEvent struct { type RunEvent struct {
Sequence string Sequence string
RunID string RunID string