worker: generalize job types in the server

The worker server was heavily tied to OSBuildJob(Result). Untie it so
that it can deal with different job types in the future.

This necessitates a change in the jobqueue: Dequeue() now returns the
job type, as well as job arguments as json.RawMessage. This is so that
the server can wait on multiple job types with different argument
types.

The weldr, composer, and koji APIs continue to use only "osbuild" jobs.
This commit is contained in:
Lars Karlitski 2020-11-02 21:49:18 +01:00
parent 6b6cd7ca9f
commit 59e73a686a
9 changed files with 108 additions and 85 deletions

View file

@ -145,13 +145,13 @@ func (q *fsJobQueue) Enqueue(jobType string, args interface{}, dependencies []uu
return j.Id, nil
}
func (q *fsJobQueue) Dequeue(ctx context.Context, jobTypes []string, args interface{}) (uuid.UUID, error) {
func (q *fsJobQueue) Dequeue(ctx context.Context, jobTypes []string) (uuid.UUID, string, json.RawMessage, error) {
q.mu.Lock()
defer q.mu.Unlock()
// Return early if the context is already canceled.
if err := ctx.Err(); err != nil {
return uuid.Nil, err
return uuid.Nil, "", nil, err
}
// Filter q.pending by the `jobTypes`. Ignore those job types that this
@ -173,12 +173,12 @@ func (q *fsJobQueue) Dequeue(ctx context.Context, jobTypes []string, args interf
q.mu.Lock()
if err != nil {
return uuid.Nil, err
return uuid.Nil, "", nil, err
}
j, err = q.readJob(id)
if err != nil {
return uuid.Nil, err
return uuid.Nil, "", nil, err
}
if !j.Canceled {
@ -186,19 +186,14 @@ func (q *fsJobQueue) Dequeue(ctx context.Context, jobTypes []string, args interf
}
}
err := json.Unmarshal(j.Args, args)
if err != nil {
return uuid.Nil, fmt.Errorf("error unmarshaling arguments for job '%s': %v", j.Id, err)
}
j.StartedAt = time.Now()
err = q.db.Write(j.Id.String(), j)
err := q.db.Write(j.Id.String(), j)
if err != nil {
return uuid.Nil, fmt.Errorf("error writing job %s: %v", j.Id, err)
return uuid.Nil, "", nil, fmt.Errorf("error writing job %s: %v", j.Id, err)
}
return j.Id, nil
return j.Id, j.Type, j.Args, nil
}
func (q *fsJobQueue) FinishJob(id uuid.UUID, result interface{}) error {

View file

@ -43,9 +43,11 @@ func pushTestJob(t *testing.T, q jobqueue.JobQueue, jobType string, args interfa
}
func finishNextTestJob(t *testing.T, q jobqueue.JobQueue, jobType string, result interface{}) uuid.UUID {
id, err := q.Dequeue(context.Background(), []string{jobType}, &json.RawMessage{})
id, typ, args, err := q.Dequeue(context.Background(), []string{jobType})
require.NoError(t, err)
require.NotEmpty(t, id)
require.Equal(t, jobType, typ)
require.NotNil(t, args)
err = q.FinishJob(id, result)
require.NoError(t, err)
@ -89,16 +91,23 @@ func TestArgs(t *testing.T) {
twoargs := argument{42, "🐙"}
two := pushTestJob(t, q, "octopus", twoargs, nil)
var args argument
id, err := q.Dequeue(context.Background(), []string{"octopus"}, &args)
var parsedArgs argument
id, typ, args, err := q.Dequeue(context.Background(), []string{"octopus"})
require.NoError(t, err)
require.Equal(t, two, id)
require.Equal(t, twoargs, args)
require.Equal(t, "octopus", typ)
err = json.Unmarshal(args, &parsedArgs)
require.NoError(t, err)
require.Equal(t, twoargs, parsedArgs)
id, err = q.Dequeue(context.Background(), []string{"fish"}, &args)
id, typ, args, err = q.Dequeue(context.Background(), []string{"fish"})
require.NoError(t, err)
require.Equal(t, one, id)
require.Equal(t, oneargs, args)
require.Equal(t, "fish", typ)
err = json.Unmarshal(args, &parsedArgs)
require.NoError(t, err)
require.Equal(t, oneargs, parsedArgs)
}
func TestJobTypes(t *testing.T) {
@ -113,9 +122,11 @@ func TestJobTypes(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
id, err := q.Dequeue(ctx, []string{"zebra"}, nil)
id, typ, args, err := q.Dequeue(ctx, []string{"zebra"})
require.Equal(t, err, context.Canceled)
require.Equal(t, uuid.Nil, id)
require.Equal(t, "", typ)
require.Nil(t, args)
}
func TestDependencies(t *testing.T) {
@ -188,9 +199,11 @@ func TestMultipleWorkers(t *testing.T) {
defer close(done)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
id, err := q.Dequeue(ctx, []string{"octopus"}, &json.RawMessage{})
id, typ, args, err := q.Dequeue(ctx, []string{"octopus"})
require.NoError(t, err)
require.NotEmpty(t, id)
require.Equal(t, "octopus", typ)
require.Equal(t, json.RawMessage("null"), args)
}()
// Increase the likelihood that the above goroutine was scheduled and
@ -199,9 +212,11 @@ func TestMultipleWorkers(t *testing.T) {
// This call to Dequeue() should not block on the one in the goroutine.
id := pushTestJob(t, q, "clownfish", nil, nil)
r, err := q.Dequeue(context.Background(), []string{"clownfish"}, &json.RawMessage{})
r, typ, args, err := q.Dequeue(context.Background(), []string{"clownfish"})
require.NoError(t, err)
require.Equal(t, id, r)
require.Equal(t, "clownfish", typ)
require.Equal(t, json.RawMessage("null"), args)
// Now wake up the Dequeue() in the goroutine and wait for it to finish.
_ = pushTestJob(t, q, "octopus", nil, nil)
@ -230,9 +245,11 @@ func TestCancel(t *testing.T) {
// Cancel a running job, which should not dequeue the canceled job from above
id = pushTestJob(t, q, "clownfish", nil, nil)
require.NotEmpty(t, id)
r, err := q.Dequeue(context.Background(), []string{"clownfish"}, &json.RawMessage{})
r, typ, args, err := q.Dequeue(context.Background(), []string{"clownfish"})
require.NoError(t, err)
require.Equal(t, id, r)
require.Equal(t, "clownfish", typ)
require.Equal(t, json.RawMessage("null"), args)
err = q.CancelJob(id)
require.NoError(t, err)
_, _, _, canceled, err = q.JobStatus(id, &testResult{})
@ -244,9 +261,11 @@ func TestCancel(t *testing.T) {
// Cancel a finished job, which is a no-op
id = pushTestJob(t, q, "clownfish", nil, nil)
require.NotEmpty(t, id)
r, err = q.Dequeue(context.Background(), []string{"clownfish"}, &json.RawMessage{})
r, typ, args, err = q.Dequeue(context.Background(), []string{"clownfish"})
require.NoError(t, err)
require.Equal(t, id, r)
require.Equal(t, "clownfish", typ)
require.Equal(t, json.RawMessage("null"), args)
err = q.FinishJob(id, &testResult{})
require.NoError(t, err)
err = q.CancelJob(id)

View file

@ -14,6 +14,7 @@ package jobqueue
import (
"context"
"encoding/json"
"errors"
"time"
@ -38,11 +39,9 @@ type JobQueue interface {
// Waits until a job with a type of any of `jobTypes` is available, or `ctx` is
// canceled.
//
// All jobs in `jobTypes` must take the same type of `args`, corresponding to
// the one that was passed to Enqueue().
//
// Returns the job's id or an error.
Dequeue(ctx context.Context, jobTypes []string, args interface{}) (uuid.UUID, error)
// Returns the job's id, type, and arguments, or an error. Arguments
// can be unmarshaled to the type given in Enqueue().
Dequeue(ctx context.Context, jobTypes []string) (uuid.UUID, string, json.RawMessage, error)
// Mark the job with `id` as finished. `result` must fit the associated
// job type and must be serializable to JSON.

View file

@ -79,7 +79,7 @@ func (q *testJobQueue) Enqueue(jobType string, args interface{}, dependencies []
return j.Id, nil
}
func (q *testJobQueue) Dequeue(ctx context.Context, jobTypes []string, args interface{}) (uuid.UUID, error) {
func (q *testJobQueue) Dequeue(ctx context.Context, jobTypes []string) (uuid.UUID, string, json.RawMessage, error) {
for _, t := range jobTypes {
if len(q.pending[t]) == 0 {
continue
@ -90,16 +90,11 @@ func (q *testJobQueue) Dequeue(ctx context.Context, jobTypes []string, args inte
j := q.jobs[id]
err := json.Unmarshal(j.Args, args)
if err != nil {
return uuid.Nil, err
}
j.StartedAt = time.Now()
return j.Id, nil
return j.Id, j.Type, j.Args, nil
}
return uuid.Nil, errors.New("no job available")
return uuid.Nil, "", nil, errors.New("no job available")
}
func (q *testJobQueue) FinishJob(id uuid.UUID, result interface{}) error {