* Ibex integrate (#1876) --------- Co-authored-by: Deke Wang <94156972+wdkcc@users.noreply.github.com>main
| @@ -46,13 +46,6 @@ type RedisPub struct { | |||
| ChannelKey string | |||
| } | |||
| type Ibex struct { | |||
| Address string | |||
| BasicAuthUser string | |||
| BasicAuthPass string | |||
| Timeout int64 | |||
| } | |||
| func (a *Alert) PreCheck(configDir string) { | |||
| if a.Alerting.TemplatesDir == "" { | |||
| a.Alerting.TemplatesDir = path.Join(configDir, "template") | |||
| @@ -24,7 +24,10 @@ import ( | |||
| "github.com/ccfos/nightingale/v6/prom" | |||
| "github.com/ccfos/nightingale/v6/pushgw/pconf" | |||
| "github.com/ccfos/nightingale/v6/pushgw/writer" | |||
| "github.com/ccfos/nightingale/v6/storage" | |||
| "github.com/ccfos/nightingale/v6/tdengine" | |||
| "github.com/flashcatcloud/ibex/src/cmd/ibex" | |||
| ) | |||
| func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| @@ -40,6 +43,14 @@ func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| ctx := ctx.NewContext(context.Background(), nil, false, config.CenterApi) | |||
| var redis storage.Redis | |||
| if config.Redis.Address != "" { | |||
| redis, err = storage.NewRedis(config.Redis) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| syncStats := memsto.NewSyncStats() | |||
| alertStats := astats.NewSyncStats() | |||
| @@ -52,16 +63,22 @@ func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| dsCache := memsto.NewDatasourceCache(ctx, syncStats) | |||
| userCache := memsto.NewUserCache(ctx, syncStats) | |||
| userGroupCache := memsto.NewUserGroupCache(ctx, syncStats) | |||
| taskTplsCache := memsto.NewTaskTplCache(ctx) | |||
| promClients := prom.NewPromClient(ctx) | |||
| tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat) | |||
| externalProcessors := process.NewExternalProcessors() | |||
| Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache) | |||
| Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, taskTplsCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache) | |||
| r := httpx.GinEngine(config.Global.RunMode, config.HTTP) | |||
| rt := router.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors) | |||
| if config.Ibex.Enable { | |||
| ibex.ServerStart(false, nil, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, nil, config.Ibex, config.HTTP.Port) | |||
| } | |||
| rt.Config(r) | |||
| dumper.ConfigRouter(r) | |||
| @@ -74,7 +91,7 @@ func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| } | |||
| func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, alertStats *astats.Stats, externalProcessors *process.ExternalProcessorsType, targetCache *memsto.TargetCacheType, busiGroupCache *memsto.BusiGroupCacheType, | |||
| alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context, | |||
| alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, taskTplsCache *memsto.TaskTplCache, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context, | |||
| promClients *prom.PromClientMap, tdendgineClients *tdengine.TdengineClientMap, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType) { | |||
| alertSubscribeCache := memsto.NewAlertSubscribeCache(ctx, syncStats) | |||
| recordingRuleCache := memsto.NewRecordingRuleCache(ctx, syncStats) | |||
| @@ -90,7 +107,7 @@ func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, al | |||
| eval.NewScheduler(alertc, externalProcessors, alertRuleCache, targetCache, targetsOfAlertRulesCache, | |||
| busiGroupCache, alertMuteCache, datasourceCache, promClients, tdendgineClients, naming, ctx, alertStats) | |||
| dp := dispatch.NewDispatch(alertRuleCache, userCache, userGroupCache, alertSubscribeCache, targetCache, notifyConfigCache, alertc.Alerting, ctx, alertStats) | |||
| dp := dispatch.NewDispatch(alertRuleCache, userCache, userGroupCache, alertSubscribeCache, targetCache, notifyConfigCache, taskTplsCache, alertc.Alerting, ctx, alertStats) | |||
| consumer := dispatch.NewConsumer(alertc.Alerting, ctx, dp) | |||
| go dp.ReloadTpls() | |||
| @@ -26,6 +26,7 @@ type Dispatch struct { | |||
| alertSubscribeCache *memsto.AlertSubscribeCacheType | |||
| targetCache *memsto.TargetCacheType | |||
| notifyConfigCache *memsto.NotifyConfigCacheType | |||
| taskTplsCache *memsto.TaskTplCache | |||
| alerting aconf.Alerting | |||
| @@ -43,7 +44,7 @@ type Dispatch struct { | |||
| // 创建一个 Notify 实例 | |||
| func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType, | |||
| alertSubscribeCache *memsto.AlertSubscribeCacheType, targetCache *memsto.TargetCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, | |||
| alerting aconf.Alerting, ctx *ctx.Context, astats *astats.Stats) *Dispatch { | |||
| taskTplsCache *memsto.TaskTplCache, alerting aconf.Alerting, ctx *ctx.Context, astats *astats.Stats) *Dispatch { | |||
| notify := &Dispatch{ | |||
| alertRuleCache: alertRuleCache, | |||
| userCache: userCache, | |||
| @@ -51,6 +52,7 @@ func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.Us | |||
| alertSubscribeCache: alertSubscribeCache, | |||
| targetCache: targetCache, | |||
| notifyConfigCache: notifyConfigCache, | |||
| taskTplsCache: taskTplsCache, | |||
| alerting: alerting, | |||
| @@ -241,7 +243,7 @@ func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, not | |||
| } | |||
| // handle event callbacks | |||
| sender.SendCallbacks(e.ctx, notifyTarget.ToCallbackList(), event, e.targetCache, e.userCache, e.notifyConfigCache.GetIbex(), e.Astats) | |||
| sender.SendCallbacks(e.ctx, notifyTarget.ToCallbackList(), event, e.targetCache, e.userCache, e.taskTplsCache, e.Astats) | |||
| // handle global webhooks | |||
| sender.SendWebhooks(notifyTarget.ToWebhookList(), event, e.Astats) | |||
| @@ -2,23 +2,24 @@ package sender | |||
| import ( | |||
| "encoding/json" | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| "time" | |||
| "github.com/ccfos/nightingale/v6/alert/aconf" | |||
| "github.com/ccfos/nightingale/v6/alert/astats" | |||
| "github.com/ccfos/nightingale/v6/memsto" | |||
| "github.com/ccfos/nightingale/v6/models" | |||
| "github.com/ccfos/nightingale/v6/pkg/ctx" | |||
| "github.com/ccfos/nightingale/v6/pkg/ibex" | |||
| "github.com/ccfos/nightingale/v6/pkg/poster" | |||
| imodels "github.com/flashcatcloud/ibex/src/models" | |||
| "github.com/flashcatcloud/ibex/src/storage" | |||
| "github.com/toolkits/pkg/logger" | |||
| ) | |||
| func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType, | |||
| ibexConf aconf.Ibex, stats *astats.Stats) { | |||
| taskTplCache *memsto.TaskTplCache, stats *astats.Stats) { | |||
| for _, url := range urls { | |||
| if url == "" { | |||
| continue | |||
| @@ -26,7 +27,7 @@ func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, | |||
| if strings.HasPrefix(url, "${ibex}") { | |||
| if !event.IsRecovered { | |||
| handleIbex(ctx, url, event, targetCache, userCache, ibexConf) | |||
| handleIbex(ctx, url, event, targetCache, userCache, taskTplCache) | |||
| } | |||
| continue | |||
| } | |||
| @@ -46,27 +47,13 @@ func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, | |||
| } | |||
| } | |||
| type TaskForm struct { | |||
| Title string `json:"title"` | |||
| Account string `json:"account"` | |||
| Batch int `json:"batch"` | |||
| Tolerance int `json:"tolerance"` | |||
| Timeout int `json:"timeout"` | |||
| Pause string `json:"pause"` | |||
| Script string `json:"script"` | |||
| Args string `json:"args"` | |||
| Stdin string `json:"stdin"` | |||
| Action string `json:"action"` | |||
| Creator string `json:"creator"` | |||
| Hosts []string `json:"hosts"` | |||
| } | |||
| type TaskCreateReply struct { | |||
| Err string `json:"err"` | |||
| Dat int64 `json:"dat"` // task.id | |||
| } | |||
| func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType, ibexConf aconf.Ibex) { | |||
| func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType, | |||
| taskTplCache *memsto.TaskTplCache) { | |||
| arr := strings.Split(url, "/") | |||
| var idstr string | |||
| @@ -96,12 +83,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe | |||
| return | |||
| } | |||
| tpl, err := models.TaskTplGetById(ctx, id) | |||
| if err != nil { | |||
| logger.Errorf("event_callback_ibex: failed to get tpl: %v", err) | |||
| return | |||
| } | |||
| tpl := taskTplCache.Get(id) | |||
| if tpl == nil { | |||
| logger.Errorf("event_callback_ibex: no such tpl(%d)", id) | |||
| return | |||
| @@ -145,61 +127,43 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe | |||
| } | |||
| // call ibex | |||
| in := TaskForm{ | |||
| Title: tpl.Title + " FH: " + host, | |||
| Account: tpl.Account, | |||
| Batch: tpl.Batch, | |||
| Tolerance: tpl.Tolerance, | |||
| Timeout: tpl.Timeout, | |||
| Pause: tpl.Pause, | |||
| Script: tpl.Script, | |||
| Args: tpl.Args, | |||
| Stdin: string(tags), | |||
| Action: "start", | |||
| Creator: tpl.UpdateBy, | |||
| Hosts: []string{host}, | |||
| } | |||
| var res TaskCreateReply | |||
| err = ibex.New( | |||
| ibexConf.Address, | |||
| ibexConf.BasicAuthUser, | |||
| ibexConf.BasicAuthPass, | |||
| ibexConf.Timeout, | |||
| ). | |||
| Path("/ibex/v1/tasks"). | |||
| In(in). | |||
| Out(&res). | |||
| POST() | |||
| in := models.TaskForm{ | |||
| Title: tpl.Title + " FH: " + host, | |||
| Account: tpl.Account, | |||
| Batch: tpl.Batch, | |||
| Tolerance: tpl.Tolerance, | |||
| Timeout: tpl.Timeout, | |||
| Pause: tpl.Pause, | |||
| Script: tpl.Script, | |||
| Args: tpl.Args, | |||
| Stdin: string(tags), | |||
| Action: "start", | |||
| Creator: tpl.UpdateBy, | |||
| Hosts: []string{host}, | |||
| AlertTriggered: true, | |||
| } | |||
| id, err = TaskAdd(in, tpl.UpdateBy, ctx.IsCenter) | |||
| if err != nil { | |||
| logger.Errorf("event_callback_ibex: call ibex fail: %v", err) | |||
| return | |||
| } | |||
| if res.Err != "" { | |||
| logger.Errorf("event_callback_ibex: call ibex response error: %v", res.Err) | |||
| return | |||
| } | |||
| // write db | |||
| record := models.TaskRecord{ | |||
| Id: res.Dat, | |||
| EventId: event.Id, | |||
| GroupId: tpl.GroupId, | |||
| IbexAddress: ibexConf.Address, | |||
| IbexAuthUser: ibexConf.BasicAuthUser, | |||
| IbexAuthPass: ibexConf.BasicAuthPass, | |||
| Title: in.Title, | |||
| Account: in.Account, | |||
| Batch: in.Batch, | |||
| Tolerance: in.Tolerance, | |||
| Timeout: in.Timeout, | |||
| Pause: in.Pause, | |||
| Script: in.Script, | |||
| Args: in.Args, | |||
| CreateAt: time.Now().Unix(), | |||
| CreateBy: in.Creator, | |||
| Id: id, | |||
| EventId: event.Id, | |||
| GroupId: tpl.GroupId, | |||
| Title: in.Title, | |||
| Account: in.Account, | |||
| Batch: in.Batch, | |||
| Tolerance: in.Tolerance, | |||
| Timeout: in.Timeout, | |||
| Pause: in.Pause, | |||
| Script: in.Script, | |||
| Args: in.Args, | |||
| CreateAt: time.Now().Unix(), | |||
| CreateBy: in.Creator, | |||
| } | |||
| if err = record.Add(ctx); err != nil { | |||
| @@ -220,3 +184,88 @@ func canDoIbex(username string, tpl *models.TaskTpl, host string, targetCache *m | |||
| return target.GroupId == tpl.GroupId, nil | |||
| } | |||
| func TaskAdd(f models.TaskForm, authUser string, isCenter bool) (int64, error) { | |||
| hosts := cleanHosts(f.Hosts) | |||
| if len(hosts) == 0 { | |||
| return 0, fmt.Errorf("arg(hosts) empty") | |||
| } | |||
| taskMeta := &imodels.TaskMeta{ | |||
| Title: f.Title, | |||
| Account: f.Account, | |||
| Batch: f.Batch, | |||
| Tolerance: f.Tolerance, | |||
| Timeout: f.Timeout, | |||
| Pause: f.Pause, | |||
| Script: f.Script, | |||
| Args: f.Args, | |||
| Stdin: f.Stdin, | |||
| Creator: f.Creator, | |||
| } | |||
| err := taskMeta.CleanFields() | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| taskMeta.HandleFH(hosts[0]) | |||
| // 任务类型分为"告警规则触发"和"n9e center用户下发"两种; | |||
| // 边缘机房"告警规则触发"的任务不需要规划,并且它可能是失联的,无法使用db资源,所以放入redis缓存中,直接下发给agentd执行 | |||
| if !isCenter && f.AlertTriggered { | |||
| if err := taskMeta.Create(); err != nil { | |||
| // 当网络不连通时,生成唯一的id,防止边缘机房中不同任务的id相同; | |||
| // 方法是,redis自增id去防止同一个机房的不同n9e edge生成的id相同; | |||
| // 但没法防止不同边缘机房生成同样的id,所以,生成id的数据不会上报存入数据库,只用于闭环执行。 | |||
| taskMeta.Id, err = storage.IdGet() | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| } | |||
| taskHost := imodels.TaskHost{ | |||
| Id: taskMeta.Id, | |||
| Host: hosts[0], | |||
| Status: "running", | |||
| } | |||
| if err = taskHost.Create(); err != nil { | |||
| logger.Warningf("task_add_fail: authUser=%s title=%s err=%s", authUser, taskMeta.Title, err.Error()) | |||
| } | |||
| // 缓存任务元信息和待下发的任务 | |||
| err = taskMeta.Cache(hosts[0]) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| } else { | |||
| // 如果是中心机房,还是保持之前的逻辑 | |||
| err = taskMeta.Save(hosts, f.Action) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| } | |||
| logger.Infof("task_add_succ: authUser=%s title=%s", authUser, taskMeta.Title) | |||
| return taskMeta.Id, nil | |||
| } | |||
| func cleanHosts(formHosts []string) []string { | |||
| cnt := len(formHosts) | |||
| arr := make([]string, 0, cnt) | |||
| for i := 0; i < cnt; i++ { | |||
| item := strings.TrimSpace(formHosts[i]) | |||
| if item == "" { | |||
| continue | |||
| } | |||
| if strings.HasPrefix(item, "#") { | |||
| continue | |||
| } | |||
| arr = append(arr, item) | |||
| } | |||
| return arr | |||
| } | |||
| @@ -7,10 +7,12 @@ import ( | |||
| "github.com/ccfos/nightingale/v6/alert" | |||
| "github.com/ccfos/nightingale/v6/alert/astats" | |||
| "github.com/ccfos/nightingale/v6/alert/process" | |||
| alertrt "github.com/ccfos/nightingale/v6/alert/router" | |||
| "github.com/ccfos/nightingale/v6/center/cconf" | |||
| "github.com/ccfos/nightingale/v6/center/cconf/rsa" | |||
| "github.com/ccfos/nightingale/v6/center/cstats" | |||
| "github.com/ccfos/nightingale/v6/center/metas" | |||
| centerrt "github.com/ccfos/nightingale/v6/center/router" | |||
| "github.com/ccfos/nightingale/v6/center/sso" | |||
| "github.com/ccfos/nightingale/v6/conf" | |||
| "github.com/ccfos/nightingale/v6/dumper" | |||
| @@ -25,13 +27,12 @@ import ( | |||
| "github.com/ccfos/nightingale/v6/pkg/version" | |||
| "github.com/ccfos/nightingale/v6/prom" | |||
| "github.com/ccfos/nightingale/v6/pushgw/idents" | |||
| pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router" | |||
| "github.com/ccfos/nightingale/v6/pushgw/writer" | |||
| "github.com/ccfos/nightingale/v6/storage" | |||
| "github.com/ccfos/nightingale/v6/tdengine" | |||
| alertrt "github.com/ccfos/nightingale/v6/alert/router" | |||
| centerrt "github.com/ccfos/nightingale/v6/center/router" | |||
| pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router" | |||
| "github.com/flashcatcloud/ibex/src/cmd/ibex" | |||
| ) | |||
| func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| @@ -90,12 +91,13 @@ func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache) | |||
| userCache := memsto.NewUserCache(ctx, syncStats) | |||
| userGroupCache := memsto.NewUserGroupCache(ctx, syncStats) | |||
| taskTplCache := memsto.NewTaskTplCache(ctx) | |||
| promClients := prom.NewPromClient(ctx) | |||
| tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat) | |||
| externalProcessors := process.NewExternalProcessors() | |||
| alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache) | |||
| alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, taskTplCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache) | |||
| writers := writer.NewWriters(config.Pushgw) | |||
| @@ -113,6 +115,11 @@ func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| pushgwRouter.Config(r) | |||
| dumper.ConfigRouter(r) | |||
| if config.Ibex.Enable { | |||
| migrate.MigrateIbexTables(db) | |||
| ibex.ServerStart(true, db, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, centerRouter, config.Ibex, config.HTTP.Port) | |||
| } | |||
| httpClean := httpx.Init(config.HTTP, r) | |||
| return func() { | |||
| @@ -356,8 +356,6 @@ func (rt *Router) Config(r *gin.Engine) { | |||
| pages.GET("/busi-groups/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.taskGetsByGids) | |||
| pages.GET("/busi-group/:id/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.bgro(), rt.taskGets) | |||
| pages.POST("/busi-group/:id/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks/add"), rt.bgrw(), rt.taskAdd) | |||
| pages.GET("/busi-group/:id/task/*url", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.taskProxy) | |||
| pages.PUT("/busi-group/:id/task/*url", rt.auth(), rt.user(), rt.perm("/job-tasks/put"), rt.bgrw(), rt.taskProxy) | |||
| pages.GET("/servers", rt.auth(), rt.admin(), rt.serversGet) | |||
| pages.GET("/server-clusters", rt.auth(), rt.admin(), rt.serverClustersGet) | |||
| @@ -488,6 +486,8 @@ func (rt *Router) Config(r *gin.Engine) { | |||
| service.GET("/alert-his-event/:eid", rt.alertHisEventGet) | |||
| service.GET("/task-tpl/:tid", rt.taskTplGetByService) | |||
| service.GET("/task-tpls", rt.taskTplGetsByService) | |||
| service.GET("/task-tpl/statistics", rt.taskTplStatistics) | |||
| service.GET("/config/:id", rt.configGet) | |||
| service.GET("/configs", rt.configsGet) | |||
| @@ -1,17 +1,14 @@ | |||
| package router | |||
| import ( | |||
| "fmt" | |||
| "net/http" | |||
| "strconv" | |||
| "strings" | |||
| "github.com/ccfos/nightingale/v6/alert/aconf" | |||
| "github.com/ccfos/nightingale/v6/models" | |||
| "github.com/ccfos/nightingale/v6/pkg/ctx" | |||
| "github.com/ccfos/nightingale/v6/pkg/ibex" | |||
| "github.com/gin-gonic/gin" | |||
| "github.com/gin-gonic/gin" | |||
| "github.com/toolkits/pkg/ginx" | |||
| ) | |||
| @@ -135,31 +132,6 @@ type TaskCreateReply struct { | |||
| Dat int64 `json:"dat"` // task.id | |||
| } | |||
| // return task.id, error | |||
| func TaskCreate(v interface{}, ibexc aconf.Ibex) (int64, error) { | |||
| var res TaskCreateReply | |||
| err := ibex.New( | |||
| ibexc.Address, | |||
| ibexc.BasicAuthUser, | |||
| ibexc.BasicAuthPass, | |||
| ibexc.Timeout, | |||
| ). | |||
| Path("/ibex/v1/tasks"). | |||
| In(v). | |||
| Out(&res). | |||
| POST() | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| if res.Err != "" { | |||
| return 0, fmt.Errorf("response.err: %v", res.Err) | |||
| } | |||
| return res.Dat, nil | |||
| } | |||
| func Username(c *gin.Context) string { | |||
| username := c.GetString(gin.AuthUserKey) | |||
| if username == "" { | |||
| @@ -92,6 +92,10 @@ func (rt *Router) jwtAuth() gin.HandlerFunc { | |||
| } | |||
| } | |||
| func (rt *Router) Auth() gin.HandlerFunc { | |||
| return rt.auth() | |||
| } | |||
| func (rt *Router) auth() gin.HandlerFunc { | |||
| if rt.HTTP.ProxyAuth.Enable { | |||
| return rt.proxyAuth() | |||
| @@ -120,6 +124,10 @@ func (rt *Router) jwtMock() gin.HandlerFunc { | |||
| } | |||
| } | |||
| func (rt *Router) User() gin.HandlerFunc { | |||
| return rt.user() | |||
| } | |||
| func (rt *Router) user() gin.HandlerFunc { | |||
| return func(c *gin.Context) { | |||
| userid := c.MustGet("userid").(int64) | |||
| @@ -174,6 +182,10 @@ func (rt *Router) bgro() gin.HandlerFunc { | |||
| } | |||
| // bgrw 逐步要被干掉,不安全 | |||
| func (rt *Router) Bgrw() gin.HandlerFunc { | |||
| return rt.bgrw() | |||
| } | |||
| func (rt *Router) bgrw() gin.HandlerFunc { | |||
| return func(c *gin.Context) { | |||
| me := c.MustGet("user").(*models.User) | |||
| @@ -233,6 +245,10 @@ func (rt *Router) bgroCheck(c *gin.Context, bgid int64) { | |||
| c.Set("busi_group", bg) | |||
| } | |||
| func (rt *Router) Perm(operation string) gin.HandlerFunc { | |||
| return rt.perm(operation) | |||
| } | |||
| func (rt *Router) perm(operation string) gin.HandlerFunc { | |||
| return func(c *gin.Context) { | |||
| me := c.MustGet("user").(*models.User) | |||
| @@ -169,10 +169,6 @@ func (rt *Router) notifyConfigPut(c *gin.Context) { | |||
| var smtp aconf.SMTPConfig | |||
| err := toml.Unmarshal([]byte(text), &smtp) | |||
| ginx.Dangerous(err) | |||
| case models.IBEX: | |||
| var ibex aconf.Ibex | |||
| err := toml.Unmarshal([]byte(f.Cval), &ibex) | |||
| ginx.Dangerous(err) | |||
| default: | |||
| ginx.Bomb(200, "key %s can not modify", f.Ckey) | |||
| } | |||
| @@ -1,13 +1,9 @@ | |||
| package router | |||
| import ( | |||
| "fmt" | |||
| "net/http" | |||
| "net/http/httputil" | |||
| "net/url" | |||
| "strings" | |||
| "time" | |||
| "github.com/ccfos/nightingale/v6/alert/sender" | |||
| "github.com/ccfos/nightingale/v6/models" | |||
| "github.com/gin-gonic/gin" | |||
| @@ -96,71 +92,6 @@ type taskForm struct { | |||
| Hosts []string `json:"hosts" binding:"required"` | |||
| } | |||
| func (f *taskForm) Verify() error { | |||
| if f.Batch < 0 { | |||
| return fmt.Errorf("arg(batch) should be nonnegative") | |||
| } | |||
| if f.Tolerance < 0 { | |||
| return fmt.Errorf("arg(tolerance) should be nonnegative") | |||
| } | |||
| if f.Timeout < 0 { | |||
| return fmt.Errorf("arg(timeout) should be nonnegative") | |||
| } | |||
| if f.Timeout > 3600*24 { | |||
| return fmt.Errorf("arg(timeout) longer than one day") | |||
| } | |||
| if f.Timeout == 0 { | |||
| f.Timeout = 30 | |||
| } | |||
| f.Pause = strings.Replace(f.Pause, ",", ",", -1) | |||
| f.Pause = strings.Replace(f.Pause, " ", "", -1) | |||
| f.Args = strings.Replace(f.Args, ",", ",", -1) | |||
| if f.Title == "" { | |||
| return fmt.Errorf("arg(title) is required") | |||
| } | |||
| if str.Dangerous(f.Title) { | |||
| return fmt.Errorf("arg(title) is dangerous") | |||
| } | |||
| if f.Script == "" { | |||
| return fmt.Errorf("arg(script) is required") | |||
| } | |||
| f.Script = strings.Replace(f.Script, "\r\n", "\n", -1) | |||
| if str.Dangerous(f.Args) { | |||
| return fmt.Errorf("arg(args) is dangerous") | |||
| } | |||
| if str.Dangerous(f.Pause) { | |||
| return fmt.Errorf("arg(pause) is dangerous") | |||
| } | |||
| if len(f.Hosts) == 0 { | |||
| return fmt.Errorf("arg(hosts) empty") | |||
| } | |||
| if f.Action != "start" && f.Action != "pause" { | |||
| return fmt.Errorf("arg(action) invalid") | |||
| } | |||
| return nil | |||
| } | |||
| func (f *taskForm) HandleFH(fh string) { | |||
| i := strings.Index(f.Title, " FH: ") | |||
| if i > 0 { | |||
| f.Title = f.Title[:i] | |||
| } | |||
| f.Title = f.Title + " FH: " + fh | |||
| } | |||
| func (rt *Router) taskRecordAdd(c *gin.Context) { | |||
| var f *models.TaskRecord | |||
| ginx.BindJSON(c, &f) | |||
| @@ -168,7 +99,7 @@ func (rt *Router) taskRecordAdd(c *gin.Context) { | |||
| } | |||
| func (rt *Router) taskAdd(c *gin.Context) { | |||
| var f taskForm | |||
| var f models.TaskForm | |||
| ginx.BindJSON(c, &f) | |||
| bgid := ginx.UrlParamInt64(c, "id") | |||
| @@ -184,7 +115,7 @@ func (rt *Router) taskAdd(c *gin.Context) { | |||
| rt.checkTargetPerm(c, f.Hosts) | |||
| // call ibex | |||
| taskId, err := TaskCreate(f, rt.NotifyConfigCache.GetIbex()) | |||
| taskId, err := sender.TaskAdd(f, user.Username, rt.Ctx.IsCenter) | |||
| ginx.Dangerous(err) | |||
| if taskId <= 0 { | |||
| @@ -193,65 +124,20 @@ func (rt *Router) taskAdd(c *gin.Context) { | |||
| // write db | |||
| record := models.TaskRecord{ | |||
| Id: taskId, | |||
| GroupId: bgid, | |||
| IbexAddress: rt.NotifyConfigCache.GetIbex().Address, | |||
| IbexAuthUser: rt.NotifyConfigCache.GetIbex().BasicAuthUser, | |||
| IbexAuthPass: rt.NotifyConfigCache.GetIbex().BasicAuthPass, | |||
| Title: f.Title, | |||
| Account: f.Account, | |||
| Batch: f.Batch, | |||
| Tolerance: f.Tolerance, | |||
| Timeout: f.Timeout, | |||
| Pause: f.Pause, | |||
| Script: f.Script, | |||
| Args: f.Args, | |||
| CreateAt: time.Now().Unix(), | |||
| CreateBy: f.Creator, | |||
| Id: taskId, | |||
| GroupId: bgid, | |||
| Title: f.Title, | |||
| Account: f.Account, | |||
| Batch: f.Batch, | |||
| Tolerance: f.Tolerance, | |||
| Timeout: f.Timeout, | |||
| Pause: f.Pause, | |||
| Script: f.Script, | |||
| Args: f.Args, | |||
| CreateAt: time.Now().Unix(), | |||
| CreateBy: f.Creator, | |||
| } | |||
| err = record.Add(rt.Ctx) | |||
| ginx.NewRender(c).Data(taskId, err) | |||
| } | |||
| func (rt *Router) taskProxy(c *gin.Context) { | |||
| target, err := url.Parse(rt.NotifyConfigCache.GetIbex().Address) | |||
| if err != nil { | |||
| ginx.NewRender(c).Message("invalid ibex address: %s", rt.NotifyConfigCache.GetIbex().Address) | |||
| return | |||
| } | |||
| director := func(req *http.Request) { | |||
| req.URL.Scheme = target.Scheme | |||
| req.URL.Host = target.Host | |||
| // fe request e.g. /api/n9e/busi-group/:id/task/*url | |||
| index := strings.Index(req.URL.Path, "/task/") | |||
| if index == -1 { | |||
| panic("url path invalid") | |||
| } | |||
| req.URL.Path = "/ibex/v1" + req.URL.Path[index:] | |||
| if target.RawQuery == "" || req.URL.RawQuery == "" { | |||
| req.URL.RawQuery = target.RawQuery + req.URL.RawQuery | |||
| } else { | |||
| req.URL.RawQuery = target.RawQuery + "&" + req.URL.RawQuery | |||
| } | |||
| if rt.NotifyConfigCache.GetIbex().BasicAuthUser != "" { | |||
| req.SetBasicAuth(rt.NotifyConfigCache.GetIbex().BasicAuthUser, rt.NotifyConfigCache.GetIbex().BasicAuthPass) | |||
| } | |||
| } | |||
| errFunc := func(w http.ResponseWriter, r *http.Request, err error) { | |||
| ginx.NewRender(c, http.StatusBadGateway).Message(err) | |||
| } | |||
| proxy := &httputil.ReverseProxy{ | |||
| Director: director, | |||
| ErrorHandler: errFunc, | |||
| } | |||
| proxy.ServeHTTP(c.Writer, c.Request) | |||
| } | |||
| @@ -91,6 +91,14 @@ func (rt *Router) taskTplGetByService(c *gin.Context) { | |||
| ginx.NewRender(c).Data(tpl, err) | |||
| } | |||
| func (rt *Router) taskTplGetsByService(c *gin.Context) { | |||
| ginx.NewRender(c).Data(models.TaskTplGetAll(rt.Ctx)) | |||
| } | |||
| func (rt *Router) taskTplStatistics(c *gin.Context) { | |||
| ginx.NewRender(c).Data(models.TaskTplStatistics(rt.Ctx)) | |||
| } | |||
| type taskTplForm struct { | |||
| Title string `json:"title" binding:"required"` | |||
| Batch int `json:"batch"` | |||
| @@ -8,6 +8,7 @@ import ( | |||
| "github.com/ccfos/nightingale/v6/alert" | |||
| "github.com/ccfos/nightingale/v6/alert/astats" | |||
| "github.com/ccfos/nightingale/v6/alert/process" | |||
| alertrt "github.com/ccfos/nightingale/v6/alert/router" | |||
| "github.com/ccfos/nightingale/v6/center/metas" | |||
| "github.com/ccfos/nightingale/v6/conf" | |||
| "github.com/ccfos/nightingale/v6/dumper" | |||
| @@ -17,12 +18,12 @@ import ( | |||
| "github.com/ccfos/nightingale/v6/pkg/logx" | |||
| "github.com/ccfos/nightingale/v6/prom" | |||
| "github.com/ccfos/nightingale/v6/pushgw/idents" | |||
| pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router" | |||
| "github.com/ccfos/nightingale/v6/pushgw/writer" | |||
| "github.com/ccfos/nightingale/v6/storage" | |||
| "github.com/ccfos/nightingale/v6/tdengine" | |||
| alertrt "github.com/ccfos/nightingale/v6/alert/router" | |||
| pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router" | |||
| "github.com/flashcatcloud/ibex/src/cmd/ibex" | |||
| ) | |||
| func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| @@ -69,17 +70,22 @@ func Initialize(configDir string, cryptoKey string) (func(), error) { | |||
| notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache) | |||
| userCache := memsto.NewUserCache(ctx, syncStats) | |||
| userGroupCache := memsto.NewUserGroupCache(ctx, syncStats) | |||
| taskTplsCache := memsto.NewTaskTplCache(ctx) | |||
| promClients := prom.NewPromClient(ctx) | |||
| tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat) | |||
| externalProcessors := process.NewExternalProcessors() | |||
| alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, | |||
| alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache) | |||
| alertRuleCache, notifyConfigCache, taskTplsCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache) | |||
| alertrtRouter := alertrt.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors) | |||
| alertrtRouter.Config(r) | |||
| if config.Ibex.Enable { | |||
| ibex.ServerStart(false, nil, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, nil, config.Ibex, config.HTTP.Port) | |||
| } | |||
| } | |||
| dumper.ConfigRouter(r) | |||
| @@ -27,6 +27,7 @@ type ConfigType struct { | |||
| Pushgw pconf.Pushgw | |||
| Alert aconf.Alert | |||
| Center cconf.Center | |||
| Ibex Ibex | |||
| } | |||
| type CenterApi struct { | |||
| @@ -40,6 +41,17 @@ type GlobalConfig struct { | |||
| RunMode string | |||
| } | |||
| type Ibex struct { | |||
| Enable bool | |||
| RPCListen string | |||
| Output Output | |||
| } | |||
| type Output struct { | |||
| ComeFrom string | |||
| AgtdPort int | |||
| } | |||
| func InitConfig(configDir, cryptoKey string) (*ConfigType, error) { | |||
| var config = new(ConfigType) | |||
| @@ -173,3 +173,7 @@ MaxIdleConnsPerHost = 100 | |||
| # Regex = "([^:]+)(?::\\d+)?" | |||
| # Replacement = "$1:80" | |||
| # TargetLabel = "__address__" | |||
| [Ibex] | |||
| Enable = false | |||
| RPCListen = "0.0.0.0:20090" | |||
| @@ -116,6 +116,10 @@ MaxIdleConnsPerHost = 100 | |||
| # Replacement = "$1:80" | |||
| # TargetLabel = "__address__" | |||
| [Ibex] | |||
| Enable = false | |||
| RPCListen = "0.0.0.0:20090" | |||
| [Redis] | |||
| # address, ip:port or ip1:port,ip2:port for cluster and sentinel(SentinelAddrs) | |||
| Address = "127.0.0.1:6379" | |||
| @@ -129,4 +133,4 @@ RedisType = "standalone" | |||
| # Mastername for sentinel type | |||
| # MasterName = "mymaster" | |||
| # SentinelUsername = "" | |||
| # SentinelPassword = "" | |||
| # SentinelPassword = "" | |||
| @@ -8,6 +8,7 @@ require ( | |||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc | |||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible | |||
| github.com/expr-lang/expr v1.16.1 | |||
| github.com/flashcatcloud/ibex v1.3.0 | |||
| github.com/gin-contrib/pprof v1.4.0 | |||
| github.com/gin-gonic/gin v1.9.1 | |||
| github.com/go-ldap/ldap/v3 v3.4.4 | |||
| @@ -38,6 +38,8 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8 | |||
| github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= | |||
| github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= | |||
| github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= | |||
| github.com/flashcatcloud/ibex v1.3.0 h1:pmapZfuQE3ZZKtOgAxUUxFbQeySG2LxiYbayjnCGiZg= | |||
| github.com/flashcatcloud/ibex v1.3.0/go.mod h1:T8hbMUySK2q6cXUaYp0AUVeKkU9Od2LjzwmB5lmTRBM= | |||
| github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= | |||
| github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= | |||
| github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= | |||
| @@ -22,7 +22,6 @@ type NotifyConfigCacheType struct { | |||
| webhooks []*models.Webhook | |||
| smtp aconf.SMTPConfig | |||
| script models.NotifyScript | |||
| ibex aconf.Ibex | |||
| sync.RWMutex | |||
| } | |||
| @@ -131,29 +130,6 @@ func (w *NotifyConfigCacheType) syncNotifyConfigs() error { | |||
| dumper.PutSyncRecord("notify_script", start.Unix(), time.Since(start).Milliseconds(), 1, "success, notify_script:\n"+cval) | |||
| start = time.Now() | |||
| cval, err = models.ConfigsGet(w.ctx, models.IBEX) | |||
| if err != nil { | |||
| dumper.PutSyncRecord("ibex", start.Unix(), -1, -1, "failed to query configs.ibex_server: "+err.Error()) | |||
| return err | |||
| } | |||
| if strings.TrimSpace(cval) != "" { | |||
| err = toml.Unmarshal([]byte(cval), &w.ibex) | |||
| if err != nil { | |||
| dumper.PutSyncRecord("ibex", start.Unix(), -1, -1, "failed to unmarshal configs.ibex_server: "+err.Error()) | |||
| logger.Errorf("failed to unmarshal ibex:%s error:%v", cval, err) | |||
| } | |||
| } else { | |||
| err = toml.Unmarshal([]byte(DefaultIbex), &w.ibex) | |||
| if err != nil { | |||
| dumper.PutSyncRecord("ibex", start.Unix(), -1, -1, "failed to unmarshal configs.ibex_server: "+err.Error()) | |||
| logger.Errorf("failed to unmarshal ibex:%s error:%v", cval, err) | |||
| } | |||
| } | |||
| dumper.PutSyncRecord("ibex", start.Unix(), time.Since(start).Milliseconds(), 1, "success, ibex_server config:\n"+cval) | |||
| return nil | |||
| } | |||
| @@ -178,9 +154,3 @@ func (w *NotifyConfigCacheType) GetNotifyScript() models.NotifyScript { | |||
| return w.script | |||
| } | |||
| func (w *NotifyConfigCacheType) GetIbex() aconf.Ibex { | |||
| w.RWMutex.RLock() | |||
| defer w.RWMutex.RUnlock() | |||
| return w.ibex | |||
| } | |||
| @@ -0,0 +1,109 @@ | |||
| package memsto | |||
| import ( | |||
| "fmt" | |||
| "sync" | |||
| "time" | |||
| "github.com/ccfos/nightingale/v6/dumper" | |||
| "github.com/ccfos/nightingale/v6/models" | |||
| "github.com/ccfos/nightingale/v6/pkg/ctx" | |||
| "github.com/pkg/errors" | |||
| "github.com/toolkits/pkg/logger" | |||
| ) | |||
| type TaskTplCache struct { | |||
| statTotal int64 | |||
| statLastUpdated int64 | |||
| ctx *ctx.Context | |||
| tpls map[int64]*models.TaskTpl | |||
| sync.RWMutex | |||
| } | |||
| func NewTaskTplCache(ctx *ctx.Context) *TaskTplCache { | |||
| ttc := &TaskTplCache{ | |||
| statTotal: -1, | |||
| statLastUpdated: -1, | |||
| ctx: ctx, | |||
| tpls: make(map[int64]*models.TaskTpl), | |||
| } | |||
| ttc.SyncTaskTpl() | |||
| return ttc | |||
| } | |||
| func (ttc *TaskTplCache) Set(tpls map[int64]*models.TaskTpl, total, lastUpdated int64) { | |||
| ttc.Lock() | |||
| ttc.tpls = tpls | |||
| ttc.Unlock() | |||
| ttc.statTotal = total | |||
| ttc.statLastUpdated = lastUpdated | |||
| } | |||
| func (ttc *TaskTplCache) Get(id int64) *models.TaskTpl { | |||
| ttc.Lock() | |||
| defer ttc.Unlock() | |||
| return ttc.tpls[id] | |||
| } | |||
| func (ttc *TaskTplCache) SyncTaskTpl() { | |||
| if err := ttc.syncTaskTpl(); err != nil { | |||
| fmt.Println("failed to sync task tpls:", err) | |||
| exit(1) | |||
| } | |||
| go ttc.loopSyncTaskTpl() | |||
| } | |||
| func (ttc *TaskTplCache) syncTaskTpl() error { | |||
| start := time.Now() | |||
| stat, err := models.TaskTplStatistics(ttc.ctx) | |||
| if err != nil { | |||
| dumper.PutSyncRecord("task_tpls", start.Unix(), -1, -1, "failed to query statistics: "+err.Error()) | |||
| return errors.WithMessage(err, "failed to exec TaskTplStatistics") | |||
| } | |||
| if !ttc.StatChange(stat.Total, stat.LastUpdated) { | |||
| dumper.PutSyncRecord("task_tpls", start.Unix(), -1, -1, "not changed") | |||
| return nil | |||
| } | |||
| lst, err := models.TaskTplGetAll(ttc.ctx) | |||
| if err != nil { | |||
| dumper.PutSyncRecord("task_tpls", start.Unix(), -1, -1, "failed to query records: "+err.Error()) | |||
| return errors.WithMessage(err, "failed to exec TaskTplGetAll") | |||
| } | |||
| m := make(map[int64]*models.TaskTpl, len(lst)) | |||
| for _, tpl := range lst { | |||
| m[tpl.Id] = tpl | |||
| } | |||
| ttc.Set(m, stat.Total, stat.LastUpdated) | |||
| ms := time.Since(start).Milliseconds() | |||
| logger.Infof("timer: sync task tpls done, cost: %dms, number: %d", ms, len(m)) | |||
| dumper.PutSyncRecord("task_tpls", start.Unix(), ms, len(m), "success") | |||
| return nil | |||
| } | |||
| func (ttc *TaskTplCache) loopSyncTaskTpl() { | |||
| d := time.Duration(9) * time.Second | |||
| for { | |||
| time.Sleep(d) | |||
| if err := ttc.syncTaskTpl(); err != nil { | |||
| logger.Warning("failed to sync task tpl:", err) | |||
| } | |||
| } | |||
| } | |||
| func (ttc *TaskTplCache) StatChange(total int64, lastUpdated int64) bool { | |||
| if ttc.statTotal == total && ttc.statLastUpdated == lastUpdated { | |||
| return false | |||
| } | |||
| return true | |||
| } | |||
| @@ -1,8 +1,12 @@ | |||
| package migrate | |||
| import ( | |||
| "fmt" | |||
| "github.com/ccfos/nightingale/v6/models" | |||
| "github.com/ccfos/nightingale/v6/pkg/ormx" | |||
| imodels "github.com/flashcatcloud/ibex/src/models" | |||
| "github.com/toolkits/pkg/logger" | |||
| "gorm.io/gorm" | |||
| ) | |||
| @@ -12,6 +16,24 @@ func Migrate(db *gorm.DB) { | |||
| MigrateEsIndexPatternTable(db) | |||
| } | |||
| func MigrateIbexTables(db *gorm.DB) { | |||
| dts := []interface{}{&imodels.TaskMeta{}, &imodels.TaskScheduler{}, &imodels.TaskSchedulerHealth{}, &imodels.TaskHostDoing{}, &imodels.TaskAction{}} | |||
| for _, dt := range dts { | |||
| err := db.AutoMigrate(dt) | |||
| if err != nil { | |||
| logger.Errorf("failed to migrate table:%v %v", dt, err) | |||
| } | |||
| } | |||
| for i := 0; i < 100; i++ { | |||
| tableName := fmt.Sprintf("task_host_%d", i) | |||
| err := db.Table(tableName).AutoMigrate(&imodels.TaskHost{}) | |||
| if err != nil { | |||
| logger.Errorf("failed to migrate table:%s %v", tableName, err) | |||
| } | |||
| } | |||
| } | |||
| func MigrateTables(db *gorm.DB) error { | |||
| dts := []interface{}{&RecordingRule{}, &AlertRule{}, &AlertSubscribe{}, &AlertMute{}, | |||
| &TaskRecord{}, &ChartShare{}, &Target{}, &Configs{}, &Datasource{}, &NotifyTpl{}, | |||
| @@ -60,6 +60,33 @@ func TaskTplTotal(ctx *ctx.Context, bgids []int64, query string) (int64, error) | |||
| return Count(session) | |||
| } | |||
| func TaskTplStatistics(ctx *ctx.Context) (*Statistics, error) { | |||
| if !ctx.IsCenter { | |||
| return poster.GetByUrls[*Statistics](ctx, "/v1/n9e/task-tpl/statistics") | |||
| } | |||
| session := DB(ctx).Model(&TaskTpl{}).Select("count(*) as total", "max(update_at) as last_updated") | |||
| var stats []*Statistics | |||
| err := session.Find(&stats).Error | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return stats[0], nil | |||
| } | |||
| func TaskTplGetAll(ctx *ctx.Context) ([]*TaskTpl, error) { | |||
| if !ctx.IsCenter { | |||
| return poster.GetByUrls[[]*TaskTpl](ctx, "/v1/n9e/task-tpls") | |||
| } | |||
| lst := make([]*TaskTpl, 0) | |||
| err := DB(ctx).Find(&lst).Error | |||
| return lst, err | |||
| } | |||
| func TaskTplGets(ctx *ctx.Context, bgids []int64, query string, limit, offset int) ([]TaskTpl, error) { | |||
| session := DB(ctx).Order("title").Limit(limit).Offset(offset) | |||
| if len(bgids) > 0 { | |||
| @@ -316,3 +343,84 @@ func (t *TaskTpl) UpdateGroup(ctx *ctx.Context, groupId int64, updateBy string) | |||
| "update_at": time.Now().Unix(), | |||
| }).Error | |||
| } | |||
| type TaskForm struct { | |||
| Title string `json:"title"` | |||
| Account string `json:"account"` | |||
| Batch int `json:"batch"` | |||
| Tolerance int `json:"tolerance"` | |||
| Timeout int `json:"timeout"` | |||
| Pause string `json:"pause"` | |||
| Script string `json:"script"` | |||
| Args string `json:"args"` | |||
| Stdin string `json:"stdin"` | |||
| Action string `json:"action"` | |||
| Creator string `json:"creator"` | |||
| Hosts []string `json:"hosts"` | |||
| AlertTriggered bool `json:"alert_triggered"` | |||
| } | |||
| func (f *TaskForm) Verify() error { | |||
| if f.Batch < 0 { | |||
| return fmt.Errorf("arg(batch) should be nonnegative") | |||
| } | |||
| if f.Tolerance < 0 { | |||
| return fmt.Errorf("arg(tolerance) should be nonnegative") | |||
| } | |||
| if f.Timeout < 0 { | |||
| return fmt.Errorf("arg(timeout) should be nonnegative") | |||
| } | |||
| if f.Timeout > 3600*24 { | |||
| return fmt.Errorf("arg(timeout) longer than one day") | |||
| } | |||
| if f.Timeout == 0 { | |||
| f.Timeout = 30 | |||
| } | |||
| f.Pause = strings.Replace(f.Pause, ",", ",", -1) | |||
| f.Pause = strings.Replace(f.Pause, " ", "", -1) | |||
| f.Args = strings.Replace(f.Args, ",", ",", -1) | |||
| if f.Title == "" { | |||
| return fmt.Errorf("arg(title) is required") | |||
| } | |||
| if str.Dangerous(f.Title) { | |||
| return fmt.Errorf("arg(title) is dangerous") | |||
| } | |||
| if f.Script == "" { | |||
| return fmt.Errorf("arg(script) is required") | |||
| } | |||
| f.Script = strings.Replace(f.Script, "\r\n", "\n", -1) | |||
| if str.Dangerous(f.Args) { | |||
| return fmt.Errorf("arg(args) is dangerous") | |||
| } | |||
| if str.Dangerous(f.Pause) { | |||
| return fmt.Errorf("arg(pause) is dangerous") | |||
| } | |||
| if len(f.Hosts) == 0 { | |||
| return fmt.Errorf("arg(hosts) empty") | |||
| } | |||
| if f.Action != "start" && f.Action != "pause" { | |||
| return fmt.Errorf("arg(action) invalid") | |||
| } | |||
| return nil | |||
| } | |||
| func (f *TaskForm) HandleFH(fh string) { | |||
| i := strings.Index(f.Title, " FH: ") | |||
| if i > 0 { | |||
| f.Title = f.Title[:i] | |||
| } | |||
| f.Title = f.Title + " FH: " + fh | |||
| } | |||