2017-01-09 03:10:55 +00:00
|
|
|
package models
|
2017-01-09 02:32:14 +00:00
|
|
|
|
2017-01-12 07:34:13 +00:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
2017-01-19 11:03:18 +00:00
|
|
|
"fmt"
|
2017-02-11 09:59:19 +00:00
|
|
|
"os/exec"
|
2017-01-12 08:35:30 +00:00
|
|
|
"strings"
|
2017-01-12 07:34:13 +00:00
|
|
|
|
|
|
|
client "github.com/coreos/etcd/clientv3"
|
2017-01-19 11:03:18 +00:00
|
|
|
"github.com/coreos/etcd/mvcc/mvccpb"
|
2017-01-12 07:34:13 +00:00
|
|
|
|
|
|
|
"sunteng/commons/log"
|
|
|
|
"sunteng/cronsun/conf"
|
2017-02-13 01:48:02 +00:00
|
|
|
"time"
|
2017-01-12 07:34:13 +00:00
|
|
|
)
|
|
|
|
|
2017-01-12 08:35:30 +00:00
|
|
|
const (
|
2017-01-21 10:16:45 +00:00
|
|
|
DefaultJobGroup = "default"
|
2017-01-12 08:35:30 +00:00
|
|
|
)
|
|
|
|
|
2017-01-09 09:13:56 +00:00
|
|
|
// 需要执行的 cron cmd 命令
|
2017-01-12 08:35:30 +00:00
|
|
|
// 注册到 /cronsun/cmd/groupName/<id>
|
2017-01-09 02:32:14 +00:00
|
|
|
type Job struct {
|
2017-01-12 08:35:30 +00:00
|
|
|
ID string `json:"id"`
|
2017-01-11 08:12:37 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Group string `json:"group"`
|
|
|
|
Command string `json:"cmd"`
|
2017-02-16 04:00:31 +00:00
|
|
|
User string `json:"user"`
|
2017-02-09 06:16:15 +00:00
|
|
|
Rules []*JobRule `json:"rules"`
|
2017-01-16 06:30:55 +00:00
|
|
|
Pause bool `json:"pause"` // 可手工控制的状态
|
2017-01-12 07:34:13 +00:00
|
|
|
|
2017-01-20 04:26:02 +00:00
|
|
|
// node 服务使用
|
2017-01-17 08:40:12 +00:00
|
|
|
// 每个任务在单个结点上只支持一个时间规则
|
|
|
|
// 如果需要多个时间规则,需建新的任务
|
2017-01-20 04:26:02 +00:00
|
|
|
schedule string
|
2017-01-20 09:11:26 +00:00
|
|
|
gid string
|
2017-01-20 04:26:02 +00:00
|
|
|
build bool
|
2017-02-13 01:48:02 +00:00
|
|
|
|
|
|
|
// 执行任务的结点,用于记录 job log
|
|
|
|
runOn string
|
2017-01-09 02:32:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type JobRule struct {
|
2017-01-11 08:12:37 +00:00
|
|
|
Timer string `json:"timer"`
|
2017-01-09 09:13:56 +00:00
|
|
|
GroupIDs []string `json:"gids"`
|
2017-01-11 08:12:37 +00:00
|
|
|
NodeIDs []string `json:"nids"`
|
|
|
|
ExcludeNodeIDs []string `json:"exclude_nids"`
|
2017-01-09 02:32:14 +00:00
|
|
|
}
|
2017-01-12 07:34:13 +00:00
|
|
|
|
2017-01-20 09:11:26 +00:00
|
|
|
func (j *JobRule) included(nid string, gs map[string]*Group) (string, bool) {
|
2017-01-20 04:26:02 +00:00
|
|
|
for _, gid := range j.GroupIDs {
|
|
|
|
if _, ok := gs[gid]; ok {
|
2017-01-20 09:11:26 +00:00
|
|
|
return gid, true
|
2017-01-20 04:26:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, count := 0, len(j.NodeIDs); i < count; i++ {
|
|
|
|
if nid == j.NodeIDs[i] {
|
2017-01-20 09:11:26 +00:00
|
|
|
return "", true
|
2017-01-20 04:26:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-20 09:11:26 +00:00
|
|
|
return "", false
|
2017-01-20 04:26:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-16 06:30:55 +00:00
|
|
|
func GetJob(group, id string) (job *Job, err error) {
|
2017-01-22 07:18:08 +00:00
|
|
|
job, _, err = GetJobAndRev(group, id)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetJobAndRev(group, id string) (job *Job, rev int64, err error) {
|
2017-01-16 06:30:55 +00:00
|
|
|
resp, err := DefalutClient.Get(JobKey(group, id))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Count == 0 {
|
|
|
|
err = ErrNotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-22 07:18:08 +00:00
|
|
|
rev = resp.Kvs[0].ModRevision
|
2017-01-16 06:30:55 +00:00
|
|
|
err = json.Unmarshal(resp.Kvs[0].Value, &job)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteJob(group, id string) (resp *client.DeleteResponse, err error) {
|
|
|
|
return DefalutClient.Delete(JobKey(group, id))
|
|
|
|
}
|
|
|
|
|
2017-01-12 07:34:13 +00:00
|
|
|
func GetJobs() (jobs map[string]*Job, err error) {
|
|
|
|
resp, err := DefalutClient.Get(conf.Config.Cmd, client.WithPrefix())
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
count := len(resp.Kvs)
|
|
|
|
if count == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
jobs = make(map[string]*Job, count)
|
|
|
|
for _, j := range resp.Kvs {
|
|
|
|
job := new(Job)
|
|
|
|
if e := json.Unmarshal(j.Value, job); e != nil {
|
|
|
|
log.Warnf("job[%s] umarshal err: %s", string(j.Key), e.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
jobs[job.ID] = job
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-19 11:03:18 +00:00
|
|
|
func WatchJobs() client.WatchChan {
|
2017-02-11 09:59:19 +00:00
|
|
|
return DefalutClient.Watch(conf.Config.Cmd, client.WithPrefix(), client.WithPrevKV())
|
2017-01-19 11:03:18 +00:00
|
|
|
}
|
|
|
|
|
2017-01-20 09:11:26 +00:00
|
|
|
func GetJobFromKv(kv *mvccpb.KeyValue) (job *Job, err error) {
|
2017-01-19 11:03:18 +00:00
|
|
|
job = new(Job)
|
|
|
|
if err = json.Unmarshal(kv.Value, job); err != nil {
|
|
|
|
err = fmt.Errorf("job[%s] umarshal err: %s", string(kv.Key), err.Error())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-20 09:42:50 +00:00
|
|
|
// Schedule return schedule and group id
|
|
|
|
func (j *Job) Schedule(nid string, gs map[string]*Group, rebuild bool) (sch string, gid string) {
|
2017-01-20 04:26:02 +00:00
|
|
|
if j.Pause {
|
2017-01-20 09:42:50 +00:00
|
|
|
return
|
2017-01-20 04:26:02 +00:00
|
|
|
}
|
2017-01-12 07:34:13 +00:00
|
|
|
|
2017-01-20 09:11:26 +00:00
|
|
|
if j.build && !rebuild {
|
|
|
|
return j.schedule, j.gid
|
2017-01-20 04:26:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
j.buildSchedule(nid, gs)
|
2017-01-20 09:11:26 +00:00
|
|
|
return j.schedule, j.gid
|
2017-01-20 04:26:02 +00:00
|
|
|
}
|
2017-01-12 07:34:13 +00:00
|
|
|
|
2017-01-20 04:26:02 +00:00
|
|
|
func (j *Job) buildSchedule(nid string, gs map[string]*Group) {
|
2017-01-20 09:11:26 +00:00
|
|
|
j.build = true
|
2017-02-09 06:16:15 +00:00
|
|
|
for _, r := range j.Rules {
|
2017-01-12 07:34:13 +00:00
|
|
|
for _, id := range r.ExcludeNodeIDs {
|
2017-01-20 04:26:02 +00:00
|
|
|
if nid == id {
|
|
|
|
return
|
|
|
|
}
|
2017-01-12 07:34:13 +00:00
|
|
|
}
|
|
|
|
|
2017-01-20 09:11:26 +00:00
|
|
|
if gid, ok := r.included(nid, gs); ok {
|
|
|
|
j.schedule, j.gid = r.Timer, gid
|
2017-01-20 04:26:02 +00:00
|
|
|
return
|
|
|
|
}
|
2017-01-12 07:34:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-17 09:06:23 +00:00
|
|
|
func (j *Job) GetID() string {
|
|
|
|
return j.ID
|
|
|
|
}
|
|
|
|
|
2017-02-13 01:48:02 +00:00
|
|
|
func (j *Job) RunOn(n string) {
|
|
|
|
j.runOn = n
|
|
|
|
}
|
|
|
|
|
2017-02-11 09:59:19 +00:00
|
|
|
func (j *Job) String() string {
|
|
|
|
data, err := json.Marshal(j)
|
|
|
|
if err != nil {
|
|
|
|
return err.Error()
|
|
|
|
}
|
|
|
|
return string(data)
|
|
|
|
}
|
|
|
|
|
2017-02-13 00:53:41 +00:00
|
|
|
// Run 执行任务
|
2017-01-12 07:34:13 +00:00
|
|
|
func (j *Job) Run() {
|
2017-02-16 04:00:31 +00:00
|
|
|
t := time.Now()
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
if len(j.User) > 0 {
|
|
|
|
if needPassword {
|
2017-02-16 07:05:59 +00:00
|
|
|
j.Fail(t, SudoErr.Error())
|
2017-02-16 04:00:31 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
cmd = exec.Command("sudo", "su", j.User, "-c", j.Command)
|
|
|
|
} else {
|
|
|
|
args := strings.Split(j.Command, " ")
|
|
|
|
cmd = exec.Command(args[0], args[1:]...)
|
|
|
|
}
|
|
|
|
|
2017-02-16 07:05:59 +00:00
|
|
|
out, err := cmd.CombinedOutput()
|
2017-02-11 09:59:19 +00:00
|
|
|
if err != nil {
|
2017-02-16 07:05:59 +00:00
|
|
|
j.Fail(t, fmt.Sprintf("%s\n\n%s", err.Error(), string(out)))
|
2017-02-11 09:59:19 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-02-16 07:05:59 +00:00
|
|
|
j.Success(t, string(out))
|
2017-01-12 08:35:30 +00:00
|
|
|
}
|
|
|
|
|
2017-01-16 06:30:55 +00:00
|
|
|
func JobKey(group, id string) string {
|
|
|
|
return conf.Config.Cmd + group + "/" + id
|
|
|
|
}
|
2017-01-12 08:35:30 +00:00
|
|
|
|
|
|
|
func (j *Job) Key() string {
|
2017-01-16 06:30:55 +00:00
|
|
|
return JobKey(j.Group, j.ID)
|
2017-01-12 08:35:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (j *Job) Check() error {
|
2017-01-18 08:58:47 +00:00
|
|
|
j.ID = strings.TrimSpace(j.ID)
|
|
|
|
if !IsValidAsKeyPath(j.ID) {
|
|
|
|
return ErrIllegalJobId
|
|
|
|
}
|
|
|
|
|
2017-01-12 08:35:30 +00:00
|
|
|
j.Name = strings.TrimSpace(j.Name)
|
|
|
|
if len(j.Name) == 0 {
|
|
|
|
return ErrEmptyJobName
|
|
|
|
}
|
|
|
|
|
|
|
|
j.Group = strings.TrimSpace(j.Group)
|
|
|
|
if len(j.Group) == 0 {
|
|
|
|
j.Group = DefaultJobGroup
|
|
|
|
}
|
|
|
|
|
2017-01-18 08:58:47 +00:00
|
|
|
if !IsValidAsKeyPath(j.Group) {
|
|
|
|
return ErrIllegalJobGroupName
|
|
|
|
}
|
|
|
|
|
2017-02-16 06:42:28 +00:00
|
|
|
j.User = strings.TrimSpace(j.User)
|
|
|
|
|
2017-01-12 08:35:30 +00:00
|
|
|
// 不修改 Command 的内容,简单判断是否为空
|
|
|
|
if len(strings.TrimSpace(j.Command)) == 0 {
|
|
|
|
return ErrEmptyJobCommand
|
|
|
|
}
|
2017-01-12 07:34:13 +00:00
|
|
|
|
2017-01-12 08:35:30 +00:00
|
|
|
return nil
|
2017-01-12 07:34:13 +00:00
|
|
|
}
|
2017-02-13 00:53:41 +00:00
|
|
|
|
|
|
|
// 执行结果写入 mongoDB
|
2017-02-16 07:05:59 +00:00
|
|
|
func (j *Job) Success(t time.Time, out string) {
|
|
|
|
CreateJobLog(j, t, out, true)
|
2017-02-13 00:53:41 +00:00
|
|
|
}
|
|
|
|
|
2017-02-16 07:05:59 +00:00
|
|
|
func (j *Job) Fail(t time.Time, msg string) {
|
|
|
|
CreateJobLog(j, t, msg, false)
|
2017-02-13 00:53:41 +00:00
|
|
|
}
|