feat(task): allow tasks to be enabled and disabled.

pull/10616/head
Lyon Hill 2018-08-06 11:54:32 -06:00
parent ec9bf78a78
commit 63b9adf86c
9 changed files with 301 additions and 81 deletions

View File

@ -8,7 +8,7 @@ type Task struct {
Organization ID `json:"organizationId"`
Name string `json:"name"`
Status string `json:"status"`
Owner User `json:"owner"`
Owner User `json:"owner"`
Flux string `json:"flux"`
Every string `json:"every,omitempty"`
Cron string `json:"cron,omitempty"`
@ -61,7 +61,8 @@ type TaskService interface {
// TaskUpdate represents updates to a task
type TaskUpdate struct {
Flux *string `json:"flux"`
Flux *string `json:"flux,omitempty"`
Status *string `json:"status,omitempty"`
}
// TaskFilter represents a set of filters that restrict the returned results

View File

@ -158,6 +158,7 @@ func (s *Store) CreateTask(ctx context.Context, org, user platform.ID, script st
// metadata
stm := backend.StoreTaskMeta{
MaxConcurrency: int32(o.Concurrency),
Status: string(backend.TaskEnabled),
}
stmBytes, err := stm.Marshal()
@ -349,6 +350,52 @@ func (s *Store) FindTaskByID(ctx context.Context, id platform.ID) (*backend.Stor
}, err
}
func (s *Store) EnableTask(ctx context.Context, id platform.ID) error {
paddedID := padID(id)
return s.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(s.bucket).Bucket(taskMetaPath)
stmBytes := b.Get(paddedID)
if stmBytes == nil {
return errors.New("task meta not found")
}
stm := backend.StoreTaskMeta{}
err := stm.Unmarshal(stmBytes)
if err != nil {
return err
}
stm.Status = string(backend.TaskEnabled)
stmBytes, err = stm.Marshal()
if err != nil {
return err
}
return b.Put(paddedID, stmBytes)
})
}
func (s *Store) DisableTask(ctx context.Context, id platform.ID) error {
paddedID := padID(id)
return s.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(s.bucket).Bucket(taskMetaPath)
stmBytes := b.Get(paddedID)
if stmBytes == nil {
return errors.New("task meta not found")
}
stm := backend.StoreTaskMeta{}
err := stm.Unmarshal(stmBytes)
if err != nil {
return err
}
stm.Status = string(backend.TaskDisabled)
stmBytes, err = stm.Marshal()
if err != nil {
return err
}
return b.Put(paddedID, stmBytes)
})
}
func (s *Store) FindTaskMetaByID(ctx context.Context, id platform.ID) (*backend.StoreTaskMeta, error) {
var stmBytes []byte
paddedID := padID(id)

View File

@ -19,32 +19,6 @@ type runReaderWriter struct {
byRunID map[string]*platform.Run
}
type RunStatus int
const (
RunQueued RunStatus = iota
RunStarted
RunSuccess
RunFail
RunCanceled
)
func (r RunStatus) String() string {
switch r {
case RunQueued:
return "queued"
case RunStarted:
return "started"
case RunSuccess:
return "success"
case RunFail:
return "failed"
case RunCanceled:
return "canceled"
}
panic(fmt.Sprintf("unknown RunStatus: %d", r))
}
func NewInMemRunReaderWriter() *runReaderWriter {
return &runReaderWriter{byRunID: map[string]*platform.Run{}, byTaskID: map[string][]*platform.Run{}}
}

View File

@ -55,7 +55,7 @@ func (s *inmem) CreateTask(_ context.Context, org, user platform.ID, script stri
s.mu.Lock()
s.tasks = append(s.tasks, task)
s.runners[id.String()] = StoreTaskMeta{MaxConcurrency: int32(o.Concurrency)}
s.runners[id.String()] = StoreTaskMeta{MaxConcurrency: int32(o.Concurrency), Status: string(TaskEnabled)}
s.mu.Unlock()
return id, nil
@ -145,6 +145,38 @@ func (s *inmem) FindTaskByID(_ context.Context, id platform.ID) (*StoreTask, err
return nil, nil
}
func (s *inmem) EnableTask(ctx context.Context, id platform.ID) error {
s.mu.Lock()
defer s.mu.Unlock()
strID := id.String()
meta, ok := s.runners[strID]
if !ok {
return errors.New("task meta not found")
}
meta.Status = string(TaskEnabled)
s.runners[strID] = meta
return nil
}
func (s *inmem) DisableTask(ctx context.Context, id platform.ID) error {
s.mu.Lock()
defer s.mu.Unlock()
strID := id.String()
meta, ok := s.runners[strID]
if !ok {
return errors.New("task meta not found")
}
meta.Status = string(TaskDisabled)
s.runners[strID] = meta
return nil
}
func (s *inmem) FindTaskMetaByID(ctx context.Context, id platform.ID) (*StoreTaskMeta, error) {
meta, ok := s.runners[id.String()]
if !ok {

View File

@ -36,7 +36,8 @@ type StoreTaskMeta struct {
MaxConcurrency int32 `protobuf:"varint,1,opt,name=max_concurrency,json=maxConcurrency,proto3" json:"max_concurrency,omitempty"`
// last_completed is a unix time stamp of the last completed run.
LastCompleted int64 `protobuf:"varint,2,opt,name=last_completed,json=lastCompleted,proto3" json:"last_completed,omitempty"`
CurrentlyRunning []*StoreTaskMetaRun `protobuf:"bytes,3,rep,name=currently_running,json=currentlyRunning" json:"currently_running,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
CurrentlyRunning []*StoreTaskMetaRun `protobuf:"bytes,4,rep,name=currently_running,json=currentlyRunning" json:"currently_running,omitempty"`
}
func (m *StoreTaskMeta) Reset() { *m = StoreTaskMeta{} }
@ -58,6 +59,13 @@ func (m *StoreTaskMeta) GetLastCompleted() int64 {
return 0
}
func (m *StoreTaskMeta) GetStatus() string {
if m != nil {
return m.Status
}
return ""
}
func (m *StoreTaskMeta) GetCurrentlyRunning() []*StoreTaskMetaRun {
if m != nil {
return m.CurrentlyRunning
@ -127,9 +135,15 @@ func (m *StoreTaskMeta) MarshalTo(dAtA []byte) (int, error) {
i++
i = encodeVarintMeta(dAtA, i, uint64(m.LastCompleted))
}
if len(m.Status) > 0 {
dAtA[i] = 0x1a
i++
i = encodeVarintMeta(dAtA, i, uint64(len(m.Status)))
i += copy(dAtA[i:], m.Status)
}
if len(m.CurrentlyRunning) > 0 {
for _, msg := range m.CurrentlyRunning {
dAtA[i] = 0x1a
dAtA[i] = 0x22
i++
i = encodeVarintMeta(dAtA, i, uint64(msg.Size()))
n, err := msg.MarshalTo(dAtA[i:])
@ -194,6 +208,10 @@ func (m *StoreTaskMeta) Size() (n int) {
if m.LastCompleted != 0 {
n += 1 + sovMeta(uint64(m.LastCompleted))
}
l = len(m.Status)
if l > 0 {
n += 1 + l + sovMeta(uint64(l))
}
if len(m.CurrentlyRunning) > 0 {
for _, e := range m.CurrentlyRunning {
l = e.Size()
@ -300,6 +318,35 @@ func (m *StoreTaskMeta) Unmarshal(dAtA []byte) error {
}
}
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowMeta
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthMeta
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Status = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field CurrentlyRunning", wireType)
}
@ -578,25 +625,26 @@ var (
func init() { proto.RegisterFile("meta.proto", fileDescriptorMeta) }
var fileDescriptorMeta = []byte{
// 311 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x90, 0xbd, 0x4e, 0xf3, 0x30,
0x14, 0x86, 0x3f, 0x7f, 0x51, 0x8b, 0x30, 0xb4, 0x14, 0x4f, 0x81, 0x21, 0x44, 0x15, 0x88, 0x2c,
0xb8, 0x12, 0x48, 0x5c, 0x40, 0xcb, 0xd2, 0x81, 0xc5, 0x30, 0x20, 0x96, 0xc8, 0x71, 0xdc, 0x10,
0x35, 0x3e, 0xae, 0x5c, 0x5b, 0xb4, 0x77, 0xc1, 0x45, 0x31, 0x30, 0x72, 0x05, 0x08, 0x85, 0x1b,
0x41, 0x71, 0xf8, 0x11, 0x4c, 0x6c, 0xef, 0x79, 0xe4, 0xf3, 0xea, 0xf1, 0xc1, 0x58, 0x49, 0xcb,
0xe9, 0xc2, 0x68, 0xab, 0xc9, 0xa1, 0xd0, 0x8a, 0x96, 0x30, 0xab, 0xdc, 0x2a, 0xe7, 0x0d, 0xad,
0xb8, 0x9d, 0x69, 0xa3, 0xa8, 0xe5, 0xcb, 0x39, 0xcd, 0xb8, 0x98, 0x4b, 0xc8, 0xf7, 0x4f, 0x8a,
0xd2, 0xde, 0xb9, 0x8c, 0x0a, 0xad, 0x46, 0x85, 0x2e, 0xf4, 0xc8, 0x2f, 0x67, 0x6e, 0xe6, 0x27,
0x3f, 0xf8, 0xd4, 0x96, 0x0e, 0x1f, 0x11, 0xee, 0x5d, 0x59, 0x6d, 0xe4, 0x35, 0x5f, 0xce, 0x2f,
0xa5, 0xe5, 0xe4, 0x18, 0xef, 0x28, 0xbe, 0x4a, 0x85, 0x06, 0xe1, 0x8c, 0x91, 0x20, 0xd6, 0x21,
0x8a, 0x51, 0xd2, 0x61, 0x7d, 0xc5, 0x57, 0x93, 0x6f, 0x4a, 0x8e, 0x70, 0xbf, 0xe2, 0x4b, 0x9b,
0x0a, 0xad, 0x16, 0x95, 0xb4, 0x32, 0x0f, 0xff, 0xc7, 0x28, 0x09, 0x58, 0xaf, 0xa1, 0x93, 0x4f,
0x48, 0x04, 0xde, 0x6d, 0x57, 0x6c, 0xb5, 0x4e, 0x8d, 0x03, 0x28, 0xa1, 0x08, 0x83, 0x38, 0x48,
0xb6, 0x4e, 0xcf, 0xe9, 0x5f, 0xbe, 0x44, 0x7f, 0xf8, 0x31, 0x07, 0x6c, 0xf0, 0x55, 0xc8, 0xda,
0xbe, 0xe1, 0x0d, 0x1e, 0xfc, 0x7e, 0x45, 0x06, 0x38, 0x00, 0x7d, 0xef, 0xe5, 0x03, 0xd6, 0xc4,
0x86, 0x58, 0xb3, 0xf6, 0x9a, 0x3d, 0xd6, 0x44, 0x12, 0xe3, 0xae, 0x71, 0x90, 0x96, 0x79, 0x18,
0xc4, 0x28, 0xd9, 0x1e, 0x6f, 0xd6, 0x2f, 0x07, 0x1d, 0xe6, 0x60, 0x7a, 0xc1, 0x3a, 0xc6, 0xc1,
0x34, 0x1f, 0xef, 0x3d, 0xd5, 0x11, 0x7a, 0xae, 0x23, 0xf4, 0x5a, 0x47, 0xe8, 0xe1, 0x2d, 0xfa,
0x77, 0xbb, 0xf1, 0xe1, 0x95, 0x75, 0xfd, 0x09, 0xcf, 0xde, 0x03, 0x00, 0x00, 0xff, 0xff, 0x6a,
0xdf, 0xfe, 0xf6, 0xa5, 0x01, 0x00, 0x00,
// 323 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x90, 0x31, 0x4e, 0xf3, 0x30,
0x1c, 0xc5, 0x3f, 0x7f, 0xa1, 0x45, 0x35, 0xb4, 0x94, 0x0c, 0x28, 0x30, 0x84, 0xa8, 0x02, 0x91,
0x05, 0x57, 0x02, 0x89, 0x03, 0xb4, 0x2c, 0x1d, 0x58, 0x0c, 0x03, 0x62, 0x89, 0x1c, 0xc7, 0x0d,
0x51, 0x63, 0xbb, 0x72, 0xfe, 0x16, 0xed, 0x2d, 0x38, 0x16, 0x23, 0x07, 0x40, 0x08, 0x85, 0x8b,
0xa0, 0xb8, 0x05, 0x04, 0x13, 0xdb, 0x7b, 0x3f, 0xd9, 0x4f, 0xef, 0xfd, 0x31, 0x96, 0x02, 0x18,
0x99, 0x1b, 0x0d, 0xda, 0x3f, 0xe2, 0x5a, 0x92, 0x42, 0x4d, 0x4b, 0xbb, 0xc8, 0x58, 0x43, 0x4b,
0x06, 0x53, 0x6d, 0x24, 0x01, 0x56, 0xcd, 0x48, 0xca, 0xf8, 0x4c, 0xa8, 0xec, 0xe0, 0x34, 0x2f,
0xe0, 0xde, 0xa6, 0x84, 0x6b, 0x39, 0xcc, 0x75, 0xae, 0x87, 0xee, 0x73, 0x6a, 0xa7, 0xce, 0x39,
0xe3, 0xd4, 0x2a, 0x74, 0xf0, 0x82, 0x70, 0xf7, 0x1a, 0xb4, 0x11, 0x37, 0xac, 0x9a, 0x5d, 0x09,
0x60, 0xfe, 0x09, 0xde, 0x91, 0x6c, 0x91, 0x70, 0xad, 0xb8, 0x35, 0x46, 0x28, 0xbe, 0x0c, 0x50,
0x84, 0xe2, 0x16, 0xed, 0x49, 0xb6, 0x18, 0x7f, 0x53, 0xff, 0x18, 0xf7, 0x4a, 0x56, 0x41, 0xc2,
0xb5, 0x9c, 0x97, 0x02, 0x44, 0x16, 0xfc, 0x8f, 0x50, 0xec, 0xd1, 0x6e, 0x43, 0xc7, 0x9f, 0xd0,
0xdf, 0xc3, 0xed, 0x0a, 0x18, 0xd8, 0x2a, 0xf0, 0x22, 0x14, 0x77, 0xe8, 0xda, 0xf9, 0x1c, 0xef,
0xae, 0xa2, 0xa0, 0x5c, 0x26, 0xc6, 0x2a, 0x55, 0xa8, 0x3c, 0xd8, 0x88, 0xbc, 0x78, 0xeb, 0xec,
0x82, 0xfc, 0x65, 0x2a, 0xf9, 0xd1, 0x9b, 0x5a, 0x45, 0xfb, 0x5f, 0x81, 0x74, 0x95, 0x37, 0xb8,
0xc5, 0xfd, 0xdf, 0xaf, 0xfc, 0x3e, 0xf6, 0x94, 0x7e, 0x70, 0xa3, 0x3c, 0xda, 0xc8, 0x86, 0x80,
0x59, 0xba, 0xfa, 0x5d, 0xda, 0x48, 0x3f, 0xc2, 0x6d, 0x63, 0x55, 0x52, 0x64, 0xae, 0xf4, 0xf6,
0xa8, 0x53, 0xbf, 0x1e, 0xb6, 0xa8, 0x55, 0x93, 0x4b, 0xda, 0x32, 0x56, 0x4d, 0xb2, 0xd1, 0xfe,
0x53, 0x1d, 0xa2, 0xe7, 0x3a, 0x44, 0x6f, 0x75, 0x88, 0x1e, 0xdf, 0xc3, 0x7f, 0x77, 0x9b, 0xeb,
0x5e, 0x69, 0xdb, 0x9d, 0xf6, 0xfc, 0x23, 0x00, 0x00, 0xff, 0xff, 0x66, 0x80, 0xc1, 0x72, 0xbd,
0x01, 0x00, 0x00,
}

View File

@ -12,8 +12,8 @@ message StoreTaskMeta {
// last_completed is a unix time stamp of the last completed run.
int64 last_completed = 2;
repeated StoreTaskMetaRun currently_running = 3;
string status = 3;
repeated StoreTaskMetaRun currently_running = 4;
}
message StoreTaskMetaRun {

View File

@ -21,6 +21,39 @@ var ErrUserNotFound = errors.New("user not found")
// ErrOrgNotFound is an error for when we can't find an org
var ErrOrgNotFound = errors.New("org not found")
type TaskStatus string
const (
TaskEnabled TaskStatus = "enabled"
TaskDisabled TaskStatus = "disabled"
)
type RunStatus int
const (
RunQueued RunStatus = iota
RunStarted
RunSuccess
RunFail
RunCanceled
)
func (r RunStatus) String() string {
switch r {
case RunQueued:
return "queued"
case RunStarted:
return "started"
case RunSuccess:
return "success"
case RunFail:
return "failed"
case RunCanceled:
return "canceled"
}
panic(fmt.Sprintf("unknown RunStatus: %d", r))
}
// Store is the interface around persisted tasks.
type Store interface {
// CreateTask saves the given task.
@ -37,6 +70,12 @@ type Store interface {
// If no task matches the ID, the returned task is nil.
FindTaskByID(ctx context.Context, id platform.ID) (*StoreTask, error)
// EnableTask updates task status to enabled.
EnableTask(ctx context.Context, id platform.ID) error
// disableTask updates task status to disabled.
DisableTask(ctx context.Context, id platform.ID) error
// FindTaskMetaByID returns the metadata about a task.
FindTaskMetaByID(ctx context.Context, id platform.ID) (*StoreTaskMeta, error)

View File

@ -27,22 +27,24 @@ func NewStoreTest(name string, cf CreateStoreFunc, df DestroyStoreFunc, funcName
"ListTasks",
"FindTask",
"FindMeta",
"EnableDisableTask",
"DeleteTask",
"CreateRun",
"FinishRun",
}
}
availableFuncs := map[string]TestFunc{
"CreateTask": testStoreCreate,
"ModifyTask": testStoreModify,
"ListTasks": testStoreListTasks,
"FindTask": testStoreFindTask,
"FindMeta": testStoreFindMeta,
"DeleteTask": testStoreDelete,
"CreateRun": testStoreCreateRun,
"FinishRun": testStoreFinishRun,
"DeleteOrg": testStoreDeleteOrg,
"DeleteUser": testStoreDeleteUser,
"CreateTask": testStoreCreate,
"ModifyTask": testStoreModify,
"ListTasks": testStoreListTasks,
"FindTask": testStoreFindTask,
"FindMeta": testStoreFindMeta,
"EnableDisableTask": testStoreTaskEnableDisable,
"DeleteTask": testStoreDelete,
"CreateRun": testStoreCreateRun,
"FinishRun": testStoreFinishRun,
"DeleteOrg": testStoreDeleteOrg,
"DeleteUser": testStoreDeleteUser,
}
return func(t *testing.T) {
@ -441,6 +443,61 @@ from(db:"test") |> range(start:-1h)`
}
}
func testStoreTaskEnableDisable(t *testing.T, create CreateStoreFunc, destroy DestroyStoreFunc) {
const script = `option task = {
name: "a task",
cron: "* * * * *",
}
from(db:"test") |> range(start:-1h)`
s := create(t)
defer destroy(t, s)
org := []byte{1}
user := []byte{2}
id, err := s.CreateTask(context.Background(), org, user, script)
if err != nil {
t.Fatal(err)
}
meta, err := s.FindTaskMetaByID(context.Background(), id)
if err != nil {
t.Fatal(err)
}
if meta.Status != string(backend.TaskEnabled) {
t.Fatal("task status not set to enabled on create")
}
if err := s.DisableTask(context.Background(), id); err != nil {
t.Fatal(err)
}
meta, err = s.FindTaskMetaByID(context.Background(), id)
if err != nil {
t.Fatal(err)
}
if meta.Status != string(backend.TaskDisabled) {
t.Fatal("task status not set to enabled on create")
}
if err := s.EnableTask(context.Background(), id); err != nil {
t.Fatal(err)
}
meta, err = s.FindTaskMetaByID(context.Background(), id)
if err != nil {
t.Fatal(err)
}
if meta.Status != string(backend.TaskEnabled) {
t.Fatal("task status not set to enabled on create")
}
}
func testStoreDelete(t *testing.T, create CreateStoreFunc, destroy DestroyStoreFunc) {
const script = `option task = {
name: "a task",

View File

@ -3,6 +3,7 @@ package task
import (
"context"
"errors"
"fmt"
"github.com/influxdata/platform"
"github.com/influxdata/platform/task/backend"
@ -77,29 +78,50 @@ func (p pAdapter) CreateTask(ctx context.Context, t *platform.Task) error {
}
func (p pAdapter) UpdateTask(ctx context.Context, id platform.ID, upd platform.TaskUpdate) (*platform.Task, error) {
if upd.Flux == nil {
return nil, errors.New("cannot update task without a script")
if upd.Flux == nil && upd.Status == nil {
return nil, errors.New("cannot update task without content")
}
opts, err := options.FromScript(*upd.Flux)
if err != nil {
return nil, err
}
if err := p.s.ModifyTask(ctx, id, *upd.Flux); err != nil {
return nil, err
}
return &platform.Task{
task := &platform.Task{
ID: id,
Name: "TODO",
Status: "TODO",
Owner: platform.User{}, // TODO(mr): populate from context?
Flux: *upd.Flux,
Every: opts.Every.String(),
Cron: opts.Cron,
Last: platform.Run{}, // TODO(mr): how to get last run info?
}, nil
Last: platform.Run{}, // TODO(mr): how to get last run info?
}
if upd.Flux != nil {
task.Flux = *upd.Flux
opts, err := options.FromScript(task.Flux)
if err != nil {
return nil, err
}
task.Every = opts.Every.String()
task.Cron = opts.Cron
if err := p.s.ModifyTask(ctx, id, task.Flux); err != nil {
return nil, err
}
}
if upd.Status != nil {
var err error
switch *upd.Status {
case string(backend.TaskEnabled):
err = p.s.EnableTask(ctx, id)
case string(backend.TaskDisabled):
err = p.s.DisableTask(ctx, id)
default:
err = fmt.Errorf("invalid status: %s", *upd.Status)
}
if err != nil {
return nil, err
}
task.Status = *upd.Status
}
return task, nil
}
func (p pAdapter) DeleteTask(ctx context.Context, id platform.ID) error {