common
Constants.go
package common
const (
// 任务保存目录
JOB_SAVE_DIR = "/cron/jobs/"
// 任务强杀目录
JOB_KILLER_DIR = "/cron/killer/"
// 任务锁目录
JOB_LOCK_DIR = "/cron/lock/"
// 服务注册目录
JOB_WORKER_DIR = "/cron/workers/"
// 保存任务事件
JOB_EVENT_SAVE = 1
// 删除任务事件
JOB_EVENT_DELETE = 2
// 强杀任务事件
JOB_EVENT_KILL = 3
)
Errors.go
package common
import "errors"
var (
ERR_LOCK_ALREADY_REQUIRED = errors.New("锁已被占用")
ERR_NO_LOCAL_IP_FOUND = errors.New("没有找到网卡IP")
)
Protocol.go
package common
import (
"encoding/json"
"strings"
"github.com/gorhill/cronexpr"
"time"
"context"
)
// 定时任务
type Job struct {
Name string `json:"name"` // 任务名
Command string `json:"command"` // shell命令
CronExpr string `json:"cronExpr"` // cron表达式
}
// 任务调度计划
type JobSchedulePlan struct {
Job *Job // 要调度的任务信息
Expr *cronexpr.Expression // 解析好的cronexpr表达式
NextTime time.Time // 下次调度时间
}
// 任务执行状态
type JobExecuteInfo struct {
Job *Job // 任务信息
PlanTime time.Time // 理论上的调度时间
RealTime time.Time // 实际的调度时间
CancelCtx context.Context // 任务command的context
CancelFunc context.CancelFunc// 用于取消command执行的cancel函数
}
// HTTP接口应答
type Response struct {
Errno int `json:"errno"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 变化事件
type JobEvent struct {
EventType int // SAVE, DELETE
Job *Job
}
// 任务执行结果
type JobExecuteResult struct {
ExecuteInfo *JobExecuteInfo // 执行状态
Output []byte // 脚本输出
Err error // 脚本错误原因
StartTime time.Time // 启动时间
EndTime time.Time // 结束时间
}
// 任务执行日志
type JobLog struct {
JobName string `json:"jobName" bson:"jobName"` // 任务名字
Command string `json:"command" bson:"command"` // 脚本命令
Err string `json:"err" bson:"err"` // 错误原因
Output string `json:"output" bson:"output"` // 脚本输出
PlanTime int64 `json:"planTime" bson:"planTime"` // 计划开始时间
ScheduleTime int64 `json:"scheduleTime" bson:"scheduleTime"` // 实际调度时间
StartTime int64 `json:"startTime" bson:"startTime"` // 任务执行开始时间
EndTime int64 `json:"endTime" bson:"endTime"` // 任务执行结束时间
}
// 日志批次
type LogBatch struct {
Logs []interface{} // 多条日志
}
// 任务日志过滤条件
type JobLogFilter struct {
JobName string `bson:"jobName"`
}
// 任务日志排序规则
type SortLogByStartTime struct {
SortOrder int `bson:"startTime"` // {startTime: -1}
}
// 应答方法
func BuildResponse(errno int, msg string, data interface{}) (resp []byte, err error) {
// 1, 定义一个response
var (
response Response
)
response.Errno = errno
response.Msg = msg
response.Data = data
// 2, 序列化json
resp, err = json.Marshal(response)
return
}
// 反序列化Job
func UnpackJob(value []byte) (ret *Job, err error) {
var (
job *Job
)
job = &Job{}
if err = json.Unmarshal(value, job); err != nil {
return
}
ret = job
return
}
// 从etcd的key中提取任务名
// /cron/jobs/job10抹掉/cron/jobs/
func ExtractJobName(jobKey string) (string) {
return strings.TrimPrefix(jobKey, JOB_SAVE_DIR)
}
// 从 /cron/killer/job10提取job10
func ExtractKillerName(killerKey string) (string) {
return strings.TrimPrefix(killerKey, JOB_KILLER_DIR)
}
// 任务变化事件有2种:1)更新任务 2)删除任务
func BuildJobEvent(eventType int, job *Job) (jobEvent *JobEvent) {
return &JobEvent{
EventType: eventType,
Job: job,
}
}
// 构造任务执行计划
func BuildJobSchedulePlan(job *Job) (jobSchedulePlan *JobSchedulePlan, err error) {
var (
expr *cronexpr.Expression
)
// 解析JOB的cron表达式
if expr, err = cronexpr.Parse(job.CronExpr); err != nil {
return
}
// 生成任务调度计划对象
jobSchedulePlan = &JobSchedulePlan{
Job: job,
Expr: expr,
NextTime: expr.Next(time.Now()),
}
return
}
// 构造执行状态信息
func BuildJobExecuteInfo(jobSchedulePlan *JobSchedulePlan) (jobExecuteInfo *JobExecuteInfo){
jobExecuteInfo = &JobExecuteInfo{
Job: jobSchedulePlan.Job,
PlanTime: jobSchedulePlan.NextTime, // 计算调度时间
RealTime: time.Now(), // 真实调度时间
}
jobExecuteInfo.CancelCtx, jobExecuteInfo.CancelFunc = context.WithCancel(context.TODO())
return
}
// 提取worker的IP
func ExtractWorkerIP(regKey string) (string) {
return strings.TrimPrefix(regKey, JOB_WORKER_DIR)
}
master
ApiServer.go
package master
import (
"net/http"
"net"
"time"
"strconv"
"github.com/owenliang/crontab/common"
"encoding/json"
)
// 任务的HTTP接口
type ApiServer struct {
httpServer *http.Server
}
var (
// 单例对象
G_apiServer *ApiServer
)
// 保存任务接口
// POST job={"name": "job1", "command": "echo hello", "cronExpr": "* * * * *"}
func handleJobSave(resp http.ResponseWriter, req *http.Request) {
var (
err error
postJob string
job common.Job
oldJob *common.Job
bytes []byte
)
// 1, 解析POST表单
if err = req.ParseForm(); err != nil {
goto ERR
}
// 2, 取表单中的job字段
postJob = req.PostForm.Get("job")
// 3, 反序列化job
if err = json.Unmarshal([]byte(postJob), &job); err != nil {
goto ERR
}
// 4, 保存到etcd
if oldJob, err = G_jobMgr.SaveJob(&job); err != nil {
goto ERR
}
// 5, 返回正常应答 ({"errno": 0, "msg": "", "data": {....}})
if bytes, err = common.BuildResponse(0, "success", oldJob); err == nil {
resp.Write(bytes)
}
return
ERR:
// 6, 返回异常应答
if bytes, err = common.BuildResponse(-1, err.Error(), nil); err == nil {
resp.Write(bytes)
}
}
// 删除任务接口
// POST /job/delete name=job1
func handleJobDelete(resp http.ResponseWriter, req *http.Request) {
var (
err error // interface{}
name string
oldJob *common.Job
bytes []byte
)
// POST: a=1&b=2&c=3
if err = req.ParseForm(); err != nil {
goto ERR
}
// 删除的任务名
name = req.PostForm.Get("name")
// 去删除任务
if oldJob, err = G_jobMgr.DeleteJob(name); err != nil {
goto ERR
}
// 正常应答
if bytes, err = common.BuildResponse(0, "success", oldJob); err == nil {
resp.Write(bytes)
}
return
ERR:
if bytes, err = common.BuildResponse(-1, err.Error(), nil); err == nil {
resp.Write(bytes)
}
}
// 列举所有crontab任务
func handleJobList(resp http.ResponseWriter, req *http.Request) {
var (
jobList []*common.Job
bytes []byte
err error
)
// 获取任务列表
if jobList, err = G_jobMgr.ListJobs(); err != nil {
goto ERR
}
// 正常应答
if bytes, err = common.BuildResponse(0, "success", jobList); err == nil {
resp.Write(bytes)
}
return
ERR:
if bytes, err = common.BuildResponse(-1, err.Error(), nil); err == nil {
resp.Write(bytes)
}
}
// 强制杀死某个任务
// POST /job/kill name=job1
func handleJobKill(resp http.ResponseWriter, req *http.Request) {
var (
err error
name string
bytes []byte
)
// 解析POST表单
if err = req.ParseForm(); err != nil {
goto ERR
}
// 要杀死的任务名
name = req.PostForm.Get("name")
// 杀死任务
if err = G_jobMgr.KillJob(name); err != nil {
goto ERR
}
// 正常应答
if bytes, err = common.BuildResponse(0, "success", nil); err == nil {
resp.Write(bytes)
}
return
ERR:
if bytes, err = common.BuildResponse(-1, err.Error(), nil); err == nil {
resp.Write(bytes)
}
}
// 查询任务日志
func handleJobLog(resp http.ResponseWriter, req *http.Request) {
var (
err error
name string // 任务名字
skipParam string// 从第几条开始