mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-27 04:35:16 +01:00
Merge pull request #8352 from ywk253100/190717_scheduler
Implement a common scheduler
This commit is contained in:
commit
388f8311f5
@ -47,12 +47,12 @@ create table retention_task
|
||||
timestamp time
|
||||
);
|
||||
|
||||
create table retention_schedule_job
|
||||
create table schedule
|
||||
(
|
||||
id integer PRIMARY KEY NOT NULL,
|
||||
status varchar(20),
|
||||
policy_id integer,
|
||||
job_id integer,
|
||||
create_time time,
|
||||
update_time time
|
||||
id SERIAL NOT NULL,
|
||||
job_id varchar(64),
|
||||
status varchar(64),
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
update_time timestamp default CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
@ -11,9 +11,16 @@ import (
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/http/modifier/auth"
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
)
|
||||
|
||||
var (
|
||||
// GlobalClient is an instance of the default client that can be used globally
|
||||
// Notes: the client needs to be initialized before can be used
|
||||
GlobalClient Client
|
||||
)
|
||||
|
||||
// Client wraps interface to access jobservice.
|
||||
type Client interface {
|
||||
SubmitJob(*models.JobData) (string, error)
|
||||
@ -29,6 +36,11 @@ type DefaultClient struct {
|
||||
client *commonhttp.Client
|
||||
}
|
||||
|
||||
// Init the GlobalClient
|
||||
func Init() {
|
||||
GlobalClient = NewDefaultClient(config.InternalJobServiceURL(), config.CoreSecret())
|
||||
}
|
||||
|
||||
// NewDefaultClient creates a default client based on endpoint and secret.
|
||||
func NewDefaultClient(endpoint, secret string) *DefaultClient {
|
||||
var c *commonhttp.Client
|
||||
|
@ -37,6 +37,7 @@ const (
|
||||
ScanJobType = "scan"
|
||||
)
|
||||
|
||||
// the managers/controllers used globally
|
||||
var (
|
||||
projectMgr project.Manager
|
||||
repositoryMgr repository.Manager
|
||||
|
@ -23,6 +23,8 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
_ "github.com/astaxie/beego/session/redis"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
@ -38,6 +40,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/filter"
|
||||
"github.com/goharbor/harbor/src/core/proxy"
|
||||
"github.com/goharbor/harbor/src/core/service/token"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler"
|
||||
"github.com/goharbor/harbor/src/replication"
|
||||
)
|
||||
|
||||
@ -107,6 +110,11 @@ func main() {
|
||||
log.Fatalf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// init the scheduler
|
||||
scheduler.Init()
|
||||
// init the jobservice client
|
||||
job.Init()
|
||||
|
||||
password, err := config.InitialAdminPassword()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get admin's initia password: %v", err)
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/service/notifications/clair"
|
||||
"github.com/goharbor/harbor/src/core/service/notifications/jobs"
|
||||
"github.com/goharbor/harbor/src/core/service/notifications/registry"
|
||||
"github.com/goharbor/harbor/src/core/service/notifications/scheduler"
|
||||
"github.com/goharbor/harbor/src/core/service/token"
|
||||
retentionCtl "github.com/goharbor/harbor/src/pkg/retention/controllers"
|
||||
|
||||
@ -131,6 +132,7 @@ func initRouters() {
|
||||
beego.Router("/service/notifications/jobs/adminjob/:id([0-9]+)", &admin.Handler{}, "post:HandleAdminJob")
|
||||
beego.Router("/service/notifications/jobs/replication/:id([0-9]+)", &jobs.Handler{}, "post:HandleReplicationScheduleJob")
|
||||
beego.Router("/service/notifications/jobs/replication/task/:id([0-9]+)", &jobs.Handler{}, "post:HandleReplicationTask")
|
||||
beego.Router("/service/notifications/schedules/:id([0-9]+)", &scheduler.Handler{}, "post:Handle")
|
||||
beego.Router("/service/token", &token.Handler{})
|
||||
|
||||
beego.Router("/api/registries", &api.RegistryAPI{}, "get:List;post:Post")
|
||||
|
70
src/core/service/notifications/scheduler/handler.go
Normal file
70
src/core/service/notifications/scheduler/handler.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/hook"
|
||||
)
|
||||
|
||||
// Handler handles the scheduler requests
|
||||
type Handler struct {
|
||||
api.BaseController
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (h *Handler) Handle() {
|
||||
var data models.JobStatusChange
|
||||
// status update
|
||||
if len(data.CheckIn) == 0 {
|
||||
schedulerID, err := h.GetInt64FromPath(":id")
|
||||
if err != nil {
|
||||
log.Errorf("failed to get the schedule ID: %v", err)
|
||||
return
|
||||
}
|
||||
if err := hook.GlobalController.UpdateStatus(schedulerID, data.Status); err != nil {
|
||||
h.SendInternalServerError(fmt.Errorf("failed to update status of job %s: %v", data.JobID, err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// run callback function
|
||||
// just log the error message when handling check in request if got any error
|
||||
params := map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(data.CheckIn), ¶ms); err != nil {
|
||||
log.Errorf("failed to unmarshal parameters from check in message: %v", err)
|
||||
return
|
||||
}
|
||||
callbackFuncNameParam, exist := params["callback_func_name"]
|
||||
if !exist {
|
||||
log.Error("cannot get the parameter \"callback_func_name\" from the check in message")
|
||||
return
|
||||
}
|
||||
callbackFuncName, ok := callbackFuncNameParam.(string)
|
||||
if !ok || len(callbackFuncName) == 0 {
|
||||
log.Errorf("invalid \"callback_func_name\": %v", callbackFuncName)
|
||||
return
|
||||
}
|
||||
if err := hook.GlobalController.Run(callbackFuncName, params["params"]); err != nil {
|
||||
log.Errorf("failed to run the callback function %s: %v", callbackFuncName, err)
|
||||
return
|
||||
}
|
||||
}
|
@ -23,10 +23,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/mgt"
|
||||
"github.com/goharbor/harbor/src/jobservice/migration"
|
||||
"github.com/goharbor/harbor/src/pkg/retention"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/api"
|
||||
"github.com/goharbor/harbor/src/jobservice/common/utils"
|
||||
"github.com/goharbor/harbor/src/jobservice/config"
|
||||
@ -40,8 +36,11 @@ import (
|
||||
"github.com/goharbor/harbor/src/jobservice/job/impl/scan"
|
||||
"github.com/goharbor/harbor/src/jobservice/lcm"
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/jobservice/mgt"
|
||||
"github.com/goharbor/harbor/src/jobservice/migration"
|
||||
"github.com/goharbor/harbor/src/jobservice/worker"
|
||||
"github.com/goharbor/harbor/src/jobservice/worker/cworker"
|
||||
"github.com/goharbor/harbor/src/pkg/retention"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
99
src/pkg/scheduler/dao/schedule.go
Normal file
99
src/pkg/scheduler/dao/schedule.go
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
)
|
||||
|
||||
// ScheduleDao defines the method that a schedule data access model should implement
|
||||
type ScheduleDao interface {
|
||||
Create(*model.Schedule) (int64, error)
|
||||
Update(*model.Schedule, ...string) error
|
||||
Delete(int64) error
|
||||
Get(int64) (*model.Schedule, error)
|
||||
List(...*model.ScheduleQuery) ([]*model.Schedule, error)
|
||||
}
|
||||
|
||||
// New returns an instance of the default schedule data access model implementation
|
||||
func New() ScheduleDao {
|
||||
return &scheduleDao{}
|
||||
}
|
||||
|
||||
type scheduleDao struct{}
|
||||
|
||||
func (s *scheduleDao) Create(schedule *model.Schedule) (int64, error) {
|
||||
if schedule == nil {
|
||||
return 0, errors.New("nil schedule")
|
||||
}
|
||||
now := time.Now()
|
||||
schedule.CreationTime = &now
|
||||
schedule.UpdateTime = &now
|
||||
return dao.GetOrmer().Insert(schedule)
|
||||
}
|
||||
|
||||
func (s *scheduleDao) Update(schedule *model.Schedule, cols ...string) error {
|
||||
if schedule == nil {
|
||||
return errors.New("nil schedule")
|
||||
}
|
||||
if schedule.ID <= 0 {
|
||||
return fmt.Errorf("invalid ID: %d", schedule.ID)
|
||||
}
|
||||
now := time.Now()
|
||||
schedule.UpdateTime = &now
|
||||
_, err := dao.GetOrmer().Update(schedule, cols...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *scheduleDao) Delete(id int64) error {
|
||||
_, err := dao.GetOrmer().Delete(&model.Schedule{
|
||||
ID: id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *scheduleDao) Get(id int64) (*model.Schedule, error) {
|
||||
schedule := &model.Schedule{
|
||||
ID: id,
|
||||
}
|
||||
if err := dao.GetOrmer().Read(schedule); err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return schedule, nil
|
||||
}
|
||||
|
||||
func (s *scheduleDao) List(query ...*model.ScheduleQuery) ([]*model.Schedule, error) {
|
||||
qs := dao.GetOrmer().QueryTable(&model.Schedule{})
|
||||
if len(query) > 0 && query[0] != nil {
|
||||
if len(query[0].JobID) > 0 {
|
||||
qs = qs.Filter("JobID", query[0].JobID)
|
||||
}
|
||||
}
|
||||
schedules := []*model.Schedule{}
|
||||
_, err := qs.All(&schedules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return schedules, nil
|
||||
}
|
122
src/pkg/scheduler/dao/schedule_test.go
Normal file
122
src/pkg/scheduler/dao/schedule_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var schDao = &scheduleDao{}
|
||||
|
||||
type scheduleTestSuite struct {
|
||||
suite.Suite
|
||||
scheduleID int64
|
||||
}
|
||||
|
||||
func (s *scheduleTestSuite) SetupSuite() {
|
||||
dao.PrepareTestForPostgresSQL()
|
||||
}
|
||||
|
||||
func (s *scheduleTestSuite) SetupTest() {
|
||||
t := s.T()
|
||||
id, err := schDao.Create(&model.Schedule{
|
||||
JobID: "1",
|
||||
Status: "pending",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
s.scheduleID = id
|
||||
}
|
||||
func (s *scheduleTestSuite) TearDownTest() {
|
||||
// clear
|
||||
dao.GetOrmer().Raw("delete from schedule").Exec()
|
||||
}
|
||||
|
||||
func (s *scheduleTestSuite) TestCreate() {
|
||||
t := s.T()
|
||||
// nil schedule
|
||||
_, err := schDao.Create(nil)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// pass
|
||||
_, err = schDao.Create(&model.Schedule{
|
||||
JobID: "1",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func (s *scheduleTestSuite) TestUpdate() {
|
||||
t := s.T()
|
||||
// nil schedule
|
||||
err := schDao.Update(nil)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// invalid ID
|
||||
err = schDao.Update(&model.Schedule{})
|
||||
require.NotNil(t, err)
|
||||
|
||||
// pass
|
||||
err = schDao.Update(&model.Schedule{
|
||||
ID: s.scheduleID,
|
||||
Status: "running",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
schedule, err := schDao.Get(s.scheduleID)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "running", schedule.Status)
|
||||
}
|
||||
|
||||
func (s *scheduleTestSuite) TestDelete() {
|
||||
t := s.T()
|
||||
err := schDao.Delete(s.scheduleID)
|
||||
require.Nil(t, err)
|
||||
schedule, err := schDao.Get(s.scheduleID)
|
||||
require.Nil(t, err)
|
||||
assert.Nil(t, schedule)
|
||||
}
|
||||
|
||||
func (s *scheduleTestSuite) TestGet() {
|
||||
t := s.T()
|
||||
schedule, err := schDao.Get(s.scheduleID)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, "pending", schedule.Status)
|
||||
}
|
||||
|
||||
func (s *scheduleTestSuite) TestList() {
|
||||
t := s.T()
|
||||
// nil query
|
||||
schedules, err := schDao.List()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 1, len(schedules))
|
||||
assert.Equal(t, s.scheduleID, schedules[0].ID)
|
||||
|
||||
// query by job ID
|
||||
schedules, err = schDao.List(&model.ScheduleQuery{
|
||||
JobID: "1",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 1, len(schedules))
|
||||
assert.Equal(t, s.scheduleID, schedules[0].ID)
|
||||
}
|
||||
|
||||
func TestScheduleDao(t *testing.T) {
|
||||
suite.Run(t, &scheduleTestSuite{})
|
||||
}
|
59
src/pkg/scheduler/hook/handler.go
Normal file
59
src/pkg/scheduler/hook/handler.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hook
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
)
|
||||
|
||||
// GlobalController is an instance of the default controller that can be used globally
|
||||
var GlobalController = NewController()
|
||||
|
||||
// Controller updates the scheduler job status or runs the callback function
|
||||
type Controller interface {
|
||||
UpdateStatus(scheduleID int64, status string) error
|
||||
Run(callbackFuncName string, params interface{}) error
|
||||
}
|
||||
|
||||
// NewController returns an instance of the default controller
|
||||
func NewController() Controller {
|
||||
return &controller{
|
||||
manager: scheduler.GlobalManager,
|
||||
}
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
manager scheduler.Manager
|
||||
}
|
||||
|
||||
func (c *controller) UpdateStatus(scheduleID int64, status string) error {
|
||||
now := time.Now()
|
||||
return c.manager.Update(&model.Schedule{
|
||||
ID: scheduleID,
|
||||
Status: status,
|
||||
UpdateTime: &now,
|
||||
}, "Status", "UpdateTime")
|
||||
}
|
||||
|
||||
func (c *controller) Run(callbackFuncName string, params interface{}) error {
|
||||
f, err := scheduler.GetCallbackFunc(callbackFuncName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(params)
|
||||
}
|
56
src/pkg/scheduler/hook/handler_test.go
Normal file
56
src/pkg/scheduler/hook/handler_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var h = &controller{
|
||||
manager: &htesting.FakeSchedulerManager{},
|
||||
}
|
||||
|
||||
func TestUpdateStatus(t *testing.T) {
|
||||
// task not exist
|
||||
err := h.UpdateStatus(1, "running")
|
||||
require.NotNil(t, err)
|
||||
|
||||
// pass
|
||||
h.manager.(*htesting.FakeSchedulerManager).Schedules = []*model.Schedule{
|
||||
{
|
||||
ID: 1,
|
||||
Status: "",
|
||||
},
|
||||
}
|
||||
err = h.UpdateStatus(1, "running")
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
// callback function not exist
|
||||
err := h.Run("not-exist", nil)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// pass
|
||||
err = scheduler.Register("callback", func(interface{}) error { return nil })
|
||||
require.Nil(t, err)
|
||||
err = h.Run("callback", nil)
|
||||
require.Nil(t, err)
|
||||
}
|
66
src/pkg/scheduler/manager.go
Normal file
66
src/pkg/scheduler/manager.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
)
|
||||
|
||||
var (
|
||||
// GlobalManager is an instance of the default manager that
|
||||
// can be used globally
|
||||
GlobalManager = NewManager()
|
||||
)
|
||||
|
||||
// Manager manages the schedule of the scheduler
|
||||
type Manager interface {
|
||||
Create(*model.Schedule) (int64, error)
|
||||
Update(*model.Schedule, ...string) error
|
||||
Delete(int64) error
|
||||
Get(int64) (*model.Schedule, error)
|
||||
List(...*model.ScheduleQuery) ([]*model.Schedule, error)
|
||||
}
|
||||
|
||||
// NewManager returns an instance of the default manager
|
||||
func NewManager() Manager {
|
||||
return &manager{
|
||||
scheduleDao: dao.New(),
|
||||
}
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
scheduleDao dao.ScheduleDao
|
||||
}
|
||||
|
||||
func (m *manager) Create(schedule *model.Schedule) (int64, error) {
|
||||
return m.scheduleDao.Create(schedule)
|
||||
}
|
||||
|
||||
func (m *manager) Update(schedule *model.Schedule, props ...string) error {
|
||||
return m.scheduleDao.Update(schedule, props...)
|
||||
}
|
||||
|
||||
func (m *manager) Delete(id int64) error {
|
||||
return m.scheduleDao.Delete(id)
|
||||
}
|
||||
|
||||
func (m *manager) List(query ...*model.ScheduleQuery) ([]*model.Schedule, error) {
|
||||
return m.scheduleDao.List(query...)
|
||||
}
|
||||
|
||||
func (m *manager) Get(id int64) (*model.Schedule, error) {
|
||||
return m.scheduleDao.Get(id)
|
||||
}
|
110
src/pkg/scheduler/manager_test.go
Normal file
110
src/pkg/scheduler/manager_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var mgr *manager
|
||||
|
||||
type fakeScheduleDao struct {
|
||||
schedules []*model.Schedule
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (f *fakeScheduleDao) Create(*model.Schedule) (int64, error) {
|
||||
f.Called()
|
||||
return 1, nil
|
||||
}
|
||||
func (f *fakeScheduleDao) Update(*model.Schedule, ...string) error {
|
||||
f.Called()
|
||||
return nil
|
||||
}
|
||||
func (f *fakeScheduleDao) Delete(int64) error {
|
||||
f.Called()
|
||||
return nil
|
||||
}
|
||||
func (f *fakeScheduleDao) Get(int64) (*model.Schedule, error) {
|
||||
f.Called()
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeScheduleDao) List(query ...*model.ScheduleQuery) ([]*model.Schedule, error) {
|
||||
f.Called()
|
||||
if len(query) == 0 || query[0] == nil {
|
||||
return f.schedules, nil
|
||||
}
|
||||
result := []*model.Schedule{}
|
||||
for _, sch := range f.schedules {
|
||||
if sch.JobID == query[0].JobID {
|
||||
result = append(result, sch)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type managerTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) SetupTest() {
|
||||
// recreate schedule manager
|
||||
mgr = &manager{
|
||||
scheduleDao: &fakeScheduleDao{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestCreate() {
|
||||
t := m.T()
|
||||
mgr.scheduleDao.(*fakeScheduleDao).On("Create", mock.Anything)
|
||||
mgr.Create(nil)
|
||||
mgr.scheduleDao.(*fakeScheduleDao).AssertCalled(t, "Create")
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestUpdate() {
|
||||
t := m.T()
|
||||
mgr.scheduleDao.(*fakeScheduleDao).On("Update", mock.Anything)
|
||||
mgr.Update(nil)
|
||||
mgr.scheduleDao.(*fakeScheduleDao).AssertCalled(t, "Update")
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestDelete() {
|
||||
t := m.T()
|
||||
mgr.scheduleDao.(*fakeScheduleDao).On("Delete", mock.Anything)
|
||||
mgr.Delete(1)
|
||||
mgr.scheduleDao.(*fakeScheduleDao).AssertCalled(t, "Delete")
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestGet() {
|
||||
t := m.T()
|
||||
mgr.scheduleDao.(*fakeScheduleDao).On("Get", mock.Anything)
|
||||
mgr.Get(1)
|
||||
mgr.scheduleDao.(*fakeScheduleDao).AssertCalled(t, "Get")
|
||||
}
|
||||
|
||||
func (m *managerTestSuite) TestList() {
|
||||
t := m.T()
|
||||
mgr.scheduleDao.(*fakeScheduleDao).On("List", mock.Anything)
|
||||
mgr.List(nil)
|
||||
mgr.scheduleDao.(*fakeScheduleDao).AssertCalled(t, "List")
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
suite.Run(t, &managerTestSuite{})
|
||||
}
|
40
src/pkg/scheduler/model/schedule.go
Normal file
40
src/pkg/scheduler/model/schedule.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(
|
||||
new(Schedule))
|
||||
}
|
||||
|
||||
// Schedule is a record for a scheduler job
|
||||
type Schedule struct {
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||
JobID string `orm:"column(job_id)" json:"job_id"`
|
||||
Status string `orm:"column(status)" json:"status"`
|
||||
CreationTime *time.Time `orm:"column(creation_time)" json:"creation_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time)" json:"update_time"`
|
||||
}
|
||||
|
||||
// ScheduleQuery is query for schedule
|
||||
type ScheduleQuery struct {
|
||||
JobID string
|
||||
}
|
@ -12,12 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package retention
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"time"
|
||||
)
|
||||
|
||||
// const definitions
|
||||
const (
|
||||
// the job name that used to register to Jobservice
|
||||
JobNameScheduler = "SCHEDULER"
|
||||
)
|
||||
|
||||
// PeriodicJob is designed to generate hook event periodically
|
||||
@ -40,5 +46,9 @@ func (pj *PeriodicJob) Validate(params job.Parameters) error {
|
||||
|
||||
// Run the job
|
||||
func (pj *PeriodicJob) Run(ctx job.Context, params job.Parameters) error {
|
||||
return ctx.Checkin(fmt.Sprintf("pong=%d", time.Now().Unix()))
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Checkin(string(data))
|
||||
}
|
192
src/pkg/scheduler/scheduler.go
Normal file
192
src/pkg/scheduler/scheduler.go
Normal file
@ -0,0 +1,192 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
chttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
jobParamCallbackFunc = "callback_func"
|
||||
jobParamCallbackFuncParams = "params"
|
||||
)
|
||||
|
||||
var (
|
||||
// GlobalScheduler is an instance of the default scheduler that
|
||||
// can be used globally. Call Init() to initialize it first
|
||||
GlobalScheduler Scheduler
|
||||
registry = make(map[string]CallbackFunc)
|
||||
)
|
||||
|
||||
// CallbackFunc defines the function that the scheduler calls when triggered
|
||||
type CallbackFunc func(interface{}) error
|
||||
|
||||
// Scheduler provides the capability to run a periodic task, a callback function
|
||||
// needs to be registered before using the scheduler
|
||||
type Scheduler interface {
|
||||
Schedule(cron string, callbackFuncName string, params interface{}) (int64, error)
|
||||
UnSchedule(id int64) error
|
||||
}
|
||||
|
||||
// Register the callback function with name, and the function will be called
|
||||
// by the scheduler when the scheduler is triggered
|
||||
func Register(name string, callbackFunc CallbackFunc) error {
|
||||
if len(name) == 0 {
|
||||
return errors.New("empty name")
|
||||
}
|
||||
if callbackFunc == nil {
|
||||
return errors.New("callback function is nil")
|
||||
}
|
||||
|
||||
_, exist := registry[name]
|
||||
if exist {
|
||||
return fmt.Errorf("callback function %s already exists", name)
|
||||
}
|
||||
registry[name] = callbackFunc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCallbackFunc returns the registered callback function specified by the name
|
||||
func GetCallbackFunc(name string) (CallbackFunc, error) {
|
||||
f, exist := registry[name]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("callback function %s not found", name)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func callbackFuncExist(name string) bool {
|
||||
_, exist := registry[name]
|
||||
return exist
|
||||
}
|
||||
|
||||
// Init the GlobalScheduler
|
||||
func Init() {
|
||||
GlobalScheduler = New(config.InternalCoreURL())
|
||||
}
|
||||
|
||||
// New returns an instance of the default scheduler
|
||||
func New(internalCoreURL string) Scheduler {
|
||||
return &scheduler{
|
||||
internalCoreURL: internalCoreURL,
|
||||
jobserviceClient: job.GlobalClient,
|
||||
manager: GlobalManager,
|
||||
}
|
||||
}
|
||||
|
||||
type scheduler struct {
|
||||
sync.RWMutex
|
||||
internalCoreURL string
|
||||
manager Manager
|
||||
jobserviceClient job.Client
|
||||
}
|
||||
|
||||
func (s *scheduler) Schedule(cron string, callbackFuncName string, params interface{}) (int64, error) {
|
||||
if !callbackFuncExist(callbackFuncName) {
|
||||
return 0, fmt.Errorf("callback function %s not found", callbackFuncName)
|
||||
}
|
||||
|
||||
// create schedule record
|
||||
now := time.Now()
|
||||
scheduleID, err := s.manager.Create(&model.Schedule{
|
||||
CreationTime: &now,
|
||||
UpdateTime: &now,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
log.Debugf("the schedule record %d created", scheduleID)
|
||||
|
||||
// submit scheduler job to Jobservice
|
||||
statusHookURL := fmt.Sprintf("%s/service/notifications/schedules/%d", s.internalCoreURL, scheduleID)
|
||||
jd := &models.JobData{
|
||||
Name: JobNameScheduler,
|
||||
Parameters: map[string]interface{}{
|
||||
jobParamCallbackFunc: callbackFuncName,
|
||||
jobParamCallbackFuncParams: params,
|
||||
},
|
||||
Metadata: &models.JobMetadata{
|
||||
JobKind: job.JobKindPeriodic,
|
||||
Cron: cron,
|
||||
},
|
||||
StatusHook: statusHookURL,
|
||||
}
|
||||
jobID, err := s.jobserviceClient.SubmitJob(jd)
|
||||
if err != nil {
|
||||
// if failed to submit to Jobservice, delete the schedule record in database
|
||||
e := s.manager.Delete(scheduleID)
|
||||
if e != nil {
|
||||
log.Errorf("failed to delete the schedule %d: %v", scheduleID, e)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
log.Debugf("the scheduler job submitted to Jobservice, job ID: %s", jobID)
|
||||
|
||||
// populate the job ID for the schedule
|
||||
err = s.manager.Update(&model.Schedule{
|
||||
ID: scheduleID,
|
||||
JobID: jobID,
|
||||
}, "JobID")
|
||||
if err != nil {
|
||||
// stop the scheduler job
|
||||
if e := s.jobserviceClient.PostAction(jobID, job.JobActionStop); e != nil {
|
||||
log.Errorf("failed to stop the scheduler job %s: %v", jobID, e)
|
||||
}
|
||||
// delete the schedule record
|
||||
if e := s.manager.Delete(scheduleID); e != nil {
|
||||
log.Errorf("failed to delete the schedule record %d: %v", scheduleID, e)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return scheduleID, nil
|
||||
}
|
||||
|
||||
func (s *scheduler) UnSchedule(id int64) error {
|
||||
schedule, err := s.manager.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if schedule == nil {
|
||||
return fmt.Errorf("the schedule record %d not found", id)
|
||||
}
|
||||
if err = s.jobserviceClient.PostAction(schedule.JobID, job.JobActionStop); err != nil {
|
||||
herr, ok := err.(*chttp.Error)
|
||||
// if the job specified by jobID is not found in Jobservice, just delete
|
||||
// the schedule record
|
||||
if !ok || herr.Code != http.StatusNotFound {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Debugf("the stop action for job %s submitted to the Jobservice", schedule.JobID)
|
||||
if err = s.manager.Delete(schedule.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("the schedule record %d deleted", schedule.ID)
|
||||
|
||||
return nil
|
||||
}
|
115
src/pkg/scheduler/scheduler_test.go
Normal file
115
src/pkg/scheduler/scheduler_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/goharbor/harbor/src/testing/job"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var sch *scheduler
|
||||
|
||||
type schedulerTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (s *schedulerTestSuite) SetupTest() {
|
||||
t := s.T()
|
||||
// empty callback function registry before running every test case
|
||||
// and register a new callback function named "callback"
|
||||
registry = make(map[string]CallbackFunc)
|
||||
err := Register("callback", func(interface{}) error { return nil })
|
||||
require.Nil(t, err)
|
||||
|
||||
// recreate the scheduler object
|
||||
sch = &scheduler{
|
||||
jobserviceClient: &job.MockJobClient{},
|
||||
manager: &htesting.FakeSchedulerManager{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *schedulerTestSuite) TestRegister() {
|
||||
t := s.T()
|
||||
var name string
|
||||
var callbackFun CallbackFunc
|
||||
|
||||
// empty name
|
||||
err := Register(name, callbackFun)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// nil callback function
|
||||
name = "test"
|
||||
err = Register(name, callbackFun)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// pass
|
||||
callbackFun = func(interface{}) error { return nil }
|
||||
err = Register(name, callbackFun)
|
||||
require.Nil(t, err)
|
||||
|
||||
// duplicate name
|
||||
err = Register(name, callbackFun)
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func (s *schedulerTestSuite) TestGetCallbackFunc() {
|
||||
t := s.T()
|
||||
// not exist
|
||||
_, err := GetCallbackFunc("not-exist")
|
||||
require.NotNil(t, err)
|
||||
|
||||
// pass
|
||||
f, err := GetCallbackFunc("callback")
|
||||
require.Nil(t, err)
|
||||
assert.NotNil(t, f)
|
||||
}
|
||||
|
||||
func (s *schedulerTestSuite) TestSchedule() {
|
||||
t := s.T()
|
||||
|
||||
// callback function not exist
|
||||
_, err := sch.Schedule("0 * * * * *", "not-exist", nil)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// pass
|
||||
id, err := sch.Schedule("0 * * * * *", "callback", nil)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(1), id)
|
||||
}
|
||||
|
||||
func (s *schedulerTestSuite) TestUnSchedule() {
|
||||
t := s.T()
|
||||
// schedule not exist
|
||||
err := sch.UnSchedule(1)
|
||||
require.NotNil(t, err)
|
||||
|
||||
// schedule exist
|
||||
id, err := sch.Schedule("0 * * * * *", "callback", nil)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(1), id)
|
||||
|
||||
err = sch.UnSchedule(id)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestScheduler(t *testing.T) {
|
||||
s := &schedulerTestSuite{}
|
||||
suite.Run(t, s)
|
||||
}
|
@ -5,8 +5,8 @@ import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
"github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
)
|
||||
|
||||
// MockJobClient ...
|
||||
@ -27,12 +27,9 @@ func (mjc *MockJobClient) GetJobLog(uuid string) ([]byte, error) {
|
||||
|
||||
// SubmitJob ...
|
||||
func (mjc *MockJobClient) SubmitJob(data *models.JobData) (string, error) {
|
||||
if data.Name == job.ImageScanAllJob || data.Name == job.Replication || data.Name == job.ImageGC || data.Name == job.ImageScanJob {
|
||||
uuid := fmt.Sprintf("u-%d", rand.Int())
|
||||
mjc.JobUUID = append(mjc.JobUUID, uuid)
|
||||
return uuid, nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported job %s", data.Name)
|
||||
uuid := fmt.Sprintf("u-%d", rand.Int())
|
||||
mjc.JobUUID = append(mjc.JobUUID, uuid)
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// PostAction ...
|
||||
@ -46,6 +43,11 @@ func (mjc *MockJobClient) PostAction(uuid, action string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExecutions ...
|
||||
func (mjc *MockJobClient) GetExecutions(uuid string) ([]job.Stats, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mjc *MockJobClient) validUUID(uuid string) bool {
|
||||
for _, u := range mjc.JobUUID {
|
||||
if uuid == u {
|
||||
|
77
src/testing/scheduler.go
Normal file
77
src/testing/scheduler.go
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/scheduler/model"
|
||||
)
|
||||
|
||||
// FakeSchedulerManager ...
|
||||
type FakeSchedulerManager struct {
|
||||
idCounter int64
|
||||
Schedules []*model.Schedule
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (f *FakeSchedulerManager) Create(schedule *model.Schedule) (int64, error) {
|
||||
f.idCounter++
|
||||
id := f.idCounter
|
||||
schedule.ID = id
|
||||
f.Schedules = append(f.Schedules, schedule)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Update ...
|
||||
func (f *FakeSchedulerManager) Update(schedule *model.Schedule, props ...string) error {
|
||||
for i, sch := range f.Schedules {
|
||||
if sch.ID == schedule.ID {
|
||||
f.Schedules[i] = schedule
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("the execution %d not found", schedule.ID)
|
||||
}
|
||||
|
||||
// Delete ...
|
||||
func (f *FakeSchedulerManager) Delete(id int64) error {
|
||||
length := len(f.Schedules)
|
||||
for i, sch := range f.Schedules {
|
||||
if sch.ID == id {
|
||||
f.Schedules = f.Schedules[:i]
|
||||
if i != length-1 {
|
||||
f.Schedules = append(f.Schedules, f.Schedules[i+1:]...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("the execution %d not found", id)
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (f *FakeSchedulerManager) Get(id int64) (*model.Schedule, error) {
|
||||
for _, sch := range f.Schedules {
|
||||
if sch.ID == id {
|
||||
return sch, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("the execution %d not found", id)
|
||||
}
|
||||
|
||||
// List ...
|
||||
func (f *FakeSchedulerManager) List(...*model.ScheduleQuery) ([]*model.Schedule, error) {
|
||||
return f.Schedules, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user