Add forcePull option

* Add forcePull option
for pulling the image when the execution environment gets updated

* Apply suggestions from code review

Co-authored-by: Sebastian Serth <MrSerth@users.noreply.github.com>

* Add unit tests

* Clean up and implement option two

Co-authored-by: Sebastian Serth <MrSerth@users.noreply.github.com>
This commit is contained in:
Maximilian Paß
2021-12-09 14:54:14 +01:00
committed by GitHub
parent af939b7810
commit 825ebdd3e6
7 changed files with 81 additions and 14 deletions

View File

@ -6,10 +6,14 @@ client {
] ]
} }
# plugin "docker" { plugin "docker" {
# config { config {
gc {
image_delay = "0s"
}
# auth { # auth {
# config = "/root/.docker/config.json" # config = "/root/.docker/config.json"
# } # }
# } }
# } }

View File

@ -173,6 +173,7 @@ func (n *NomadEnvironment) SetNetworkAccess(allow bool, exposedPorts []uint16) {
// Register creates a Nomad job based on the default job configuration and the given parameters. // Register creates a Nomad job based on the default job configuration and the given parameters.
// It registers the job with Nomad and waits until the registration completes. // It registers the job with Nomad and waits until the registration completes.
func (n *NomadEnvironment) Register(apiClient nomad.ExecutorAPI) error { func (n *NomadEnvironment) Register(apiClient nomad.ExecutorAPI) error {
nomad.SetForcePullFlag(n.job, true) // This must be the default as otherwise new runners could have different images.
evalID, err := apiClient.RegisterNomadJob(n.job) evalID, err := apiClient.RegisterNomadJob(n.job)
if err != nil { if err != nil {
return fmt.Errorf("couldn't register job: %w", err) return fmt.Errorf("couldn't register job: %w", err)
@ -202,7 +203,7 @@ func (n *NomadEnvironment) Scale(apiClient nomad.ExecutorAPI) error {
required := int(n.PrewarmingPoolSize()) - n.idleRunners.Length() required := int(n.PrewarmingPoolSize()) - n.idleRunners.Length()
if required > 0 { if required > 0 {
return n.createRunners(apiClient, uint(required)) return n.createRunners(apiClient, uint(required), true)
} else { } else {
return n.removeRunners(apiClient, uint(-required)) return n.removeRunners(apiClient, uint(-required))
} }
@ -221,6 +222,7 @@ func (n *NomadEnvironment) UpdateRunnerSpecs(apiClient nomad.ExecutorAPI) error
updatedRunnerJob := n.DeepCopyJob() updatedRunnerJob := n.DeepCopyJob()
updatedRunnerJob.ID = &runnerID updatedRunnerJob.ID = &runnerID
updatedRunnerJob.Name = &runnerID updatedRunnerJob.Name = &runnerID
nomad.SetForcePullFlag(updatedRunnerJob, true)
err := apiClient.RegisterRunnerJob(updatedRunnerJob) err := apiClient.RegisterRunnerJob(updatedRunnerJob)
if err != nil { if err != nil {
@ -237,7 +239,7 @@ func (n *NomadEnvironment) Sample(apiClient nomad.ExecutorAPI) (runner.Runner, b
r, ok := n.idleRunners.Sample() r, ok := n.idleRunners.Sample()
if ok { if ok {
go func() { go func() {
err := n.createRunner(apiClient) err := n.createRunner(apiClient, false)
if err != nil { if err != nil {
log.WithError(err).WithField("environmentID", n.ID()).Error("Couldn't create new runner for claimed one") log.WithError(err).WithField("environmentID", n.ID()).Error("Couldn't create new runner for claimed one")
} }
@ -315,10 +317,10 @@ func parseJob(jobHCL string) (*nomadApi.Job, error) {
return job, nil return job, nil
} }
func (n *NomadEnvironment) createRunners(apiClient nomad.ExecutorAPI, count uint) error { func (n *NomadEnvironment) createRunners(apiClient nomad.ExecutorAPI, count uint, forcePull bool) error {
log.WithField("runnersRequired", count).WithField("id", n.ID()).Debug("Creating new runners") log.WithField("runnersRequired", count).WithField("id", n.ID()).Debug("Creating new runners")
for i := 0; i < int(count); i++ { for i := 0; i < int(count); i++ {
err := n.createRunner(apiClient) err := n.createRunner(apiClient, forcePull)
if err != nil { if err != nil {
return fmt.Errorf("couldn't create new runner: %w", err) return fmt.Errorf("couldn't create new runner: %w", err)
} }
@ -326,7 +328,7 @@ func (n *NomadEnvironment) createRunners(apiClient nomad.ExecutorAPI, count uint
return nil return nil
} }
func (n *NomadEnvironment) createRunner(apiClient nomad.ExecutorAPI) error { func (n *NomadEnvironment) createRunner(apiClient nomad.ExecutorAPI, forcePull bool) error {
newUUID, err := uuid.NewUUID() newUUID, err := uuid.NewUUID()
if err != nil { if err != nil {
return fmt.Errorf("failed generating runner id: %w", err) return fmt.Errorf("failed generating runner id: %w", err)
@ -336,6 +338,7 @@ func (n *NomadEnvironment) createRunner(apiClient nomad.ExecutorAPI) error {
template := n.DeepCopyJob() template := n.DeepCopyJob()
template.ID = &newRunnerID template.ID = &newRunnerID
template.Name = &newRunnerID template.Name = &newRunnerID
nomad.SetForcePullFlag(template, forcePull)
err = apiClient.RegisterRunnerJob(template) err = apiClient.RegisterRunnerJob(template)
if err != nil { if err != nil {

View File

@ -183,3 +183,28 @@ func TestTwoSampleAddExactlyTwoRunners(t *testing.T) {
<-time.After(tests.ShortTimeout) // New Runners are requested asynchronously <-time.After(tests.ShortTimeout) // New Runners are requested asynchronously
apiMock.AssertNumberOfCalls(t, "RegisterRunnerJob", 2) apiMock.AssertNumberOfCalls(t, "RegisterRunnerJob", 2)
} }
func TestSampleDoesNotSetForcePullFlag(t *testing.T) {
apiMock := &nomad.ExecutorAPIMock{}
call := apiMock.On("RegisterRunnerJob", mock.AnythingOfType("*api.Job"))
call.Run(func(args mock.Arguments) {
job, ok := args.Get(0).(*nomadApi.Job)
assert.True(t, ok)
taskGroup := nomad.FindOrCreateDefaultTaskGroup(job)
task := nomad.FindOrCreateDefaultTask(taskGroup)
assert.False(t, task.Config["force_pull"].(bool))
call.ReturnArguments = mock.Arguments{nil}
})
_, job := helpers.CreateTemplateJob()
environment := &NomadEnvironment{templateEnvironmentJobHCL, job, runner.NewLocalRunnerStorage()}
runner1 := &runner.RunnerMock{}
runner1.On("ID").Return(tests.DefaultRunnerID)
environment.AddRunner(runner1)
_, ok := environment.Sample(apiMock)
require.True(t, ok)
<-time.After(tests.ShortTimeout) // New Runners are requested asynchronously
}

View File

@ -60,6 +60,33 @@ func (s *CreateOrUpdateTestSuite) TestReturnsErrorIfCreatesOrUpdateEnvironmentRe
s.ErrorIs(err, tests.ErrDefault) s.ErrorIs(err, tests.ErrDefault)
} }
func (s *CreateOrUpdateTestSuite) TestCreateOrUpdatesSetsForcePullFlag() {
s.apiMock.On("RegisterNomadJob", mock.AnythingOfType("*api.Job")).Return("", nil)
s.runnerManagerMock.On("GetEnvironment", mock.AnythingOfType("dto.EnvironmentID")).Return(nil, false)
s.runnerManagerMock.On("SetEnvironment", mock.AnythingOfType("*environment.NomadEnvironment")).Return(true)
s.apiMock.On("MonitorEvaluation", mock.AnythingOfType("string"), mock.Anything).Return(nil)
s.apiMock.On("LoadRunnerIDs", mock.AnythingOfType("string")).Return([]string{}, nil)
call := s.apiMock.On("RegisterRunnerJob", mock.AnythingOfType("*api.Job"))
count := 0
call.Run(func(args mock.Arguments) {
count++
job, ok := args.Get(0).(*nomadApi.Job)
s.True(ok)
// The environment job itself has not the force_pull flag
if count > 1 {
taskGroup := nomad.FindOrCreateDefaultTaskGroup(job)
task := nomad.FindOrCreateDefaultTask(taskGroup)
s.True(task.Config["force_pull"].(bool))
}
call.ReturnArguments = mock.Arguments{nil}
})
_, err := s.manager.CreateOrUpdate(dto.EnvironmentID(tests.DefaultEnvironmentIDAsInteger), s.request)
s.NoError(err)
s.True(count > 1)
}
func TestNewNomadEnvironmentManager(t *testing.T) { func TestNewNomadEnvironmentManager(t *testing.T) {
executorAPIMock := &nomad.ExecutorAPIMock{} executorAPIMock := &nomad.ExecutorAPIMock{}
executorAPIMock.On("LoadEnvironmentJobs").Return([]*nomadApi.Job{}, nil) executorAPIMock.On("LoadEnvironmentJobs").Return([]*nomadApi.Job{}, nil)

View File

@ -130,6 +130,13 @@ func FindOrCreateDefaultTask(taskGroup *nomadApi.TaskGroup) *nomadApi.Task {
return task return task
} }
// SetForcePullFlag sets the flag of a job if the image should be pulled again.
func SetForcePullFlag(job *nomadApi.Job, value bool) {
taskGroup := FindOrCreateDefaultTaskGroup(job)
task := FindOrCreateDefaultTask(taskGroup)
task.Config["force_pull"] = value
}
// IsEnvironmentTemplateID checks if the passed job id belongs to a template job. // IsEnvironmentTemplateID checks if the passed job id belongs to a template job.
func IsEnvironmentTemplateID(jobID string) bool { func IsEnvironmentTemplateID(jobID string) bool {
parts := strings.Split(jobID, "-") parts := strings.Split(jobID, "-")

View File

@ -20,6 +20,7 @@ const (
AnotherEnvironmentIDAsString = "42" AnotherEnvironmentIDAsString = "42"
DefaultUUID = "MY-DEFAULT-RANDOM-UUID" DefaultUUID = "MY-DEFAULT-RANDOM-UUID"
AnotherUUID = "another-uuid-43" AnotherUUID = "another-uuid-43"
DefaultTemplateJobID = "template-" + DefaultEnvironmentIDAsString
DefaultRunnerID = DefaultEnvironmentIDAsString + "-" + DefaultUUID DefaultRunnerID = DefaultEnvironmentIDAsString + "-" + DefaultUUID
AnotherRunnerID = AnotherEnvironmentIDAsString + "-" + AnotherUUID AnotherRunnerID = AnotherEnvironmentIDAsString + "-" + AnotherUUID
DefaultExecutionID = "s0m3-3x3cu710n-1d" DefaultExecutionID = "s0m3-3x3cu710n-1d"

View File

@ -174,8 +174,8 @@ func HTTPPutJSON(url string, body interface{}) (response *http.Response, err err
const templateJobPriority = 100 const templateJobPriority = 100
func CreateTemplateJob() (base, job *nomadApi.Job) { func CreateTemplateJob() (base, job *nomadApi.Job) {
base = nomadApi.NewBatchJob(tests.DefaultRunnerID, tests.DefaultRunnerID, "global", templateJobPriority) base = nomadApi.NewBatchJob(tests.DefaultTemplateJobID, tests.DefaultTemplateJobID, "global", templateJobPriority)
job = nomadApi.NewBatchJob(tests.DefaultRunnerID, tests.DefaultRunnerID, "global", templateJobPriority) job = nomadApi.NewBatchJob(tests.DefaultTemplateJobID, tests.DefaultTemplateJobID, "global", templateJobPriority)
job.Datacenters = []string{"dc1"} job.Datacenters = []string{"dc1"}
configTaskGroup := nomadApi.NewTaskGroup("config", 0) configTaskGroup := nomadApi.NewTaskGroup("config", 0)
configTaskGroup.Meta = make(map[string]string) configTaskGroup.Meta = make(map[string]string)