mirror of
https://github.com/astaxie/beego.git
synced 2024-11-24 14:20:55 +00:00
design Command for governor module & decouple web module from task module
This commit is contained in:
parent
089006525e
commit
44127edefc
87
pkg/infrastructure/governor/command.go
Normal file
87
pkg/infrastructure/governor/command.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2020
|
||||||
|
//
|
||||||
|
// 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 governor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command is an experimental interface
|
||||||
|
// We try to use this to decouple modules
|
||||||
|
// All other modules depends on this, and they register the command they support
|
||||||
|
// We may change the API in the future, so be careful about this.
|
||||||
|
type Command interface {
|
||||||
|
Execute(params ...interface{}) *Result
|
||||||
|
}
|
||||||
|
|
||||||
|
var CommandNotFound = errors.New("Command not found")
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
// Status is the same as http.Status
|
||||||
|
Status int
|
||||||
|
Error error
|
||||||
|
Content interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) IsSuccess() bool {
|
||||||
|
return r.Status >= 200 && r.Status < 300
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandRegistry stores all commands
|
||||||
|
// name => command
|
||||||
|
type moduleCommands map[string]Command
|
||||||
|
|
||||||
|
// Get returns command with the name
|
||||||
|
func (m moduleCommands) Get(name string) Command {
|
||||||
|
c, ok := m[name]
|
||||||
|
if ok {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &doNothingCommand{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// module name => moduleCommand
|
||||||
|
type commandRegistry map[string]moduleCommands
|
||||||
|
|
||||||
|
// Get returns module's commands
|
||||||
|
func (c commandRegistry) Get(moduleName string) moduleCommands {
|
||||||
|
if mcs, ok := c[moduleName]; ok {
|
||||||
|
return mcs
|
||||||
|
}
|
||||||
|
res := make(moduleCommands)
|
||||||
|
c[moduleName] = res
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdRegistry = make(commandRegistry)
|
||||||
|
|
||||||
|
// RegisterCommand is not thread-safe
|
||||||
|
// do not use it in concurrent case
|
||||||
|
func RegisterCommand(module string, commandName string, command Command) {
|
||||||
|
cmdRegistry.Get(module)[commandName] = command
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCommand(module string, cmdName string) Command {
|
||||||
|
return cmdRegistry.Get(module).Get(cmdName)
|
||||||
|
}
|
||||||
|
|
||||||
|
type doNothingCommand struct{}
|
||||||
|
|
||||||
|
func (d *doNothingCommand) Execute(params ...interface{}) *Result {
|
||||||
|
return &Result{
|
||||||
|
Status: 404,
|
||||||
|
Error: CommandNotFound,
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/pkg/infrastructure/logs"
|
"github.com/astaxie/beego/pkg/infrastructure/logs"
|
||||||
"github.com/astaxie/beego/pkg/task"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeeAdminApp is the default adminApp used by admin module.
|
// BeeAdminApp is the default adminApp used by admin module.
|
||||||
@ -86,9 +85,12 @@ type adminApp struct {
|
|||||||
// Route adds http.HandlerFunc to adminApp with url pattern.
|
// Route adds http.HandlerFunc to adminApp with url pattern.
|
||||||
func (admin *adminApp) Run() {
|
func (admin *adminApp) Run() {
|
||||||
|
|
||||||
if len(task.AdminTaskList) > 0 {
|
// if len(task.AdminTaskList) > 0 {
|
||||||
task.StartTask()
|
// task.StartTask()
|
||||||
}
|
// }
|
||||||
|
logs.Warning("now we don't start tasks here, if you use task module," +
|
||||||
|
" please invoke task.StartTask, or task will not be executed")
|
||||||
|
|
||||||
addr := BConfig.Listen.AdminAddr
|
addr := BConfig.Listen.AdminAddr
|
||||||
|
|
||||||
if BConfig.Listen.AdminPort != 0 {
|
if BConfig.Listen.AdminPort != 0 {
|
||||||
|
@ -16,7 +16,6 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
context2 "context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -26,7 +25,6 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
"github.com/astaxie/beego/pkg/infrastructure/governor"
|
"github.com/astaxie/beego/pkg/infrastructure/governor"
|
||||||
"github.com/astaxie/beego/pkg/task"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type adminController struct {
|
type adminController struct {
|
||||||
@ -90,19 +88,22 @@ func (a *adminController) TaskStatus() {
|
|||||||
req.ParseForm()
|
req.ParseForm()
|
||||||
taskname := req.Form.Get("taskname")
|
taskname := req.Form.Get("taskname")
|
||||||
if taskname != "" {
|
if taskname != "" {
|
||||||
if t, ok := task.AdminTaskList[taskname]; ok {
|
cmd := governor.GetCommand("task", "run")
|
||||||
if err := t.Run(nil); err != nil {
|
res := cmd.Execute(taskname)
|
||||||
data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", err))}
|
if res.IsSuccess() {
|
||||||
}
|
|
||||||
data["Message"] = []string{"success", template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus(nil)))}
|
data["Message"] = []string{"success",
|
||||||
|
template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is <br>%s",
|
||||||
|
taskname, res.Content.(string)))}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
data["Message"] = []string{"warning", template.HTMLEscapeString(fmt.Sprintf("there's no task which named: %s", taskname))}
|
data["Message"] = []string{"error", template.HTMLEscapeString(fmt.Sprintf("%s", res.Error))}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List Tasks
|
// List Tasks
|
||||||
content := make(M)
|
content := make(M)
|
||||||
resultList := new([][]string)
|
resultList := governor.GetCommand("task", "list").Execute().Content.([][]string)
|
||||||
var fields = []string{
|
var fields = []string{
|
||||||
"Task Name",
|
"Task Name",
|
||||||
"Task Spec",
|
"Task Spec",
|
||||||
@ -110,15 +111,6 @@ func (a *adminController) TaskStatus() {
|
|||||||
"Last Time",
|
"Last Time",
|
||||||
"",
|
"",
|
||||||
}
|
}
|
||||||
for tname, tk := range task.AdminTaskList {
|
|
||||||
result := []string{
|
|
||||||
template.HTMLEscapeString(tname),
|
|
||||||
template.HTMLEscapeString(tk.GetSpec(nil)),
|
|
||||||
template.HTMLEscapeString(tk.GetStatus(nil)),
|
|
||||||
template.HTMLEscapeString(tk.GetPrev(context2.Background()).String()),
|
|
||||||
}
|
|
||||||
*resultList = append(*resultList, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
content["Fields"] = fields
|
content["Fields"] = fields
|
||||||
content["Data"] = resultList
|
content["Data"] = resultList
|
||||||
|
92
pkg/task/govenor_command.go
Normal file
92
pkg/task/govenor_command.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2020
|
||||||
|
//
|
||||||
|
// 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 task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/governor"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listTaskCommand struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *listTaskCommand) Execute(params ...interface{}) *governor.Result {
|
||||||
|
resultList := make([][]string, 0, len(AdminTaskList))
|
||||||
|
for tname, tk := range AdminTaskList {
|
||||||
|
result := []string{
|
||||||
|
template.HTMLEscapeString(tname),
|
||||||
|
template.HTMLEscapeString(tk.GetSpec(nil)),
|
||||||
|
template.HTMLEscapeString(tk.GetStatus(nil)),
|
||||||
|
template.HTMLEscapeString(tk.GetPrev(context.Background()).String()),
|
||||||
|
}
|
||||||
|
resultList = append(resultList, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &governor.Result{
|
||||||
|
Status: 200,
|
||||||
|
Content: resultList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type runTaskCommand struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runTaskCommand) Execute(params ...interface{}) *governor.Result {
|
||||||
|
if len(params) == 0 {
|
||||||
|
return &governor.Result{
|
||||||
|
Status: 400,
|
||||||
|
Error: errors.New("task name not passed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tn, ok := params[0].(string)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return &governor.Result{
|
||||||
|
Status: 400,
|
||||||
|
Error: errors.New("parameter is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := AdminTaskList[tn]; ok {
|
||||||
|
err := t.Run(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return &governor.Result{
|
||||||
|
Status: 500,
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &governor.Result{
|
||||||
|
Status: 200,
|
||||||
|
Content: t.GetStatus(context.Background()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &governor.Result{
|
||||||
|
Status: 400,
|
||||||
|
Error: errors.New(fmt.Sprintf("task with name %s not found", tn)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerCommands() {
|
||||||
|
governor.RegisterCommand("task", "list", &listTaskCommand{})
|
||||||
|
governor.RegisterCommand("task", "run", &runTaskCommand{})
|
||||||
|
}
|
111
pkg/task/governor_command_test.go
Normal file
111
pkg/task/governor_command_test.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2020
|
||||||
|
//
|
||||||
|
// 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 task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type countTask struct {
|
||||||
|
cnt int
|
||||||
|
mockErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countTask) GetSpec(ctx context.Context) string {
|
||||||
|
return "AAA"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countTask) GetStatus(ctx context.Context) string {
|
||||||
|
return "SUCCESS"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countTask) Run(ctx context.Context) error {
|
||||||
|
c.cnt++
|
||||||
|
return c.mockErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countTask) SetNext(ctx context.Context, time time.Time) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countTask) GetNext(ctx context.Context) time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countTask) SetPrev(ctx context.Context, time time.Time) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countTask) GetPrev(ctx context.Context) time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunTaskCommand_Execute(t *testing.T) {
|
||||||
|
task := &countTask{}
|
||||||
|
AddTask("count", task)
|
||||||
|
|
||||||
|
cmd := &runTaskCommand{}
|
||||||
|
|
||||||
|
res := cmd.Execute()
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotNil(t, res.Error)
|
||||||
|
assert.Equal(t, "task name not passed", res.Error.Error())
|
||||||
|
|
||||||
|
res = cmd.Execute(10)
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotNil(t, res.Error)
|
||||||
|
assert.Equal(t, "parameter is invalid", res.Error.Error())
|
||||||
|
|
||||||
|
res = cmd.Execute("CCCC")
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotNil(t, res.Error)
|
||||||
|
assert.Equal(t, "task with name CCCC not found", res.Error.Error())
|
||||||
|
|
||||||
|
res = cmd.Execute("count")
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.True(t, res.IsSuccess())
|
||||||
|
|
||||||
|
task.mockErr = errors.New("mock error")
|
||||||
|
res = cmd.Execute("count")
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotNil(t, res.Error)
|
||||||
|
assert.Equal(t, "mock error", res.Error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListTaskCommand_Execute(t *testing.T) {
|
||||||
|
task := &countTask{}
|
||||||
|
|
||||||
|
cmd := &listTaskCommand{}
|
||||||
|
|
||||||
|
res := cmd.Execute()
|
||||||
|
|
||||||
|
assert.True(t, res.IsSuccess())
|
||||||
|
|
||||||
|
_, ok := res.Content.([][]string)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
AddTask("count", task)
|
||||||
|
|
||||||
|
res = cmd.Execute()
|
||||||
|
|
||||||
|
assert.True(t, res.IsSuccess())
|
||||||
|
|
||||||
|
rl, ok := res.Content.([][]string)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, 1, len(rl))
|
||||||
|
}
|
@ -189,9 +189,9 @@ func (t *Task) GetPrev(context.Context) time.Time {
|
|||||||
// SetCron some signals:
|
// SetCron some signals:
|
||||||
// *: any time
|
// *: any time
|
||||||
// ,: separate signal
|
// ,: separate signal
|
||||||
// -:duration
|
// -:duration
|
||||||
// /n : do as n times of time duration
|
// /n : do as n times of time duration
|
||||||
/////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////
|
||||||
// 0/30 * * * * * every 30s
|
// 0/30 * * * * * every 30s
|
||||||
// 0 43 21 * * * 21:43
|
// 0 43 21 * * * 21:43
|
||||||
// 0 15 05 * * * 05:15
|
// 0 15 05 * * * 05:15
|
||||||
@ -401,10 +401,12 @@ func StartTask() {
|
|||||||
taskLock.Lock()
|
taskLock.Lock()
|
||||||
defer taskLock.Unlock()
|
defer taskLock.Unlock()
|
||||||
if isstart {
|
if isstart {
|
||||||
//If already started, no need to start another goroutine.
|
// If already started, no need to start another goroutine.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isstart = true
|
isstart = true
|
||||||
|
|
||||||
|
registerCommands()
|
||||||
go run()
|
go run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user