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:
parent
11ae921364
commit
a53e14b4e8
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue