未验证 提交 00aacca9 编写于 作者: M M-Cosmosss 提交者: GitHub

feat: Workflow job Guanceyun check (#2966)

* wip
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* wip
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* wip
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* wip
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* wip
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* wip
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* wip
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* fix select
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* fix
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* wip rename status
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* fix
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* fix
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* fix monitor logic
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

* clear
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>

---------
Signed-off-by: NM-Cosmosss <yuzhou@koderover.com>
上级 ee56c253
......@@ -221,6 +221,7 @@ const (
JobOfflineService JobType = "offline-service"
JobMseGrayRelease JobType = "mse-gray-release"
JobMseGrayOffline JobType = "mse-gray-offline"
JobGuanceyunCheck JobType = "guanceyun-check"
)
const (
......
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 models
import "go.mongodb.org/mongo-driver/bson/primitive"
type Observability struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty" yaml:"id"`
Type string `json:"type" bson:"type" yaml:"type"`
Name string `json:"name" bson:"name" yaml:"name"`
Host string `json:"host" bson:"host" yaml:"host"`
// ConsoleHost is used for guanceyun console, Host is guanceyun OpenApi Addr
ConsoleHost string `json:"console_host" bson:"console_host" yaml:"console_host"`
// ApiKey is used for guanceyun
ApiKey string `json:"api_key" bson:"api_key" yaml:"api_key"`
UpdateTime int64 `json:"update_time" bson:"update_time" yaml:"update_time"`
}
func (Observability) TableName() string {
return "observability"
}
......@@ -477,6 +477,15 @@ type JobTaskOfflineServiceEvent struct {
Error string `bson:"error" json:"error" yaml:"error"`
}
type JobTaskGuanceyunCheckSpec struct {
ID string `bson:"id" json:"id" yaml:"id"`
Name string `bson:"name" json:"name" yaml:"name"`
// CheckTime minute
CheckTime int64 `bson:"check_time" json:"check_time" yaml:"check_time"`
CheckMode string `bson:"check_mode" json:"check_mode" yaml:"check_mode"`
Monitors []*GuanceyunMonitor `bson:"monitors" json:"monitors" yaml:"monitors"`
}
type JobTaskMseGrayReleaseSpec struct {
Production bool `bson:"production" json:"production" yaml:"production"`
GrayTag string `bson:"gray_tag" json:"gray_tag" yaml:"gray_tag"`
......
......@@ -31,6 +31,7 @@ import (
commontypes "github.com/koderover/zadig/pkg/microservice/aslan/core/common/types"
"github.com/koderover/zadig/pkg/setting"
"github.com/koderover/zadig/pkg/tool/dingtalk"
"github.com/koderover/zadig/pkg/tool/guanceyun"
"github.com/koderover/zadig/pkg/tool/lark"
"github.com/koderover/zadig/pkg/types"
)
......@@ -605,6 +606,24 @@ type IstioJobTarget struct {
TargetReplica int `bson:"target_replica,omitempty" json:"target_replica,omitempty" yaml:"target_replica,omitempty"`
}
type GuanceyunCheckJobSpec struct {
ID string `bson:"id" json:"id" yaml:"id"`
Name string `bson:"name" json:"name" yaml:"name"`
// CheckTime minute
CheckTime int64 `bson:"check_time" json:"check_time" yaml:"check_time"`
CheckMode string `bson:"check_mode" json:"check_mode" yaml:"check_mode"`
Monitors []*GuanceyunMonitor `bson:"monitors" json:"monitors" yaml:"monitors"`
}
type GuanceyunMonitor struct {
ID string `bson:"id" json:"id" yaml:"id"`
Name string `bson:"name" json:"name" yaml:"name"`
// Level is the lowest level to trigger alarm
Level guanceyun.Level `bson:"level" json:"level" yaml:"level"`
Status string `bson:"status,omitempty" json:"status,omitempty" yaml:"status,omitempty"`
Url string `bson:"url,omitempty" json:"url,omitempty" yaml:"url,omitempty"`
}
type MseGrayReleaseJobSpec struct {
Production bool `bson:"production" json:"production" yaml:"production"`
GrayTag string `bson:"gray_tag" json:"gray_tag" yaml:"gray_tag"`
......
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 mongodb
import (
"context"
"fmt"
"time"
"github.com/pkg/errors"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"github.com/koderover/zadig/pkg/microservice/aslan/config"
"github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models"
mongotool "github.com/koderover/zadig/pkg/tool/mongo"
)
type ObservabilityColl struct {
*mongo.Collection
coll string
}
func NewObservabilityColl() *ObservabilityColl {
name := models.Observability{}.TableName()
return &ObservabilityColl{
Collection: mongotool.Database(config.MongoDatabase()).Collection(name),
coll: name,
}
}
func (c *ObservabilityColl) GetCollectionName() string {
return c.coll
}
func (c *ObservabilityColl) EnsureIndex(ctx context.Context) error {
return nil
}
func (c *ObservabilityColl) Create(ctx context.Context, args *models.Observability) error {
if args == nil {
return errors.New("observability is nil")
}
args.UpdateTime = time.Now().Unix()
_, err := c.InsertOne(ctx, args)
return err
}
func (c *ObservabilityColl) Update(ctx context.Context, idString string, args *models.Observability) error {
if args == nil {
return errors.New("observability is nil")
}
id, err := primitive.ObjectIDFromHex(idString)
if err != nil {
return fmt.Errorf("invalid id")
}
args.UpdateTime = time.Now().Unix()
query := bson.M{"_id": id}
change := bson.M{"$set": args}
_, err = c.UpdateOne(ctx, query, change)
return err
}
func (c *ObservabilityColl) List(ctx context.Context, _type string) ([]*models.Observability, error) {
resp := make([]*models.Observability, 0)
query := bson.M{}
if _type != "" {
query["type"] = _type
}
cursor, err := c.Collection.Find(ctx, query)
if err != nil {
return nil, err
}
return resp, cursor.All(ctx, &resp)
}
func (c *ObservabilityColl) GetByID(ctx context.Context, idString string) (*models.Observability, error) {
id, err := primitive.ObjectIDFromHex(idString)
if err != nil {
return nil, err
}
query := bson.M{"_id": id}
resp := new(models.Observability)
return resp, c.FindOne(ctx, query).Decode(resp)
}
func (c *ObservabilityColl) DeleteByID(ctx context.Context, idString string) error {
id, err := primitive.ObjectIDFromHex(idString)
if err != nil {
return err
}
query := bson.M{"_id": id}
_, err = c.DeleteOne(ctx, query)
return err
}
......@@ -92,6 +92,8 @@ func initJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTas
jobCtl = NewMseGrayReleaseJobCtl(job, workflowCtx, ack, logger)
case string(config.JobMseGrayOffline):
jobCtl = NewMseGrayOfflineJobCtl(job, workflowCtx, ack, logger)
case string(config.JobGuanceyunCheck):
jobCtl = NewGuanceyunCheckJobCtl(job, workflowCtx, ack, logger)
default:
jobCtl = NewFreestyleJobCtl(job, workflowCtx, ack, logger)
}
......
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 jobcontroller
import (
"context"
"fmt"
"net/url"
"time"
"go.uber.org/zap"
"github.com/koderover/zadig/pkg/microservice/aslan/config"
commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models"
"github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb"
"github.com/koderover/zadig/pkg/tool/guanceyun"
)
const (
StatusChecking = "checking"
StatusNormal = "normal"
StatusAbnormal = "abnormal"
StatusUnfinished = "unfinished"
)
type GuanceyunCheckJobCtl struct {
job *commonmodels.JobTask
workflowCtx *commonmodels.WorkflowTaskCtx
logger *zap.SugaredLogger
jobTaskSpec *commonmodels.JobTaskGuanceyunCheckSpec
ack func()
}
func NewGuanceyunCheckJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func(), logger *zap.SugaredLogger) *GuanceyunCheckJobCtl {
jobTaskSpec := &commonmodels.JobTaskGuanceyunCheckSpec{}
if err := commonmodels.IToi(job.Spec, jobTaskSpec); err != nil {
logger.Error(err)
}
job.Spec = jobTaskSpec
return &GuanceyunCheckJobCtl{
job: job,
workflowCtx: workflowCtx,
logger: logger,
ack: ack,
jobTaskSpec: jobTaskSpec,
}
}
func (c *GuanceyunCheckJobCtl) Clean(ctx context.Context) {}
func (c *GuanceyunCheckJobCtl) Run(ctx context.Context) {
c.job.Status = config.StatusRunning
c.ack()
info, err := mongodb.NewObservabilityColl().GetByID(context.Background(), c.jobTaskSpec.ID)
if err != nil {
logError(c.job, fmt.Sprintf("get observability info error: %v", err), c.logger)
return
}
link := func(checker string) string {
return info.ConsoleHost + "/keyevents/monitorChart?leftActiveKey=Events&activeName=Events&query=df_monitor_checker_name" + url.QueryEscape(`:"`+checker+`"`)
}
client := guanceyun.NewClient(info.Host, info.ApiKey)
timeout := time.After(time.Duration(c.jobTaskSpec.CheckTime) * time.Minute)
checkArgs := make([]*guanceyun.SearchEventByMonitorArg, 0)
checkMap := make(map[string]*commonmodels.GuanceyunMonitor)
for _, monitor := range c.jobTaskSpec.Monitors {
checkArgs = append(checkArgs, &guanceyun.SearchEventByMonitorArg{
CheckerName: monitor.Name,
CheckerID: monitor.ID,
})
checkMap[monitor.ID] = monitor
monitor.Status = StatusChecking
}
c.ack()
check := func() (bool, error) {
triggered := false
resp, err := client.SearchEventByChecker(checkArgs, time.Now().UnixMilli(), time.Now().UnixMilli())
if err != nil {
return false, err
}
for _, eventResp := range resp {
if checker, ok := checkMap[eventResp.CheckerID]; ok {
// checker has been triggered if url not empty, ignore it
if checker.Url == "" && guanceyun.LevelMap[eventResp.EventLevel] >= guanceyun.LevelMap[checker.Level] {
checker.Status = StatusAbnormal
checker.Url = link(eventResp.CheckerName)
triggered = true
}
} else {
return false, fmt.Errorf("checker %s %s not found", eventResp.CheckerID, eventResp.CheckerName)
}
}
return triggered, nil
}
setNoEventMonitorStatusUnfinished := func() {
for _, monitor := range c.jobTaskSpec.Monitors {
if monitor.Url == "" {
monitor.Status = StatusUnfinished
}
}
}
isAllMonitorHasEvent := func() bool {
for _, monitor := range c.jobTaskSpec.Monitors {
if monitor.Url == "" {
return false
}
}
return true
}
isNoMonitorHasEvent := func() bool {
for _, monitor := range c.jobTaskSpec.Monitors {
if monitor.Url != "" {
return false
}
}
return true
}
for {
c.ack()
// GuanceYun default openapi limit is 20 per minute
time.Sleep(time.Second * 10)
triggered, err := check()
if err != nil {
logError(c.job, fmt.Sprintf("check error: %v", err), c.logger)
return
}
switch c.jobTaskSpec.CheckMode {
case "trigger":
if triggered {
setNoEventMonitorStatusUnfinished()
c.job.Status = config.StatusFailed
return
}
case "monitor":
if isAllMonitorHasEvent() {
c.job.Status = config.StatusFailed
return
}
default:
logError(c.job, fmt.Sprintf("invalid check mode: %s", c.jobTaskSpec.CheckMode), c.logger)
return
}
select {
case <-ctx.Done():
c.job.Status = config.StatusCancelled
return
case <-timeout:
if isNoMonitorHasEvent() {
c.job.Status = config.StatusPassed
} else {
c.job.Status = config.StatusFailed
}
// no event triggered in check time
for _, monitor := range c.jobTaskSpec.Monitors {
if monitor.Url == "" {
monitor.Status = StatusNormal
}
}
return
default:
}
}
}
func (c *GuanceyunCheckJobCtl) SaveInfo(ctx context.Context) error {
return mongodb.NewJobInfoColl().Create(context.TODO(), &commonmodels.JobInfo{
Type: c.job.JobType,
WorkflowName: c.workflowCtx.WorkflowName,
WorkflowDisplayName: c.workflowCtx.WorkflowDisplayName,
TaskID: c.workflowCtx.TaskID,
ProductName: c.workflowCtx.ProjectName,
StartTime: c.job.StartTime,
EndTime: c.job.EndTime,
Duration: c.job.EndTime - c.job.StartTime,
Status: string(c.job.Status),
})
}
......@@ -391,6 +391,7 @@ func initDatabase() {
commonrepo.NewDiffNoteColl(),
commonrepo.NewDindCleanColl(),
commonrepo.NewIMAppColl(),
commonrepo.NewObservabilityColl(),
commonrepo.NewFavoriteColl(),
commonrepo.NewGithubAppColl(),
commonrepo.NewHelmRepoColl(),
......
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 handler
import (
"github.com/gin-gonic/gin"
"github.com/koderover/zadig/pkg/microservice/aslan/core/system/service"
internalhandler "github.com/koderover/zadig/pkg/shared/handler"
)
type GuanceyunMonitor struct {
CheckerName string `json:"checker_name"`
CheckerID string `json:"checker_id"`
}
func ListGuanceyunMonitor(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
contents, err := service.ListGuanceyunMonitor(c.Param("id"), c.Query("search"))
if err != nil {
ctx.Err = err
return
}
resp := make([]GuanceyunMonitor, 0)
for _, content := range contents {
resp = append(resp, GuanceyunMonitor{
CheckerName: content.JSONScript.Name,
CheckerID: content.UUID,
})
}
ctx.Resp = resp
}
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 handler
import (
"fmt"
"github.com/gin-gonic/gin"
internalhandler "github.com/koderover/zadig/pkg/shared/handler"
)
func isSystemAdmin(c *gin.Context) {
ctx, err := internalhandler.NewContextWithAuthorization(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
if err != nil {
ctx.Logger.Errorf("failed to generate authorization info for user: %s, error: %s", ctx.UserID, err)
ctx.Err = fmt.Errorf("authorization Info Generation failed: err %s", err)
ctx.UnAuthorized = true
return
}
if !ctx.Resources.IsSystemAdmin {
ctx.UnAuthorized = true
c.Abort()
}
return
}
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 handler
import (
"github.com/gin-gonic/gin"
commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models"
"github.com/koderover/zadig/pkg/microservice/aslan/core/system/service"
internalhandler "github.com/koderover/zadig/pkg/shared/handler"
e "github.com/koderover/zadig/pkg/tool/errors"
)
func ListObservability(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
ctx.Resp, ctx.Err = service.ListObservability(c.Query("type"), false)
}
func ListObservabilityDetail(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
ctx.Resp, ctx.Err = service.ListObservability(c.Query("type"), true)
}
func CreateObservability(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
var args commonmodels.Observability
if err := c.ShouldBindJSON(&args); err != nil {
ctx.Err = e.ErrInvalidParam.AddErr(err)
return
}
ctx.Err = service.CreateObservability(&args)
}
func UpdateObservability(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
var args commonmodels.Observability
if err := c.ShouldBindJSON(&args); err != nil {
ctx.Err = e.ErrInvalidParam.AddErr(err)
return
}
ctx.Err = service.UpdateObservability(c.Param("id"), &args)
}
func DeleteObservability(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
ctx.Err = service.DeleteObservability(c.Param("id"))
}
func ValidateObservability(c *gin.Context) {
ctx := internalhandler.NewContext(c)
defer func() { internalhandler.JSONResponse(c, ctx) }()
var args commonmodels.Observability
if err := c.ShouldBindJSON(&args); err != nil {
ctx.Err = e.ErrInvalidParam.AddErr(err)
return
}
ctx.Err = service.ValidateObservability(&args)
}
......@@ -270,6 +270,17 @@ func (*Router) Inject(router *gin.RouterGroup) {
imapp.POST("/validate", ValidateIMApp)
}
observability := router.Group("observability")
{
observability.GET("", ListObservability)
observability = observability.Group("", isSystemAdmin)
observability.GET("/detail", ListObservabilityDetail)
observability.POST("", CreateObservability)
observability.PUT("/:id", UpdateObservability)
observability.DELETE("/:id", DeleteObservability)
observability.POST("/validate", ValidateObservability)
}
lark := router.Group("lark")
{
lark.GET("/:id/department/:department_id", GetLarkDepartment)
......@@ -335,6 +346,12 @@ func (*Router) Inject(router *gin.RouterGroup) {
meego.GET("/projects/:projectID/work_item/:workItemID/transitions", ListAvailableWorkItemTransitions)
}
// guanceyun api
guanceyun := router.Group("guanceyun")
{
guanceyun.GET("/:id/monitor", ListGuanceyunMonitor)
}
// personal favorite API
favorite := router.Group("favorite")
{
......
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 service
import (
"context"
"github.com/pkg/errors"
"github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb"
"github.com/koderover/zadig/pkg/tool/guanceyun"
)
func ListGuanceyunMonitor(id, search string) ([]guanceyun.MonitorContent, error) {
info, err := mongodb.NewObservabilityColl().GetByID(context.Background(), id)
if err != nil {
return nil, errors.Wrapf(err, "get observability info %s failed", id)
}
contents, err := guanceyun.NewClient(info.Host, info.ApiKey).ListAllMonitor(search)
if err != nil {
return nil, errors.Wrapf(err, "list guanceyun monitor failed")
}
return contents, nil
}
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 service
import (
"context"
"errors"
"github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models"
"github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb"
e "github.com/koderover/zadig/pkg/tool/errors"
"github.com/koderover/zadig/pkg/tool/guanceyun"
)
func ListObservability(_type string, isAdmin bool) ([]*models.Observability, error) {
resp, err := mongodb.NewObservabilityColl().List(context.Background(), _type)
if err != nil {
return nil, e.ErrListObservabilityIntegration.AddErr(err)
}
if !isAdmin {
for _, v := range resp {
v.ApiKey = ""
}
}
return resp, nil
}
func CreateObservability(args *models.Observability) error {
if err := mongodb.NewObservabilityColl().Create(context.Background(), args); err != nil {
return e.ErrCreateObservabilityIntegration.AddErr(err)
}
return nil
}
func UpdateObservability(id string, args *models.Observability) error {
if err := mongodb.NewObservabilityColl().Update(context.Background(), id, args); err != nil {
return e.ErrUpdateObservabilityIntegration.AddErr(err)
}
return nil
}
func DeleteObservability(id string) error {
if err := mongodb.NewObservabilityColl().DeleteByID(context.Background(), id); err != nil {
return e.ErrDeleteObservabilityIntegration.AddErr(err)
}
return nil
}
func ValidateObservability(args *models.Observability) error {
switch args.Type {
case "guanceyun":
return validateGuanceyun(args)
default:
return errors.New("invalid observability type")
}
}
func validateGuanceyun(args *models.Observability) error {
_, _, err := guanceyun.NewClient(args.Host, args.ApiKey).ListMonitor("", 1, 1)
return err
}
......@@ -109,6 +109,8 @@ func InitJobCtl(job *commonmodels.Job, workflow *commonmodels.WorkflowV4) (JobCt
resp = &MseGrayReleaseJob{job: job, workflow: workflow}
case config.JobMseGrayOffline:
resp = &MseGrayOfflineJob{job: job, workflow: workflow}
case config.JobGuanceyunCheck:
resp = &GuanceyunCheckJob{job: job, workflow: workflow}
default:
return resp, fmt.Errorf("job type not found %s", job.JobType)
}
......
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 job
import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/koderover/zadig/pkg/microservice/aslan/config"
commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models"
)
type GuanceyunCheckJob struct {
job *commonmodels.Job
workflow *commonmodels.WorkflowV4
spec *commonmodels.GuanceyunCheckJobSpec
}
func (j *GuanceyunCheckJob) Instantiate() error {
j.spec = &commonmodels.GuanceyunCheckJobSpec{}
if err := commonmodels.IToiYaml(j.job.Spec, j.spec); err != nil {
return err
}
j.job.Spec = j.spec
return nil
}
func (j *GuanceyunCheckJob) SetPreset() error {
j.spec = &commonmodels.GuanceyunCheckJobSpec{}
if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil {
return err
}
j.job.Spec = j.spec
return nil
}
func (j *GuanceyunCheckJob) MergeArgs(args *commonmodels.Job) error {
j.spec = &commonmodels.GuanceyunCheckJobSpec{}
if err := commonmodels.IToi(args.Spec, j.spec); err != nil {
return err
}
j.job.Spec = j.spec
return nil
}
func (j *GuanceyunCheckJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) {
j.spec = &commonmodels.GuanceyunCheckJobSpec{}
if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil {
return nil, err
}
j.job.Spec = j.spec
nameSet := sets.NewString()
for _, monitor := range j.spec.Monitors {
if nameSet.Has(monitor.Name) {
return nil, errors.Errorf("duplicate monitor name %s", monitor.Name)
}
nameSet.Insert(monitor.Name)
monitor.Status = "checking"
}
jobTask := &commonmodels.JobTask{
Name: j.job.Name,
Key: j.job.Name,
JobInfo: map[string]string{
JobNameKey: j.job.Name,
},
JobType: string(config.JobGuanceyunCheck),
Spec: &commonmodels.JobTaskGuanceyunCheckSpec{
ID: j.spec.ID,
Name: j.spec.Name,
CheckTime: j.spec.CheckTime,
CheckMode: j.spec.CheckMode,
Monitors: j.spec.Monitors,
},
}
return []*commonmodels.JobTask{jobTask}, nil
}
func (j *GuanceyunCheckJob) LintJob() error {
j.spec = &commonmodels.GuanceyunCheckJobSpec{}
if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil {
return err
}
if j.spec.CheckTime <= 0 {
return errors.Errorf("check time must be greater than 0")
}
if len(j.spec.Monitors) == 0 {
return errors.Errorf("num of check monitor must be greater than 0")
}
switch j.spec.CheckMode {
case "monitor", "trigger":
default:
return errors.Errorf("The failed policy is invalid")
}
return nil
}
......@@ -863,4 +863,13 @@ var (
ErrUpdateLLMIntegration = NewHTTPError(7012, "更新llm集成失败")
ErrDeleteLLMIntegration = NewHTTPError(7013, "删除llm集成失败")
ErrGetLLMIntegration = NewHTTPError(7014, "获取llm集成详情失败")
//-----------------------------------------------------------------------------------------------
// observability integration Error Range: 7020 - 7029
//-----------------------------------------------------------------------------------------------
ErrCreateObservabilityIntegration = NewHTTPError(7020, "创建 观测工具 集成失败")
ErrListObservabilityIntegration = NewHTTPError(7021, "获取 观测工具 集成列表失败")
ErrUpdateObservabilityIntegration = NewHTTPError(7022, "更新 观测工具 集成失败")
ErrDeleteObservabilityIntegration = NewHTTPError(7023, "删除 观测工具 集成失败")
ErrGetObservabilityIntegration = NewHTTPError(7024, "获取 观测工具 集成详情失败")
)
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 guanceyun
import (
"github.com/imroc/req/v3"
"github.com/pkg/errors"
)
type Client struct {
*req.Client
BaseURL string
}
func NewClient(url, apiKey string) *Client {
return &Client{
Client: req.C().
SetCommonHeader("DF-API-KEY", apiKey).
OnAfterResponse(func(client *req.Client, resp *req.Response) error {
if resp.Err != nil {
resp.Err = errors.Wrapf(resp.Err, "body: %s", resp.String())
return nil
}
if !resp.IsSuccessState() {
resp.Err = errors.Errorf("unexpected status code %d, body: %s", resp.GetStatusCode(), resp.String())
return nil
}
return nil
}),
BaseURL: url,
}
}
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 guanceyun
import (
"encoding/json"
"github.com/pkg/errors"
)
type Level string
var (
Critical Level = "critical"
Error Level = "error"
Warning Level = "warning"
LevelMap = map[Level]int{
Critical: 4,
Error: 3,
Warning: 2,
}
)
type EventResponse struct {
Code int `json:"code"`
Content *EventContent `json:"content"`
ErrorCode string `json:"errorCode"`
Message string `json:"message"`
Success bool `json:"success"`
TraceID string `json:"traceId"`
}
type Data struct {
Docid string `json:"__docid"`
AlertTimeRanges []interface{} `json:"alert_time_ranges"`
Date int64 `json:"date"`
DfBotObsDetail interface{} `json:"df_bot_obs_detail"`
DfDimensionTags string `json:"df_dimension_tags"`
DfEventID string `json:"df_event_id"`
DfMessage string `json:"df_message"`
DfMeta string `json:"df_meta"`
DfMonitorChecker string `json:"df_monitor_checker"`
DfMonitorCheckerEventRef string `json:"df_monitor_checker_event_ref"`
DfMonitorCheckerName string `json:"df_monitor_checker_name"`
DfMonitorType string `json:"df_monitor_type"`
DfSource string `json:"df_source"`
DfStatus string `json:"df_status"`
DfTitle string `json:"df_title"`
}
type MonitorMeta struct {
AlertInfo AlertInfo `json:"alert_info"`
CheckTargets []CheckTargets `json:"check_targets"`
CheckerOpt CheckerOpt `json:"checker_opt"`
DimensionTags DimensionTags `json:"dimension_tags"`
ExtraData ExtraData `json:"extra_data"`
MonitorOpt MonitorOpt `json:"monitor_opt"`
}
type Targets struct {
HasSecret bool `json:"hasSecret"`
IgnoreReason string `json:"ignoreReason"`
IsIgnored bool `json:"isIgnored"`
MinInterval int `json:"minInterval"`
Status []interface{} `json:"status"`
SubStatus []string `json:"subStatus"`
To []string `json:"to,omitempty"`
Type string `json:"type"`
BodyType string `json:"bodyType,omitempty"`
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
}
type AlertInfo struct {
MatchedSilentRule interface{} `json:"matchedSilentRule"`
Targets []Targets `json:"targets"`
}
type CheckTargets struct {
Alias string `json:"alias"`
Dql string `json:"dql"`
QueryType string `json:"queryType"`
Range int `json:"range"`
}
type CheckerOpt struct {
ID string `json:"id"`
InfoEvent bool `json:"infoEvent"`
Interval int `json:"interval"`
Message interface{} `json:"message"`
Name string `json:"name"`
NoDataAction string `json:"noDataAction"`
NoDataInterval int `json:"noDataInterval"`
NoDataMessage interface{} `json:"noDataMessage"`
NoDataRecoverMessage interface{} `json:"noDataRecoverMessage"`
NoDataRecoverTitle interface{} `json:"noDataRecoverTitle"`
NoDataScale int `json:"noDataScale"`
NoDataTitle interface{} `json:"noDataTitle"`
RecoverInterval int `json:"recoverInterval"`
RecoverMessage interface{} `json:"recoverMessage"`
RecoverScale int `json:"recoverScale"`
RecoverTitle string `json:"recoverTitle"`
Rules []Rules `json:"rules"`
Title string `json:"title"`
}
type DimensionTags struct {
}
type ExtraData struct {
Type string `json:"type"`
}
type MonitorOpt struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
}
type EventContent struct {
Data []Data `json:"data"`
Limit int `json:"limit"`
Offset int `json:"offset"`
TotalCount int `json:"total_count"`
}
type SearchEventBody struct {
TimeRange []int64 `json:"timeRange"`
Filters []EventFilters `json:"filters"`
Limit int `json:"limit"`
}
type EventFilters struct {
Name string `json:"name"`
Condition string `json:"condition"`
Operation string `json:"operation"`
Value []string `json:"value"`
}
type SearchEventByMonitorArg struct {
CheckerName string
CheckerID string
}
type EventResp struct {
CheckerName string
CheckerID string
EventLevel Level
}
// SearchEventByChecker startTime and endTime are Millisecond timestamps
func (c *Client) SearchEventByChecker(args []*SearchEventByMonitorArg, startTime, endTime int64) ([]*EventResp, error) {
body := SearchEventBody{
TimeRange: []int64{startTime, endTime},
Limit: 100,
}
for _, arg := range args {
body.Filters = append(body.Filters, EventFilters{
Name: "df_title",
Condition: "or",
Operation: "=",
Value: []string{arg.CheckerName},
})
}
resp := new(EventResponse)
_, err := c.R().SetBodyJsonMarshal(body).SetSuccessResult(resp).
Post(c.BaseURL + "/api/v1/events/abnormal/list")
if err != nil {
return nil, err
}
eventRespMap := make(map[string]*EventResp, 0)
for _, data := range resp.Content.Data {
meta := new(MonitorMeta)
err = json.Unmarshal([]byte(data.DfMeta), meta)
if err != nil {
return nil, errors.Wrapf(err, "SearchEventByChecker unmarshal %s meta failed", data.DfTitle)
}
if e, ok := eventRespMap[meta.CheckerOpt.ID]; ok {
if LevelMap[e.EventLevel] < LevelMap[Level(data.DfStatus)] {
e.EventLevel = Level(data.DfStatus)
}
} else {
eventRespMap[meta.CheckerOpt.ID] = &EventResp{
CheckerName: data.DfTitle,
CheckerID: meta.CheckerOpt.ID,
EventLevel: Level(data.DfStatus),
}
}
}
result := make([]*EventResp, 0)
for id, eventResp := range eventRespMap {
for _, arg := range args {
if arg.CheckerID == id {
result = append(result, eventResp)
}
}
}
return result, nil
}
/*
* Copyright 2023 The KodeRover Authors.
*
* 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 guanceyun
import (
"strconv"
"github.com/pkg/errors"
)
const MonitorPageSize = 100
type MonitorResponse struct {
Code int `json:"code"`
Content []MonitorContent `json:"content"`
ErrorCode string `json:"errorCode"`
Message string `json:"message"`
PageInfo PageInfo `json:"pageInfo"`
Success bool `json:"success"`
TraceID string `json:"traceId"`
}
type CrontabInfo struct {
Crontab string `json:"crontab"`
ID string `json:"id"`
}
type Filters struct {
ID string `json:"id"`
Logic string `json:"logic"`
Name string `json:"name"`
Op string `json:"op"`
Type string `json:"type"`
Value string `json:"value"`
}
type Query struct {
Alias string `json:"alias"`
Code string `json:"code"`
DataSource string `json:"dataSource"`
Field string `json:"field"`
FieldFunc string `json:"fieldFunc"`
FieldType string `json:"fieldType"`
Filters []Filters `json:"filters"`
FuncList []interface{} `json:"funcList"`
GroupBy []interface{} `json:"groupBy"`
GroupByTime string `json:"groupByTime"`
Namespace string `json:"namespace"`
Q string `json:"q"`
Type string `json:"type"`
}
type Querylist struct {
Datasource string `json:"datasource"`
Qtype string `json:"qtype"`
Query Query `json:"query"`
UUID string `json:"uuid"`
}
type Conditions struct {
Alias string `json:"alias"`
Operands []string `json:"operands"`
Operator string `json:"operator"`
}
type Rules struct {
ConditionLogic string `json:"conditionLogic"`
Conditions []Conditions `json:"conditions"`
Status string `json:"status"`
}
type Extend struct {
FuncName string `json:"funcName"`
Querylist []Querylist `json:"querylist"`
Rules []Rules `json:"rules"`
}
type JSONScript struct {
AtAccounts []string `json:"atAccounts"`
AtNoDataAccounts []interface{} `json:"atNoDataAccounts"`
Channels []interface{} `json:"channels"`
CheckerOpt CheckerOpt `json:"checkerOpt"`
Every string `json:"every"`
GroupBy []interface{} `json:"groupBy"`
Interval int `json:"interval"`
Message string `json:"message"`
Name string `json:"name"`
NoDataMessage string `json:"noDataMessage"`
NoDataTitle string `json:"noDataTitle"`
RecoverNeedPeriodCount int `json:"recoverNeedPeriodCount"`
Title string `json:"title"`
Type string `json:"type"`
}
type UpdatorInfo struct {
AcntWsNickname string `json:"acntWsNickname"`
Email string `json:"email"`
Name string `json:"name"`
UserIconURL string `json:"userIconUrl"`
}
type MonitorContent struct {
CreateAt int `json:"createAt"`
CreatedWay string `json:"createdWay"`
Creator string `json:"creator"`
CrontabInfo CrontabInfo `json:"crontabInfo"`
DeleteAt int `json:"deleteAt"`
Extend Extend `json:"extend"`
ID int `json:"id"`
IsSLI bool `json:"isSLI"`
JSONScript JSONScript `json:"jsonScript"`
MonitorName string `json:"monitorName"`
MonitorUUID string `json:"monitorUUID"`
RefKey string `json:"refKey"`
Status int `json:"status"`
Type string `json:"type"`
UpdateAt int `json:"updateAt"`
Updator string `json:"updator"`
UpdatorInfo UpdatorInfo `json:"updatorInfo"`
UUID string `json:"uuid"`
WorkspaceUUID string `json:"workspaceUUID"`
}
type PageInfo struct {
Count int `json:"count"`
PageIndex int `json:"pageIndex"`
PageSize int `json:"pageSize"`
TotalCount int `json:"totalCount"`
}
func (c *Client) ListAllMonitor(search string) (resp []MonitorContent, err error) {
size := MonitorPageSize
for index := 1; ; index++ {
result, total, err := c.ListMonitor(search, size, index)
if err != nil {
return nil, errors.Wrapf(err, "list monitor %d index failed", index)
}
resp = append(resp, result...)
if index*size >= total {
break
}
}
return resp, nil
}
func (c *Client) ListMonitor(search string, pageSize, pageIndex int) ([]MonitorContent, int, error) {
resp := new(MonitorResponse)
params := map[string]string{
"pageSize": strconv.Itoa(pageSize),
"pageIndex": strconv.Itoa(pageIndex),
}
if search != "" {
params["search"] = search
}
_, err := c.R().SetQueryParams(params).SetSuccessResult(resp).
Get(c.BaseURL + "/api/v1/monitor/check/list")
if err != nil {
return nil, 0, err
}
return resp.Content, resp.PageInfo.TotalCount, nil
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册