2019-04-09 22:52:54 +00:00
|
|
|
package kv
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-04-09 22:52:54 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2020-04-03 17:39:20 +00:00
|
|
|
"github.com/influxdata/influxdb/v2"
|
|
|
|
icontext "github.com/influxdata/influxdb/v2/context"
|
2020-06-17 14:20:05 +00:00
|
|
|
"github.com/influxdata/influxdb/v2/kit/feature"
|
2020-04-03 17:39:20 +00:00
|
|
|
"github.com/influxdata/influxdb/v2/resource"
|
|
|
|
"github.com/influxdata/influxdb/v2/task/options"
|
2019-04-09 22:52:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Task Storage Schema
|
|
|
|
// taskBucket:
|
|
|
|
// <taskID>: task data storage
|
|
|
|
// taskRunBucket:
|
|
|
|
// <taskID>/<runID>: run data storage
|
|
|
|
// <taskID>/manualRuns: list of runs to run manually
|
|
|
|
// <taskID>/latestCompleted: run data for the latest completed run of a task
|
|
|
|
// taskIndexBucket
|
|
|
|
// <orgID>/<taskID>: index for tasks by org
|
|
|
|
|
|
|
|
// We may want to add a <taskName>/<taskID> index to allow us to look up tasks by task name.
|
|
|
|
|
|
|
|
var (
|
|
|
|
taskBucket = []byte("tasksv1")
|
|
|
|
taskRunBucket = []byte("taskRunsv1")
|
|
|
|
taskIndexBucket = []byte("taskIndexsv1")
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ influxdb.TaskService = (*Service)(nil)
|
|
|
|
|
2019-11-13 01:13:56 +00:00
|
|
|
type kvTask struct {
|
|
|
|
ID influxdb.ID `json:"id"`
|
|
|
|
Type string `json:"type,omitempty"`
|
|
|
|
OrganizationID influxdb.ID `json:"orgID"`
|
|
|
|
Organization string `json:"org"`
|
|
|
|
OwnerID influxdb.ID `json:"ownerID"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
Flux string `json:"flux"`
|
|
|
|
Every string `json:"every,omitempty"`
|
|
|
|
Cron string `json:"cron,omitempty"`
|
|
|
|
LastRunStatus string `json:"lastRunStatus,omitempty"`
|
|
|
|
LastRunError string `json:"lastRunError,omitempty"`
|
|
|
|
Offset influxdb.Duration `json:"offset,omitempty"`
|
|
|
|
LatestCompleted time.Time `json:"latestCompleted,omitempty"`
|
|
|
|
LatestScheduled time.Time `json:"latestScheduled,omitempty"`
|
2020-08-25 14:44:37 +00:00
|
|
|
LatestSuccess time.Time `json:"latestSuccess,omitempty"`
|
|
|
|
LatestFailure time.Time `json:"latestFailure,omitempty"`
|
2019-11-13 01:13:56 +00:00
|
|
|
CreatedAt time.Time `json:"createdAt,omitempty"`
|
|
|
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
|
|
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func kvToInfluxTask(k *kvTask) *influxdb.Task {
|
|
|
|
return &influxdb.Task{
|
|
|
|
ID: k.ID,
|
|
|
|
Type: k.Type,
|
|
|
|
OrganizationID: k.OrganizationID,
|
|
|
|
Organization: k.Organization,
|
|
|
|
OwnerID: k.OwnerID,
|
|
|
|
Name: k.Name,
|
|
|
|
Description: k.Description,
|
|
|
|
Status: k.Status,
|
|
|
|
Flux: k.Flux,
|
|
|
|
Every: k.Every,
|
|
|
|
Cron: k.Cron,
|
|
|
|
LastRunStatus: k.LastRunStatus,
|
|
|
|
LastRunError: k.LastRunError,
|
|
|
|
Offset: k.Offset.Duration,
|
|
|
|
LatestCompleted: k.LatestCompleted,
|
|
|
|
LatestScheduled: k.LatestScheduled,
|
2020-08-25 14:44:37 +00:00
|
|
|
LatestSuccess: k.LatestSuccess,
|
|
|
|
LatestFailure: k.LatestFailure,
|
2019-11-13 01:13:56 +00:00
|
|
|
CreatedAt: k.CreatedAt,
|
|
|
|
UpdatedAt: k.UpdatedAt,
|
|
|
|
Metadata: k.Metadata,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
// FindTaskByID returns a single task
|
|
|
|
func (s *Service) FindTaskByID(ctx context.Context, id influxdb.ID) (*influxdb.Task, error) {
|
|
|
|
var t *influxdb.Task
|
|
|
|
err := s.kv.View(ctx, func(tx Tx) error {
|
2020-08-26 16:26:32 +00:00
|
|
|
task, err := s.findTaskByID(ctx, tx, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2020-08-26 16:26:32 +00:00
|
|
|
t = task
|
2019-04-09 22:52:54 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
2019-08-21 21:56:33 +00:00
|
|
|
// findTaskByID is an internal method used to do any action with tasks internally
|
|
|
|
// that do not require authorization.
|
2019-04-09 22:52:54 +00:00
|
|
|
func (s *Service) findTaskByID(ctx context.Context, tx Tx, id influxdb.ID) (*influxdb.Task, error) {
|
|
|
|
taskKey, err := taskKey(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := tx.Bucket(taskBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
v, err := b.Get(taskKey)
|
|
|
|
if IsNotFound(err) {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrTaskNotFound
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-11-13 01:13:56 +00:00
|
|
|
kvTask := &kvTask{}
|
|
|
|
if err := json.Unmarshal(v, kvTask); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-04-19 14:37:59 +00:00
|
|
|
|
2019-11-13 01:13:56 +00:00
|
|
|
t := kvToInfluxTask(kvTask)
|
|
|
|
|
|
|
|
if t.LatestCompleted.IsZero() {
|
2019-04-09 22:52:54 +00:00
|
|
|
t.LatestCompleted = t.CreatedAt
|
|
|
|
}
|
|
|
|
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindTasks returns a list of tasks that match a filter (limit 100) and the total count
|
|
|
|
// of matching tasks.
|
|
|
|
func (s *Service) FindTasks(ctx context.Context, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) {
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
if filter.Organization != "" {
|
|
|
|
org, err := s.orgs.FindOrganization(ctx, influxdb.OrganizationFilter{
|
|
|
|
Name: &filter.Organization,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
filter.OrganizationID = &org.ID
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
var ts []*influxdb.Task
|
|
|
|
err := s.kv.View(ctx, func(tx Tx) error {
|
|
|
|
tasks, _, err := s.findTasks(ctx, tx, filter)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ts = tasks
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ts, len(ts), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) findTasks(ctx context.Context, tx Tx, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) {
|
2019-04-22 19:09:19 +00:00
|
|
|
// complain about limits
|
2019-04-09 22:52:54 +00:00
|
|
|
if filter.Limit < 0 {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, 0, influxdb.ErrPageSizeTooSmall
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
if filter.Limit > influxdb.TaskMaxPageSize {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, 0, influxdb.ErrPageSizeTooLarge
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
if filter.Limit == 0 {
|
|
|
|
filter.Limit = influxdb.TaskDefaultPageSize
|
|
|
|
}
|
|
|
|
|
2019-04-22 19:09:19 +00:00
|
|
|
// if no user or organization is passed, assume contexts auth is the user we are looking for.
|
|
|
|
// it is possible for a internal system to call this with no auth so we shouldnt fail if no auth is found.
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
if filter.OrganizationID == nil && filter.User == nil {
|
2019-04-22 19:09:19 +00:00
|
|
|
userAuth, err := icontext.GetAuthorizer(ctx)
|
|
|
|
if err == nil {
|
|
|
|
userID := userAuth.GetUserID()
|
2020-06-17 14:20:05 +00:00
|
|
|
if userID.Valid() {
|
|
|
|
filter.User = &userID
|
|
|
|
}
|
2019-04-22 19:09:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
// filter by user id.
|
|
|
|
if filter.User != nil {
|
2019-04-19 14:37:59 +00:00
|
|
|
return s.findTasksByUser(ctx, tx, filter)
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
} else if filter.OrganizationID != nil {
|
|
|
|
return s.findTasksByOrg(ctx, tx, *filter.OrganizationID, filter)
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return s.findAllTasks(ctx, tx, filter)
|
|
|
|
}
|
|
|
|
|
|
|
|
// findTasksByUser is a subset of the find tasks function. Used for cleanliness
|
|
|
|
func (s *Service) findTasksByUser(ctx context.Context, tx Tx, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) {
|
2020-06-17 14:20:05 +00:00
|
|
|
var ts []*influxdb.Task
|
|
|
|
|
|
|
|
taskBucket, err := tx.Bucket(taskBucket)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
seek []byte
|
|
|
|
opts []CursorOption
|
|
|
|
)
|
|
|
|
|
|
|
|
if filter.After != nil {
|
|
|
|
seek, err = taskKey(*filter.After)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
opts = append(opts, WithCursorSkipFirstItem())
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := taskBucket.ForwardCursor(seek, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
matchFn := newTaskMatchFn(filter, nil)
|
|
|
|
|
|
|
|
for k, v := c.Next(); k != nil; k, v = c.Next() {
|
|
|
|
kvTask := &kvTask{}
|
|
|
|
if err := json.Unmarshal(v, kvTask); err != nil {
|
|
|
|
return nil, 0, influxdb.ErrInternalTaskServiceError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t := kvToInfluxTask(kvTask)
|
|
|
|
if matchFn == nil || matchFn(t) {
|
|
|
|
ts = append(ts, t)
|
|
|
|
|
|
|
|
if len(ts) >= filter.Limit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := c.Err(); err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ts, len(ts), c.Close()
|
|
|
|
}
|
|
|
|
|
2019-08-16 18:16:46 +00:00
|
|
|
// findTasksByOrg is a subset of the find tasks function. Used for cleanliness
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
func (s *Service) findTasksByOrg(ctx context.Context, tx Tx, orgID influxdb.ID, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) {
|
2019-04-19 14:37:59 +00:00
|
|
|
var err error
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
if !orgID.Valid() {
|
|
|
|
return nil, 0, fmt.Errorf("finding tasks by organization ID: %w", influxdb.ErrInvalidID)
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
|
2019-04-19 14:37:59 +00:00
|
|
|
var ts []*influxdb.Task
|
|
|
|
|
|
|
|
indexBucket, err := tx.Bucket(taskIndexBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, 0, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
|
|
|
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
prefix, err := orgID.Encode()
|
2019-04-19 14:37:59 +00:00
|
|
|
if err != nil {
|
2020-02-03 15:49:51 +00:00
|
|
|
return nil, 0, influxdb.ErrInvalidTaskID
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
2019-12-02 22:16:10 +00:00
|
|
|
|
2020-02-06 19:18:48 +00:00
|
|
|
var (
|
|
|
|
key = prefix
|
|
|
|
opts []CursorOption
|
|
|
|
)
|
2019-04-19 14:37:59 +00:00
|
|
|
// we can filter by orgID
|
|
|
|
if filter.After != nil {
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
key, err = taskOrgKey(orgID, *filter.After)
|
2019-04-19 14:37:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
|
2020-02-06 19:18:48 +00:00
|
|
|
opts = append(opts, WithCursorSkipFirstItem())
|
2020-02-03 15:49:51 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 19:18:48 +00:00
|
|
|
c, err := indexBucket.ForwardCursor(
|
|
|
|
key,
|
|
|
|
append(opts, WithCursorPrefix(prefix))...,
|
|
|
|
)
|
2020-02-03 15:49:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, 0, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-22 19:09:19 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 15:48:55 +00:00
|
|
|
// free cursor resources
|
|
|
|
defer c.Close()
|
|
|
|
|
2019-12-02 22:16:10 +00:00
|
|
|
matchFn := newTaskMatchFn(filter, nil)
|
2019-04-19 14:37:59 +00:00
|
|
|
|
2020-02-03 15:49:51 +00:00
|
|
|
for k, v := c.Next(); k != nil; k, v = c.Next() {
|
2019-04-19 14:37:59 +00:00
|
|
|
id, err := influxdb.IDFromString(string(v))
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, 0, influxdb.ErrInvalidTaskID
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 16:26:32 +00:00
|
|
|
t, err := s.findTaskByID(ctx, tx, *id)
|
2019-04-19 14:37:59 +00:00
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
if err == influxdb.ErrTaskNotFound {
|
2019-06-14 22:21:38 +00:00
|
|
|
// we might have some crufty index's
|
2020-02-07 15:48:55 +00:00
|
|
|
err = nil
|
2019-06-14 22:21:38 +00:00
|
|
|
continue
|
|
|
|
}
|
2019-04-19 14:37:59 +00:00
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the new task doesn't belong to the org we have looped outside the org filter
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
if t.OrganizationID != orgID {
|
2019-04-19 14:37:59 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2019-12-02 22:16:10 +00:00
|
|
|
if matchFn == nil || matchFn(t) {
|
|
|
|
ts = append(ts, t)
|
|
|
|
// Check if we are over running the limit
|
|
|
|
if len(ts) >= filter.Limit {
|
|
|
|
break
|
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-08-16 18:16:46 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 15:48:55 +00:00
|
|
|
return ts, len(ts), c.Err()
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
|
2019-10-31 20:01:41 +00:00
|
|
|
type taskMatchFn func(*influxdb.Task) bool
|
|
|
|
|
|
|
|
// newTaskMatchFn returns a function for validating
|
|
|
|
// a task matches the filter. Will return nil if
|
|
|
|
// the filter should match all tasks.
|
|
|
|
func newTaskMatchFn(f influxdb.TaskFilter, org *influxdb.Organization) func(t *influxdb.Task) bool {
|
|
|
|
var fn taskMatchFn
|
|
|
|
|
|
|
|
if org != nil {
|
|
|
|
expected := org.ID
|
|
|
|
prevFn := fn
|
|
|
|
fn = func(t *influxdb.Task) bool {
|
|
|
|
res := prevFn == nil || prevFn(t)
|
|
|
|
return res && expected == t.OrganizationID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Type != nil {
|
|
|
|
expected := *f.Type
|
|
|
|
prevFn := fn
|
|
|
|
fn = func(t *influxdb.Task) bool {
|
|
|
|
res := prevFn == nil || prevFn(t)
|
|
|
|
return res &&
|
|
|
|
((expected == influxdb.TaskSystemType && (t.Type == influxdb.TaskSystemType || t.Type == "")) || expected == t.Type)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Name != nil {
|
|
|
|
expected := *f.Name
|
|
|
|
prevFn := fn
|
|
|
|
fn = func(t *influxdb.Task) bool {
|
|
|
|
res := prevFn == nil || prevFn(t)
|
|
|
|
return res && (expected == t.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-02 22:16:10 +00:00
|
|
|
if f.Status != nil {
|
|
|
|
prevFn := fn
|
|
|
|
fn = func(t *influxdb.Task) bool {
|
|
|
|
res := prevFn == nil || prevFn(t)
|
|
|
|
return res && (t.Status == *f.Status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-17 14:20:05 +00:00
|
|
|
if f.User != nil {
|
|
|
|
prevFn := fn
|
|
|
|
fn = func(t *influxdb.Task) bool {
|
|
|
|
res := prevFn == nil || prevFn(t)
|
|
|
|
return res && t.OwnerID == *f.User
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-31 20:01:41 +00:00
|
|
|
return fn
|
|
|
|
}
|
|
|
|
|
2019-04-19 14:37:59 +00:00
|
|
|
// findAllTasks is a subset of the find tasks function. Used for cleanliness.
|
|
|
|
// This function should only be executed internally because it doesn't force organization or user filtering.
|
|
|
|
// Enforcing filters should be done in a validation layer.
|
|
|
|
func (s *Service) findAllTasks(ctx context.Context, tx Tx, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) {
|
|
|
|
var ts []*influxdb.Task
|
|
|
|
taskBucket, err := tx.Bucket(taskBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, 0, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 19:18:48 +00:00
|
|
|
var (
|
|
|
|
seek []byte
|
|
|
|
opts []CursorOption
|
|
|
|
)
|
|
|
|
|
2019-04-19 14:37:59 +00:00
|
|
|
if filter.After != nil {
|
2020-02-03 15:49:51 +00:00
|
|
|
seek, err = taskKey(*filter.After)
|
2019-04-19 14:37:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
2020-02-06 17:44:36 +00:00
|
|
|
|
2020-02-06 19:18:48 +00:00
|
|
|
opts = append(opts, WithCursorSkipFirstItem())
|
2020-02-03 15:49:51 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 19:18:48 +00:00
|
|
|
c, err := taskBucket.ForwardCursor(seek, opts...)
|
2020-02-03 15:49:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, 0, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 15:48:55 +00:00
|
|
|
// free cursor resources
|
|
|
|
defer c.Close()
|
|
|
|
|
2019-10-31 20:01:41 +00:00
|
|
|
matchFn := newTaskMatchFn(filter, nil)
|
2019-04-22 19:09:19 +00:00
|
|
|
|
2020-02-03 15:49:51 +00:00
|
|
|
for k, v := c.Next(); k != nil; k, v = c.Next() {
|
2019-11-13 01:13:56 +00:00
|
|
|
kvTask := &kvTask{}
|
|
|
|
if err := json.Unmarshal(v, kvTask); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, 0, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
2019-10-11 14:53:38 +00:00
|
|
|
|
2019-11-13 01:13:56 +00:00
|
|
|
t := kvToInfluxTask(kvTask)
|
|
|
|
|
2019-10-31 20:01:41 +00:00
|
|
|
if matchFn == nil || matchFn(t) {
|
|
|
|
ts = append(ts, t)
|
2019-04-19 14:37:59 +00:00
|
|
|
|
2019-10-31 20:01:41 +00:00
|
|
|
if len(ts) >= filter.Limit {
|
|
|
|
break
|
|
|
|
}
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
2019-08-16 18:16:46 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 15:48:55 +00:00
|
|
|
if err := c.Err(); err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
2019-04-19 14:37:59 +00:00
|
|
|
return ts, len(ts), err
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CreateTask creates a new task.
|
|
|
|
// The owner of the task is inferred from the authorizer associated with ctx.
|
|
|
|
func (s *Service) CreateTask(ctx context.Context, tc influxdb.TaskCreate) (*influxdb.Task, error) {
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
var orgFilter influxdb.OrganizationFilter
|
|
|
|
|
|
|
|
if tc.Organization != "" {
|
|
|
|
orgFilter.Name = &tc.Organization
|
|
|
|
} else if tc.OrganizationID.Valid() {
|
|
|
|
orgFilter.ID = &tc.OrganizationID
|
|
|
|
|
|
|
|
} else {
|
|
|
|
return nil, errors.New("organization required")
|
|
|
|
}
|
|
|
|
|
|
|
|
org, err := s.orgs.FindOrganization(ctx, orgFilter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
var t *influxdb.Task
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
err = s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
task, err := s.createTask(ctx, tx, org, tc)
|
2019-04-09 22:52:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t = task
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
return t, err
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
func (s *Service) createTask(ctx context.Context, tx Tx, org *influxdb.Organization, tc influxdb.TaskCreate) (*influxdb.Task, error) {
|
2019-08-20 14:42:40 +00:00
|
|
|
// TODO: Uncomment this once the checks/notifications no longer create tasks in kv
|
|
|
|
// confirm the owner is a real user.
|
|
|
|
// if _, err = s.findUserByID(ctx, tx, tc.OwnerID); err != nil {
|
|
|
|
// return nil, influxdb.ErrInvalidOwnerID
|
|
|
|
// }
|
|
|
|
|
2020-06-23 16:22:53 +00:00
|
|
|
opts, err := ExtractTaskOptions(ctx, s.FluxLanguageService, tc.Flux)
|
2019-04-09 22:52:54 +00:00
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrTaskOptionParse(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if tc.Status == "" {
|
2020-03-06 22:19:32 +00:00
|
|
|
tc.Status = string(influxdb.TaskActive)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
2019-12-06 03:55:26 +00:00
|
|
|
createdAt := s.clock.Now().Truncate(time.Second).UTC()
|
2019-04-09 22:52:54 +00:00
|
|
|
task := &influxdb.Task{
|
|
|
|
ID: s.IDGenerator.ID(),
|
2019-08-06 16:27:52 +00:00
|
|
|
Type: tc.Type,
|
2019-04-09 22:52:54 +00:00
|
|
|
OrganizationID: org.ID,
|
|
|
|
Organization: org.Name,
|
2019-08-16 00:31:52 +00:00
|
|
|
OwnerID: tc.OwnerID,
|
2019-10-11 14:53:38 +00:00
|
|
|
Metadata: tc.Metadata,
|
2020-06-23 16:22:53 +00:00
|
|
|
Name: opts.Name,
|
2019-05-08 18:16:20 +00:00
|
|
|
Description: tc.Description,
|
2019-04-09 22:52:54 +00:00
|
|
|
Status: tc.Status,
|
|
|
|
Flux: tc.Flux,
|
2020-06-23 16:22:53 +00:00
|
|
|
Every: opts.Every.String(),
|
|
|
|
Cron: opts.Cron,
|
2019-05-01 20:54:27 +00:00
|
|
|
CreatedAt: createdAt,
|
|
|
|
LatestCompleted: createdAt,
|
2019-12-06 03:55:26 +00:00
|
|
|
LatestScheduled: createdAt,
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-11-13 01:13:56 +00:00
|
|
|
|
2020-06-23 16:22:53 +00:00
|
|
|
if opts.Offset != nil {
|
|
|
|
off, err := time.ParseDuration(opts.Offset.String())
|
2019-11-13 01:13:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrTaskTimeParse(err)
|
|
|
|
}
|
|
|
|
task.Offset = off
|
|
|
|
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
|
|
|
|
taskBucket, err := tx.Bucket(taskBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
indexBucket, err := tx.Bucket(taskIndexBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
taskBytes, err := json.Marshal(task)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
taskKey, err := taskKey(task.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
orgKey, err := taskOrgKey(task.OrganizationID, task.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// write the task
|
|
|
|
err = taskBucket.Put(taskKey, taskBytes)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// write the org index
|
|
|
|
err = indexBucket.Put(orgKey, taskKey)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-08-07 22:34:07 +00:00
|
|
|
|
2020-01-13 14:22:52 +00:00
|
|
|
uid, _ := icontext.GetUserID(ctx)
|
|
|
|
if err := s.audit.Log(resource.Change{
|
|
|
|
Type: resource.Create,
|
|
|
|
ResourceID: task.ID,
|
|
|
|
ResourceType: influxdb.TasksResourceType,
|
|
|
|
OrganizationID: task.OrganizationID,
|
|
|
|
UserID: uid,
|
|
|
|
ResourceBody: taskBytes,
|
|
|
|
Time: time.Now(),
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
return task, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateTask updates a single task with changeset.
|
|
|
|
func (s *Service) UpdateTask(ctx context.Context, id influxdb.ID, upd influxdb.TaskUpdate) (*influxdb.Task, error) {
|
|
|
|
var t *influxdb.Task
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
task, err := s.updateTask(ctx, tx, id, upd)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t = task
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) updateTask(ctx context.Context, tx Tx, id influxdb.ID, upd influxdb.TaskUpdate) (*influxdb.Task, error) {
|
|
|
|
// retrieve the task
|
|
|
|
task, err := s.findTaskByID(ctx, tx, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-12-06 03:55:26 +00:00
|
|
|
updatedAt := s.clock.Now().UTC()
|
2019-09-06 15:45:10 +00:00
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
// update the flux script
|
|
|
|
if !upd.Options.IsZero() || upd.Flux != nil {
|
2020-08-20 15:57:23 +00:00
|
|
|
if err = upd.UpdateFlux(ctx, s.FluxLanguageService, task.Flux); err != nil {
|
2019-04-09 22:52:54 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
task.Flux = *upd.Flux
|
|
|
|
|
2020-06-23 16:22:53 +00:00
|
|
|
opts, err := ExtractTaskOptions(ctx, s.FluxLanguageService, *upd.Flux)
|
2019-04-09 22:52:54 +00:00
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrTaskOptionParse(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2020-06-23 16:22:53 +00:00
|
|
|
task.Name = opts.Name
|
|
|
|
task.Every = opts.Every.String()
|
|
|
|
task.Cron = opts.Cron
|
2019-11-13 01:13:56 +00:00
|
|
|
|
|
|
|
var off time.Duration
|
2020-06-23 16:22:53 +00:00
|
|
|
if opts.Offset != nil {
|
|
|
|
off, err = time.ParseDuration(opts.Offset.String())
|
2019-11-13 01:13:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrTaskTimeParse(err)
|
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-11-13 01:13:56 +00:00
|
|
|
task.Offset = off
|
2019-09-06 15:45:10 +00:00
|
|
|
task.UpdatedAt = updatedAt
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
2019-05-08 18:16:20 +00:00
|
|
|
if upd.Description != nil {
|
|
|
|
task.Description = *upd.Description
|
2019-09-06 15:45:10 +00:00
|
|
|
task.UpdatedAt = updatedAt
|
2019-05-08 18:16:20 +00:00
|
|
|
}
|
|
|
|
|
2019-12-06 03:55:26 +00:00
|
|
|
if upd.Status != nil && task.Status != *upd.Status {
|
2019-04-09 22:52:54 +00:00
|
|
|
task.Status = *upd.Status
|
2019-09-06 15:45:10 +00:00
|
|
|
task.UpdatedAt = updatedAt
|
|
|
|
|
2019-12-06 03:55:26 +00:00
|
|
|
// task is transitioning from inactive to active, ensure scheduled and completed are updated
|
|
|
|
if task.Status == influxdb.TaskStatusActive {
|
|
|
|
updatedAtTrunc := updatedAt.Truncate(time.Second).UTC()
|
|
|
|
task.LatestCompleted = updatedAtTrunc
|
|
|
|
task.LatestScheduled = updatedAtTrunc
|
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 14:53:38 +00:00
|
|
|
if upd.Metadata != nil {
|
|
|
|
task.Metadata = upd.Metadata
|
|
|
|
task.UpdatedAt = updatedAt
|
|
|
|
}
|
|
|
|
|
2019-04-19 14:37:59 +00:00
|
|
|
if upd.LatestCompleted != nil {
|
2019-08-15 21:28:35 +00:00
|
|
|
// make sure we only update latest completed one way
|
2019-11-13 01:13:56 +00:00
|
|
|
tlc := task.LatestCompleted
|
|
|
|
ulc := *upd.LatestCompleted
|
2019-08-15 21:28:35 +00:00
|
|
|
|
|
|
|
if !ulc.IsZero() && ulc.After(tlc) {
|
|
|
|
task.LatestCompleted = *upd.LatestCompleted
|
|
|
|
}
|
2019-04-19 14:37:59 +00:00
|
|
|
}
|
|
|
|
|
2019-11-06 22:10:52 +00:00
|
|
|
if upd.LatestScheduled != nil {
|
|
|
|
// make sure we only update latest scheduled one way
|
|
|
|
if upd.LatestScheduled.After(task.LatestScheduled) {
|
|
|
|
task.LatestScheduled = *upd.LatestScheduled
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-25 14:44:37 +00:00
|
|
|
if upd.LatestSuccess != nil {
|
|
|
|
// make sure we only update latest success one way
|
|
|
|
tlc := task.LatestSuccess
|
|
|
|
ulc := *upd.LatestSuccess
|
|
|
|
|
|
|
|
if !ulc.IsZero() && ulc.After(tlc) {
|
|
|
|
task.LatestSuccess = *upd.LatestSuccess
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if upd.LatestFailure != nil {
|
|
|
|
// make sure we only update latest failure one way
|
|
|
|
tlc := task.LatestFailure
|
|
|
|
ulc := *upd.LatestFailure
|
|
|
|
|
|
|
|
if !ulc.IsZero() && ulc.After(tlc) {
|
|
|
|
task.LatestFailure = *upd.LatestFailure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:24:41 +00:00
|
|
|
if upd.LastRunStatus != nil {
|
|
|
|
task.LastRunStatus = *upd.LastRunStatus
|
|
|
|
if *upd.LastRunStatus == "failed" && upd.LastRunError != nil {
|
|
|
|
task.LastRunError = *upd.LastRunError
|
|
|
|
} else {
|
|
|
|
task.LastRunError = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
// save the updated task
|
|
|
|
bucket, err := tx.Bucket(taskBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
key, err := taskKey(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
taskBytes, err := json.Marshal(task)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
2020-01-13 14:22:52 +00:00
|
|
|
err = bucket.Put(key, taskBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
uid, _ := icontext.GetUserID(ctx)
|
|
|
|
if err := s.audit.Log(resource.Change{
|
|
|
|
Type: resource.Update,
|
|
|
|
ResourceID: task.ID,
|
|
|
|
ResourceType: influxdb.TasksResourceType,
|
|
|
|
OrganizationID: task.OrganizationID,
|
|
|
|
UserID: uid,
|
|
|
|
ResourceBody: taskBytes,
|
|
|
|
Time: time.Now(),
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return task, nil
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteTask removes a task by ID and purges all associated data and scheduled runs.
|
|
|
|
func (s *Service) DeleteTask(ctx context.Context, id influxdb.ID) error {
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
err := s.deleteTask(ctx, tx, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) deleteTask(ctx context.Context, tx Tx, id influxdb.ID) error {
|
|
|
|
taskBucket, err := tx.Bucket(taskBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runBucket, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
indexBucket, err := tx.Bucket(taskIndexBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// retrieve the task
|
|
|
|
task, err := s.findTaskByID(ctx, tx, id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove the orgs index
|
|
|
|
orgKey, err := taskOrgKey(task.OrganizationID, task.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := indexBucket.Delete(orgKey); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// remove latest completed
|
|
|
|
lastCompletedKey, err := taskLatestCompletedKey(task.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := runBucket.Delete(lastCompletedKey); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// remove the runs
|
|
|
|
runs, _, err := s.findRuns(ctx, tx, influxdb.RunFilter{Task: task.ID})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, run := range runs {
|
|
|
|
key, err := taskRunKey(task.ID, run.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := runBucket.Delete(key); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// remove the task
|
|
|
|
key, err := taskKey(task.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := taskBucket.Delete(key); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-06-11 16:28:09 +00:00
|
|
|
|
2020-01-13 14:22:52 +00:00
|
|
|
uid, _ := icontext.GetUserID(ctx)
|
|
|
|
return s.audit.Log(resource.Change{
|
|
|
|
Type: resource.Delete,
|
|
|
|
ResourceID: task.ID,
|
|
|
|
ResourceType: influxdb.TasksResourceType,
|
|
|
|
OrganizationID: task.OrganizationID,
|
|
|
|
UserID: uid,
|
|
|
|
Time: time.Now(),
|
|
|
|
})
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// FindLogs returns logs for a run.
|
|
|
|
func (s *Service) FindLogs(ctx context.Context, filter influxdb.LogFilter) ([]*influxdb.Log, int, error) {
|
|
|
|
var logs []*influxdb.Log
|
|
|
|
err := s.kv.View(ctx, func(tx Tx) error {
|
|
|
|
ls, _, err := s.findLogs(ctx, tx, filter)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logs = ls
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return logs, len(logs), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) findLogs(ctx context.Context, tx Tx, filter influxdb.LogFilter) ([]*influxdb.Log, int, error) {
|
|
|
|
if filter.Run != nil {
|
|
|
|
r, err := s.findRunByID(ctx, tx, filter.Task, *filter.Run)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
rtn := make([]*influxdb.Log, len(r.Log))
|
2019-04-19 14:37:59 +00:00
|
|
|
for i := 0; i < len(r.Log); i++ {
|
|
|
|
rtn[i] = &r.Log[i]
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
return rtn, len(rtn), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
runs, _, err := s.findRuns(ctx, tx, influxdb.RunFilter{Task: filter.Task})
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
var logs []*influxdb.Log
|
|
|
|
for _, run := range runs {
|
2019-04-17 14:41:05 +00:00
|
|
|
for i := 0; i < len(run.Log); i++ {
|
|
|
|
logs = append(logs, &run.Log[i])
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return logs, len(logs), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindRuns returns a list of runs that match a filter and the total count of returned runs.
|
|
|
|
func (s *Service) FindRuns(ctx context.Context, filter influxdb.RunFilter) ([]*influxdb.Run, int, error) {
|
|
|
|
var runs []*influxdb.Run
|
|
|
|
err := s.kv.View(ctx, func(tx Tx) error {
|
|
|
|
rs, _, err := s.findRuns(ctx, tx, filter)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
runs = rs
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return runs, len(runs), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) findRuns(ctx context.Context, tx Tx, filter influxdb.RunFilter) ([]*influxdb.Run, int, error) {
|
2019-06-06 22:55:58 +00:00
|
|
|
if filter.Limit == 0 {
|
|
|
|
filter.Limit = influxdb.TaskDefaultPageSize
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-06-06 22:55:58 +00:00
|
|
|
|
|
|
|
if filter.Limit < 0 || filter.Limit > influxdb.TaskMaxPageSize {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, 0, influxdb.ErrOutOfBoundsLimit
|
2019-06-06 22:55:58 +00:00
|
|
|
}
|
2020-11-10 20:30:34 +00:00
|
|
|
parsedFilterAfterTime := time.Time{}
|
|
|
|
parsedFilterBeforeTime := time.Now().UTC()
|
|
|
|
var err error
|
2020-11-11 18:21:51 +00:00
|
|
|
if len(filter.AfterTime) > 0 {
|
2020-11-10 20:30:34 +00:00
|
|
|
parsedFilterAfterTime, err = time.Parse(time.RFC3339, filter.AfterTime)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if filter.BeforeTime != "" {
|
|
|
|
parsedFilterBeforeTime, err = time.Parse(time.RFC3339, filter.BeforeTime)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
}
|
2019-06-06 22:55:58 +00:00
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
var runs []*influxdb.Run
|
|
|
|
// manual runs
|
|
|
|
manualRuns, err := s.manualRuns(ctx, tx, filter.Task)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
for _, run := range manualRuns {
|
2020-11-10 20:30:34 +00:00
|
|
|
if run.ScheduledFor.After(parsedFilterAfterTime) && run.ScheduledFor.Before(parsedFilterBeforeTime) {
|
|
|
|
runs = append(runs, run)
|
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
if len(runs) >= filter.Limit {
|
|
|
|
return runs, len(runs), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// append currently running
|
|
|
|
currentlyRunning, err := s.currentlyRunning(ctx, tx, filter.Task)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
for _, run := range currentlyRunning {
|
2020-11-10 20:30:34 +00:00
|
|
|
if run.ScheduledFor.After(parsedFilterAfterTime) && run.ScheduledFor.Before(parsedFilterBeforeTime) {
|
|
|
|
runs = append(runs, run)
|
|
|
|
}
|
2019-04-09 22:52:54 +00:00
|
|
|
if len(runs) >= filter.Limit {
|
|
|
|
return runs, len(runs), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return runs, len(runs), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindRunByID returns a single run.
|
|
|
|
func (s *Service) FindRunByID(ctx context.Context, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
var run *influxdb.Run
|
|
|
|
err := s.kv.View(ctx, func(tx Tx) error {
|
|
|
|
r, err := s.findRunByID(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
run = r
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return run, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) findRunByID(ctx context.Context, tx Tx, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
bucket, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key, err := taskRunKey(taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
runBytes, err := bucket.Get(key)
|
|
|
|
if err != nil {
|
2019-04-17 14:41:05 +00:00
|
|
|
if IsNotFound(err) {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrRunNotFound
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
run := &influxdb.Run{}
|
|
|
|
err = json.Unmarshal(runBytes, run)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return run, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CancelRun cancels a currently running run.
|
|
|
|
func (s *Service) CancelRun(ctx context.Context, taskID, runID influxdb.ID) error {
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
err := s.cancelRun(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) cancelRun(ctx context.Context, tx Tx, taskID, runID influxdb.ID) error {
|
|
|
|
// get the run
|
|
|
|
run, err := s.findRunByID(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// set status to canceled
|
|
|
|
run.Status = "canceled"
|
|
|
|
|
|
|
|
// save
|
2019-12-17 04:08:24 +00:00
|
|
|
bucket, err := tx.Bucket(taskRunBucket)
|
2019-04-09 22:52:54 +00:00
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runBytes, err := json.Marshal(run)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runKey, err := taskRunKey(taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := bucket.Put(runKey, runBytes); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RetryRun creates and returns a new run (which is a retry of another run).
|
|
|
|
func (s *Service) RetryRun(ctx context.Context, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
var r *influxdb.Run
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
run, err := s.retryRun(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r = run
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) retryRun(ctx context.Context, tx Tx, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
// find the run
|
|
|
|
r, err := s.findRunByID(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.ID = s.IDGenerator.ID()
|
2020-03-06 22:19:32 +00:00
|
|
|
r.Status = influxdb.RunScheduled.String()
|
2019-10-18 00:23:45 +00:00
|
|
|
r.StartedAt = time.Time{}
|
|
|
|
r.FinishedAt = time.Time{}
|
|
|
|
r.RequestedAt = time.Time{}
|
2019-04-09 22:52:54 +00:00
|
|
|
|
|
|
|
// add a clean copy of the run to the manual runs
|
|
|
|
bucket, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key, err := taskManualRunKey(taskID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
runs := []*influxdb.Run{}
|
|
|
|
runsBytes, err := bucket.Get(key)
|
|
|
|
if err != nil {
|
|
|
|
if err != ErrKeyNotFound {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrRunNotFound
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if runsBytes != nil {
|
|
|
|
if err := json.Unmarshal(runsBytes, &runs); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
runs = append(runs, r)
|
|
|
|
|
|
|
|
// save manual runs
|
|
|
|
runsBytes, err = json.Marshal(runs)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := bucket.Put(key, runsBytes); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForceRun forces a run to occur with unix timestamp scheduledFor, to be executed as soon as possible.
|
|
|
|
// The value of scheduledFor may or may not align with the task's schedule.
|
|
|
|
func (s *Service) ForceRun(ctx context.Context, taskID influxdb.ID, scheduledFor int64) (*influxdb.Run, error) {
|
|
|
|
var r *influxdb.Run
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
run, err := s.forceRun(ctx, tx, taskID, scheduledFor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r = run
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) forceRun(ctx context.Context, tx Tx, taskID influxdb.ID, scheduledFor int64) (*influxdb.Run, error) {
|
|
|
|
// create a run
|
|
|
|
t := time.Unix(scheduledFor, 0).UTC()
|
|
|
|
r := &influxdb.Run{
|
|
|
|
ID: s.IDGenerator.ID(),
|
|
|
|
TaskID: taskID,
|
2020-03-06 22:19:32 +00:00
|
|
|
Status: influxdb.RunScheduled.String(),
|
2019-10-18 00:23:45 +00:00
|
|
|
RequestedAt: time.Now().UTC(),
|
|
|
|
ScheduledFor: t,
|
2019-04-17 14:41:05 +00:00
|
|
|
Log: []influxdb.Log{},
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add a clean copy of the run to the manual runs
|
|
|
|
bucket, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runs, err := s.manualRuns(ctx, tx, taskID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// check to see if this run is already queued
|
|
|
|
for _, run := range runs {
|
|
|
|
if run.ScheduledFor == r.ScheduledFor {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrTaskRunAlreadyQueued
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
runs = append(runs, r)
|
|
|
|
|
|
|
|
// save manual runs
|
|
|
|
runsBytes, err := json.Marshal(runs)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key, err := taskManualRunKey(taskID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := bucket.Put(key, runsBytes); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
2019-07-08 14:13:29 +00:00
|
|
|
// CreateRun creates a run with a scheduledFor time as now.
|
2019-12-11 22:50:32 +00:00
|
|
|
func (s *Service) CreateRun(ctx context.Context, taskID influxdb.ID, scheduledFor time.Time, runAt time.Time) (*influxdb.Run, error) {
|
2019-07-08 14:13:29 +00:00
|
|
|
var r *influxdb.Run
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
2019-12-11 22:50:32 +00:00
|
|
|
run, err := s.createRun(ctx, tx, taskID, scheduledFor, runAt)
|
2019-07-08 14:13:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r = run
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return r, err
|
|
|
|
}
|
2019-12-11 22:50:32 +00:00
|
|
|
func (s *Service) createRun(ctx context.Context, tx Tx, taskID influxdb.ID, scheduledFor time.Time, runAt time.Time) (*influxdb.Run, error) {
|
2019-07-08 14:13:29 +00:00
|
|
|
id := s.IDGenerator.ID()
|
2020-01-02 19:41:21 +00:00
|
|
|
t := time.Unix(scheduledFor.Unix(), 0).UTC()
|
2019-07-08 14:13:29 +00:00
|
|
|
|
|
|
|
run := influxdb.Run{
|
|
|
|
ID: id,
|
|
|
|
TaskID: taskID,
|
2020-01-02 19:41:21 +00:00
|
|
|
ScheduledFor: t,
|
2019-12-11 22:50:32 +00:00
|
|
|
RunAt: runAt,
|
2020-03-06 22:19:32 +00:00
|
|
|
Status: influxdb.RunScheduled.String(),
|
2019-07-08 14:13:29 +00:00
|
|
|
Log: []influxdb.Log{},
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
runBytes, err := json.Marshal(run)
|
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
runKey, err := taskRunKey(taskID, run.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := b.Put(runKey, runBytes); err != nil {
|
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &run, nil
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
func (s *Service) CurrentlyRunning(ctx context.Context, taskID influxdb.ID) ([]*influxdb.Run, error) {
|
|
|
|
var runs []*influxdb.Run
|
|
|
|
err := s.kv.View(ctx, func(tx Tx) error {
|
|
|
|
rs, err := s.currentlyRunning(ctx, tx, taskID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
runs = rs
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return runs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) currentlyRunning(ctx context.Context, tx Tx, taskID influxdb.ID) ([]*influxdb.Run, error) {
|
|
|
|
bucket, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-31 20:01:41 +00:00
|
|
|
c, err := bucket.Cursor(WithCursorHintPrefix(taskID.String()))
|
2019-04-09 22:52:54 +00:00
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
var runs []*influxdb.Run
|
|
|
|
|
|
|
|
taskKey, err := taskKey(taskID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
k, v := c.Seek(taskKey)
|
|
|
|
for {
|
|
|
|
if k == nil || !strings.HasPrefix(string(k), string(taskKey)) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if strings.HasSuffix(string(k), "manualRuns") || strings.HasSuffix(string(k), "latestCompleted") {
|
|
|
|
k, v = c.Next()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
r := &influxdb.Run{}
|
|
|
|
if err := json.Unmarshal(v, r); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// if the run no longer belongs to the task we are done
|
|
|
|
if r.TaskID != taskID {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
runs = append(runs, r)
|
|
|
|
k, v = c.Next()
|
|
|
|
}
|
|
|
|
return runs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) ManualRuns(ctx context.Context, taskID influxdb.ID) ([]*influxdb.Run, error) {
|
|
|
|
var runs []*influxdb.Run
|
|
|
|
err := s.kv.View(ctx, func(tx Tx) error {
|
|
|
|
rs, err := s.manualRuns(ctx, tx, taskID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
runs = rs
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return runs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) manualRuns(ctx context.Context, tx Tx, taskID influxdb.ID) ([]*influxdb.Run, error) {
|
|
|
|
b, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
key, err := taskManualRunKey(taskID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
runs := []*influxdb.Run{}
|
|
|
|
val, err := b.Get(key)
|
|
|
|
if err != nil {
|
|
|
|
if err == ErrKeyNotFound {
|
|
|
|
return runs, nil
|
|
|
|
}
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
if err := json.Unmarshal(val, &runs); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
return runs, nil
|
|
|
|
}
|
|
|
|
|
2019-07-08 14:13:29 +00:00
|
|
|
func (s *Service) StartManualRun(ctx context.Context, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
var r *influxdb.Run
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
run, err := s.startManualRun(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r = run
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) startManualRun(ctx context.Context, tx Tx, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
|
|
|
|
mRuns, err := s.manualRuns(ctx, tx, taskID)
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrRunNotFound
|
2019-07-08 14:13:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(mRuns) < 1 {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrRunNotFound
|
2019-07-08 14:13:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var run *influxdb.Run
|
|
|
|
for i, r := range mRuns {
|
|
|
|
if r.ID == runID {
|
|
|
|
run = r
|
|
|
|
mRuns = append(mRuns[:i], mRuns[i+1:]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if run == nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrRunNotFound
|
2019-07-08 14:13:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// save manual runs
|
|
|
|
mRunsBytes, err := json.Marshal(mRuns)
|
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
runsKey, err := taskManualRunKey(taskID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.Put(runsKey, mRunsBytes); err != nil {
|
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// add mRun to the list of currently running
|
|
|
|
mRunBytes, err := json.Marshal(run)
|
|
|
|
if err != nil {
|
|
|
|
return nil, influxdb.ErrInternalTaskServiceError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
runKey, err := taskRunKey(taskID, run.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.Put(runKey, mRunBytes); err != nil {
|
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return run, nil
|
|
|
|
}
|
|
|
|
|
2019-04-09 22:52:54 +00:00
|
|
|
// FinishRun removes runID from the list of running tasks and if its `now` is later then last completed update it.
|
|
|
|
func (s *Service) FinishRun(ctx context.Context, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
var run *influxdb.Run
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
r, err := s.finishRun(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
run = r
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return run, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) finishRun(ctx context.Context, tx Tx, taskID, runID influxdb.ID) (*influxdb.Run, error) {
|
|
|
|
// get the run
|
|
|
|
r, err := s.findRunByID(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-08-15 21:28:35 +00:00
|
|
|
|
|
|
|
// tell task to update latest completed
|
2019-11-13 01:13:56 +00:00
|
|
|
scheduled := r.ScheduledFor
|
2020-08-25 14:44:37 +00:00
|
|
|
|
|
|
|
var latestSuccess, latestFailure *time.Time
|
|
|
|
|
|
|
|
if r.Status == "failed" {
|
|
|
|
latestFailure = &scheduled
|
|
|
|
} else {
|
|
|
|
latestSuccess = &scheduled
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:24:41 +00:00
|
|
|
_, err = s.updateTask(ctx, tx, taskID, influxdb.TaskUpdate{
|
2019-11-13 01:13:56 +00:00
|
|
|
LatestCompleted: &scheduled,
|
2020-08-25 14:44:37 +00:00
|
|
|
LatestSuccess: latestSuccess,
|
|
|
|
LatestFailure: latestFailure,
|
2019-10-30 00:24:41 +00:00
|
|
|
LastRunStatus: &r.Status,
|
|
|
|
LastRunError: func() *string {
|
|
|
|
if r.Status == "failed" {
|
|
|
|
// prefer the second to last log message as the error message
|
|
|
|
// per https://github.com/influxdata/influxdb/issues/15153#issuecomment-547706005
|
|
|
|
if len(r.Log) > 1 {
|
|
|
|
return &r.Log[len(r.Log)-2].Message
|
|
|
|
} else if len(r.Log) > 0 {
|
|
|
|
return &r.Log[len(r.Log)-1].Message
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}(),
|
|
|
|
})
|
2019-04-09 22:52:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-08-15 21:28:35 +00:00
|
|
|
// remove run
|
2019-04-09 22:52:54 +00:00
|
|
|
bucket, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
key, err := taskRunKey(taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := bucket.Delete(key); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return nil, influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateRunState sets the run state at the respective time.
|
2020-03-06 22:19:32 +00:00
|
|
|
func (s *Service) UpdateRunState(ctx context.Context, taskID, runID influxdb.ID, when time.Time, state influxdb.RunStatus) error {
|
2019-04-09 22:52:54 +00:00
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
err := s.updateRunState(ctx, tx, taskID, runID, when, state)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-03-06 22:19:32 +00:00
|
|
|
func (s *Service) updateRunState(ctx context.Context, tx Tx, taskID, runID influxdb.ID, when time.Time, state influxdb.RunStatus) error {
|
2019-04-09 22:52:54 +00:00
|
|
|
// find run
|
|
|
|
run, err := s.findRunByID(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// update state
|
|
|
|
run.Status = state.String()
|
|
|
|
switch state {
|
2020-03-06 22:19:32 +00:00
|
|
|
case influxdb.RunStarted:
|
2019-10-18 00:23:45 +00:00
|
|
|
run.StartedAt = when
|
2020-03-06 22:19:32 +00:00
|
|
|
case influxdb.RunSuccess, influxdb.RunFail, influxdb.RunCanceled:
|
2019-10-18 00:23:45 +00:00
|
|
|
run.FinishedAt = when
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// save run
|
|
|
|
b, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runBytes, err := json.Marshal(run)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runKey, err := taskRunKey(taskID, run.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := b.Put(runKey, runBytes); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddRunLog adds a log line to the run.
|
|
|
|
func (s *Service) AddRunLog(ctx context.Context, taskID, runID influxdb.ID, when time.Time, log string) error {
|
|
|
|
err := s.kv.Update(ctx, func(tx Tx) error {
|
|
|
|
err := s.addRunLog(ctx, tx, taskID, runID, when, log)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) addRunLog(ctx context.Context, tx Tx, taskID, runID influxdb.ID, when time.Time, log string) error {
|
|
|
|
// find run
|
|
|
|
run, err := s.findRunByID(ctx, tx, taskID, runID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// update log
|
2019-06-05 17:53:44 +00:00
|
|
|
l := influxdb.Log{RunID: runID, Time: when.Format(time.RFC3339Nano), Message: log}
|
2019-04-09 22:52:54 +00:00
|
|
|
run.Log = append(run.Log, l)
|
|
|
|
// save run
|
|
|
|
b, err := tx.Bucket(taskRunBucket)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runBytes, err := json.Marshal(run)
|
|
|
|
if err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrInternalTaskServiceError(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runKey, err := taskRunKey(taskID, run.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.Put(runKey, runBytes); err != nil {
|
2019-06-13 19:48:20 +00:00
|
|
|
return influxdb.ErrUnexpectedTaskBucketErr(err)
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func taskKey(taskID influxdb.ID) ([]byte, error) {
|
|
|
|
encodedID, err := taskID.Encode()
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrInvalidTaskID
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
return encodedID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func taskLatestCompletedKey(taskID influxdb.ID) ([]byte, error) {
|
|
|
|
encodedID, err := taskID.Encode()
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrInvalidTaskID
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
return []byte(string(encodedID) + "/latestCompleted"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func taskManualRunKey(taskID influxdb.ID) ([]byte, error) {
|
|
|
|
encodedID, err := taskID.Encode()
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrInvalidTaskID
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
return []byte(string(encodedID) + "/manualRuns"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func taskOrgKey(orgID, taskID influxdb.ID) ([]byte, error) {
|
|
|
|
encodedOrgID, err := orgID.Encode()
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrInvalidTaskID
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
encodedID, err := taskID.Encode()
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrInvalidTaskID
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return []byte(string(encodedOrgID) + "/" + string(encodedID)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func taskRunKey(taskID, runID influxdb.ID) ([]byte, error) {
|
|
|
|
encodedID, err := taskID.Encode()
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrInvalidTaskID
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
encodedRunID, err := runID.Encode()
|
|
|
|
if err != nil {
|
2019-07-15 20:57:51 +00:00
|
|
|
return nil, influxdb.ErrInvalidTaskID
|
2019-04-09 22:52:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return []byte(string(encodedID) + "/" + string(encodedRunID)), nil
|
|
|
|
}
|
2020-06-17 18:30:37 +00:00
|
|
|
|
2020-06-23 16:22:53 +00:00
|
|
|
// ExtractTaskOptions is a feature-flag driven switch between normal options
|
|
|
|
// parsing and a more simplified variant.
|
|
|
|
//
|
|
|
|
// The simplified variant extracts the options assignment and passes only that
|
|
|
|
// content through the parser. This allows us to allow scenarios like [1] to
|
|
|
|
// pass through options validation. One clear drawback of this is that it
|
|
|
|
// requires constant values for the parameter assignments. However, most people
|
|
|
|
// are doing that anyway.
|
|
|
|
//
|
|
|
|
// [1]: https://github.com/influxdata/influxdb/issues/17666
|
|
|
|
func ExtractTaskOptions(ctx context.Context, lang influxdb.FluxLanguageService, flux string) (options.Options, error) {
|
2020-08-20 15:57:23 +00:00
|
|
|
if feature.SimpleTaskOptionsExtraction().Enabled(ctx) {
|
|
|
|
return options.FromScriptAST(lang, flux)
|
2020-06-23 16:22:53 +00:00
|
|
|
}
|
2020-08-20 15:57:23 +00:00
|
|
|
return options.FromScript(lang, flux)
|
2020-06-23 16:22:53 +00:00
|
|
|
}
|