support tt automation by job (#370)

* support tt automation by job

* format

* import order

Co-authored-by: dujiashu <dujiashu@didiglobal.com>
master
dujiashu 4 years ago committed by GitHub
parent 10fef82225
commit 6f999f6a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,6 +24,8 @@ require (
github.com/influxdata/influxdb v1.8.0
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-sqlite3 v1.14.0 // indirect
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/open-falcon/rrdlite v0.0.0-20200214140804-bf5829f786ad
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect

@ -9,10 +9,12 @@ import (
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/net/httplib"
"github.com/toolkits/pkg/slice"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/job/config"
"github.com/didi/nightingale/src/common/address"
)
type taskForm struct {
@ -563,7 +565,10 @@ func taskCallback(c *gin.Context) {
// 这个数据结构是tt回调的时候使用的通用数据结构里边既有工单基本信息也有结构化数据job这里只需要从中解析出结构化数据
type ttForm struct {
Id int64 `json:"id" binding:"required"`
RunUser string `json:"runUser" binding:"required"`
Form map[string]interface{} `json:"form" binding:"required"`
Approval int `json:"approval"`
}
// /api/job-ce/run/:id?hosts=10.3.4.5,10.4.5.6
@ -571,6 +576,11 @@ func taskRunForTT(c *gin.Context) {
var f ttForm
bind(c, &f)
action := c.Request.Host + c.Request.URL.Path
if f.Approval == 2 {
renderMessage(c, "该任务未通过审批")
return
}
tpl := TaskTpl(urlParamInt64(c, "id"))
arr, err := tpl.Hosts()
dangerous(err)
@ -587,13 +597,14 @@ func taskRunForTT(c *gin.Context) {
arr = tmp
}
} else {
// TODO 从结构化数据中取hosts
hosts = "xyz"
hosts = strings.ReplaceAll(hosts, "\r", ",")
hosts = strings.ReplaceAll(hosts, "\n", ",")
tmp := cleanHosts(strings.Split(hosts, ","))
if len(tmp) > 0 {
arr = tmp
if v, ok := f.Form["hosts"]; ok {
hosts = v.(string)
hosts = strings.ReplaceAll(hosts, "\r", ",")
hosts = strings.ReplaceAll(hosts, "\n", ",")
tmp := cleanHosts(strings.Split(hosts, ","))
if len(tmp) > 0 {
arr = tmp
}
}
}
@ -616,9 +627,144 @@ func taskRunForTT(c *gin.Context) {
Creator: user.Username,
}
// TODO 把结构化数据转换为脚本命令行参数
task.Args = ""
for k, v := range f.Form {
switch v.(type) {
case string:
if k == "hosts" {
tmp := v.(string)
tmp = strings.ReplaceAll(tmp, "\r", ",")
tmp = strings.ReplaceAll(tmp, "\n", ",")
tmpArray := cleanHosts(strings.Split(hosts, ","))
if len(tmpArray) > 0 {
v = strings.Join(tmpArray, ",")
}
}
if len(v.(string)) < 1600 {
task.Args += fmt.Sprintf("--%s=%s,,", k, v.(string))
}
case int:
task.Args += fmt.Sprintf("--%s=%d,,", k, v.(int))
case int64:
task.Args += fmt.Sprintf("--%s=%d,,", k, v.(int64))
case float64:
//TODO 暂时不支持传非整型
task.Args += fmt.Sprintf("--%s=%d,,", k, int64(v.(float64)))
}
}
task.Args = strings.TrimSuffix(task.Args, ",,")
dangerous(task.Save(arr, "start"))
renderData(c, task.Id, nil)
go func() {
for {
var (
restHosts []string
)
for _, h := range arr {
th, err := models.TaskHostGet(task.Id, h)
if err == nil {
if th.Status == "killed" {
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行失败"+
"原因为task被kill掉\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
task.Id, h, action, th.Stdout, th.Stderr)
err = TicketSender(f.Id, action, "task has been killed", reply, -1,
nil)
if err != nil {
logger.Errorf("send callback to ticket, err: %v", err)
}
} else if th.Status == "failed" {
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行失败"+
"详情见错误输出\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
task.Id, h, action, th.Stdout, th.Stderr)
err = TicketSender(f.Id, action, "run task failed", reply, -1,
nil)
if err != nil {
logger.Errorf("send callback to ticket, err: %v", err)
}
} else if th.Status == "timeout" {
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行超时"+
"\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
task.Id, h, action, th.Stdout, th.Stderr)
err = TicketSender(f.Id, action, "run task failed", reply, -1,
nil)
if err != nil {
logger.Errorf("send callback to ticket, err: %v", err)
}
} else if th.Status == "success" {
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行成功"+
"\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
task.Id, h, action, th.Stdout, th.Stderr)
err = TicketSender(f.Id, action, "task ", reply, 1,
nil)
if err != nil {
logger.Errorf("send callback to ticket, err: %v", err)
}
} else {
restHosts = append(restHosts, h)
}
} else {
logger.Errorf("get task_host err: %v", err)
}
}
arr = restHosts
time.Sleep(time.Second)
}
}()
go func() {
time.Sleep(time.Second)
reply := fmt.Sprintf("[任务详情请关注Job平台任务(ID:%d)详情页地址](%s)", task.Id, fmt.Sprintf("/job/tasks/%d/result", task.Id))
err = TicketSender(f.Id, action, "", reply, -1,
nil)
if err != nil {
logger.Errorf("send callback to ticket, err: %v", err)
}
}()
renderData(c, gin.H{"taskID": task.Id, "detailPage": fmt.Sprintf("/job/tasks/%d/result", task.Id)}, nil)
}
type ticketCallBackForm struct {
TicketId int64 `json:"ticketId" binding:"required"`
ActionApi string `json:"actionApi" binding:"required"`
SystemName string `json:"systemName" binding:"required"`
Success int `json:"success" binding:"required"`
Reason string `json:"reason"`
Info interface{} `json:"info"`
AutoReply string `json:"autoReply"`
}
func TicketSender(id int64, action, reason, reply string, result int, info interface{}) error {
addr := address.GetHTTPListen("ticket")
data := ticketCallBackForm{
TicketId: id,
ActionApi: action,
Success: result,
Reason: reason,
Info: info,
AutoReply: reply,
}
url := fmt.Sprintf("%s/v1/ticket/callback?systemName=job", addr)
if !(strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) {
url = "http://" + url
}
res, code, err := httplib.PostJSON(url, time.Second*5, data, map[string]string{"x-srv-token": "ticket-builtin-token"})
if err != nil {
logger.Errorf("call sender api failed, server: %v, data: %+v, err: %v, resp:%v, status code:%d", url, data, err, string(res), code)
return err
}
if code != 200 {
logger.Errorf("call sender api failed, server: %v, data: %+v, resp:%v, code:%d", url, data, string(res), code)
return err
}
logger.Debugf("ticket response %s", string(res))
return nil
}

Loading…
Cancel
Save