diff --git a/api/strategy.proto b/api/strategy.proto index 6a0cd0296..9ccc0b078 100644 --- a/api/strategy.proto +++ b/api/strategy.proto @@ -126,6 +126,8 @@ message DomainStrategyItem { expression: 'this.size() > 0' message: "域名不允许为空" }]; + // 判断条件 + Condition condition = 10; // 策略名称 string alert = 20 [(buf.validate.field).cel = { id: "Strategy_alert" diff --git a/cmd/server/houyi/internal/biz/bo/strategy_domain.go b/cmd/server/houyi/internal/biz/bo/strategy_domain.go index 6f55724e7..7033bf73c 100644 --- a/cmd/server/houyi/internal/biz/bo/strategy_domain.go +++ b/cmd/server/houyi/internal/biz/bo/strategy_domain.go @@ -45,6 +45,8 @@ type StrategyDomain struct { Port uint32 `json:"port,omitempty"` // 类型 StrategyType vobj.StrategyType `json:"strategyType,omitempty"` + // 判断条件 + Condition vobj.Condition `json:"condition,omitempty"` } // String 策略转字符串 @@ -73,12 +75,12 @@ func (s *StrategyDomain) IsCompletelyMeet(values []*datasource.Value) (map[strin } for _, point := range values { // 域名证书检测、小于等于阈值都是满足条件的 - if s.StrategyType.IsDomainCertificate() && point.Value <= s.Threshold { - return nil, true + if s.StrategyType.IsDomainCertificate() && s.Condition.Judge(s.Threshold, point.Value) { + return point.Ext, true } // 端口检测、等于阈值才是满足条件的 1开启, 0关闭 - if s.StrategyType.IsDomainPort() && point.Value == s.Threshold { - return nil, true + if s.StrategyType.IsDomainPort() && s.Condition.Judge(s.Threshold, point.Value) { + return point.Ext, true } } return nil, false diff --git a/cmd/server/houyi/internal/biz/bo/strategy_domain_test.go b/cmd/server/houyi/internal/biz/bo/strategy_domain_test.go new file mode 100644 index 000000000..9aef3d0b6 --- /dev/null +++ b/cmd/server/houyi/internal/biz/bo/strategy_domain_test.go @@ -0,0 +1,78 @@ +package bo_test + +import ( + "context" + "testing" + + "github.com/aide-family/moon/cmd/server/houyi/internal/biz/bo" + "github.com/aide-family/moon/pkg/util/types" + "github.com/aide-family/moon/pkg/vobj" +) + +func TestStrategyDomain_Eval(t *testing.T) { + domainStrategy := &bo.StrategyDomain{ + ReceiverGroupIDs: nil, + LabelNotices: nil, + ID: 1, + LevelID: 1, + TeamID: 1, + Status: vobj.StatusEnable, + Alert: "百度域名证书监控", + Threshold: 1, + Labels: vobj.NewLabels(map[string]string{"domain": "baidu.com"}), + Annotations: vobj.NewAnnotations(map[string]string{ + "summary": "百度域名证书监控", + "description": "百度域名证书监控 明细 {{ . }}", + }), + Domain: "baidu.com", + Port: 443, + StrategyType: vobj.StrategyTypeDomainCertificate, + Condition: vobj.ConditionGTE, + } + + eval, err := domainStrategy.Eval(context.Background()) + if err != nil { + t.Error(err) + return + } + + for indexer, point := range eval { + bs, _ := types.Marshal(point.Values) + t.Logf("indexer: %s, point: %+v, meet: ", indexer, string(bs)) + t.Log(domainStrategy.IsCompletelyMeet(point.Values)) + } +} + +func TestStrategyDomainPort_Eval(t *testing.T) { + domainStrategy := &bo.StrategyDomain{ + ReceiverGroupIDs: nil, + LabelNotices: nil, + ID: 1, + LevelID: 1, + TeamID: 1, + Status: vobj.StatusEnable, + Alert: "百度域名端口监控", + Threshold: 1, + Labels: vobj.NewLabels(map[string]string{"domain": "baidu.com"}), + Annotations: vobj.NewAnnotations(map[string]string{ + "summary": "百度域名端口监控", + "description": "百度域名端口监控 明细 {{ . }}", + }), + Domain: "baidu.com", + Port: 1443, + StrategyType: vobj.StrategyTypeDomainPort, + Condition: vobj.ConditionGTE, + } + + eval, err := domainStrategy.Eval(context.Background()) + if err != nil { + t.Error(err) + return + } + + for indexer, point := range eval { + bs, _ := types.Marshal(point.Values) + t.Logf("indexer: %s, point: %+v, meet: ", indexer, string(bs)) + t.Log(domainStrategy.IsCompletelyMeet(point.Values)) + } +} diff --git a/cmd/server/houyi/internal/biz/bo/strategy_http.go b/cmd/server/houyi/internal/biz/bo/strategy_http.go index a66d669ef..501e7e30b 100644 --- a/cmd/server/houyi/internal/biz/bo/strategy_http.go +++ b/cmd/server/houyi/internal/biz/bo/strategy_http.go @@ -155,7 +155,13 @@ func (e *StrategyHTTP) IsCompletelyMeet(values []*datasource.Value) (map[string] } codeMatch := types.MatchStatusCodes(e.StatusCode, int(code)) - responseTimeMatch := e.ResponseTimeCondition.Judge(duration, e.ResponseTime) + responseTimeMatch := e.ResponseTimeCondition.Judge(e.ResponseTime, duration) + if e.StatusCodeCondition.IsEQ() && codeMatch { + codeMatch = true + } + if e.StatusCodeCondition.IsNE() && !codeMatch { + codeMatch = true + } return extJSON, codeMatch && responseTimeMatch } diff --git a/cmd/server/houyi/internal/biz/bo/strategy_http_test.go b/cmd/server/houyi/internal/biz/bo/strategy_http_test.go new file mode 100644 index 000000000..ac17ffa4c --- /dev/null +++ b/cmd/server/houyi/internal/biz/bo/strategy_http_test.go @@ -0,0 +1,47 @@ +package bo_test + +import ( + "context" + "testing" + + "github.com/aide-family/moon/cmd/server/houyi/internal/biz/bo" + "github.com/aide-family/moon/pkg/util/types" + "github.com/aide-family/moon/pkg/vobj" +) + +func TestStrategyHTTP_Eval(t *testing.T) { + httpStrategy := &bo.StrategyHTTP{ + StrategyType: vobj.StrategyTypeHTTP, + URL: "https://baidu.com", + StatusCode: "5xx", + StatusCodeCondition: vobj.ConditionGT, + Headers: map[string]string{"Content-Type": "application/json"}, + Body: "", + Method: vobj.HTTPMethodGet, + ResponseTime: 1, + ResponseTimeCondition: vobj.ConditionGTE, + Labels: vobj.NewLabels(map[string]string{"http": "baidu.com"}), + Annotations: vobj.NewAnnotations(map[string]string{ + "summary": "baidu.com http 探测", + "description": "baidu.com http 探测, 明细 {{ . }}", + }), + ReceiverGroupIDs: nil, + LabelNotices: nil, + TeamID: 1, + Status: vobj.StatusEnable, + Alert: "baidu http 探测", + LevelID: 1, + ID: 1, + } + eval, err := httpStrategy.Eval(context.Background()) + if err != nil { + t.Error(err) + return + } + + for indexer, point := range eval { + bs, _ := types.Marshal(point.Values) + t.Logf("indexer: %s, point: %+v, meet: ", indexer, string(bs)) + t.Log(httpStrategy.IsCompletelyMeet(point.Values)) + } +} diff --git a/cmd/server/palace/internal/service/builder/strategy.go b/cmd/server/palace/internal/service/builder/strategy.go index dc17268c0..7e4ec6284 100644 --- a/cmd/server/palace/internal/service/builder/strategy.go +++ b/cmd/server/palace/internal/service/builder/strategy.go @@ -535,8 +535,9 @@ func (d *doStrategyLevelsBuilder) ToDomainAPI(strategy *bizmodel.Strategy, level Annotations: strategy.Annotations.Map(), Threshold: level.Threshold, Domain: strategy.Expr, + Condition: api.Condition(level.Condition), Alert: strategy.Name, - Port: 0, + Port: 443, StrategyType: api.StrategyType(strategy.StrategyType), } } @@ -569,6 +570,7 @@ func (d *doStrategyLevelsBuilder) ToPortAPI(strategy *bizmodel.Strategy, level * Annotations: strategy.Annotations.Map(), Threshold: level.Threshold, Domain: strategy.Expr, + Condition: api.Condition(vobj.ConditionEQ), Alert: strategy.Name, Port: level.Port, StrategyType: api.StrategyType(strategy.StrategyType), diff --git a/pkg/houyi/datasource/datasource.go b/pkg/houyi/datasource/datasource.go index 5f25fd26e..0f9f320e7 100644 --- a/pkg/houyi/datasource/datasource.go +++ b/pkg/houyi/datasource/datasource.go @@ -19,8 +19,9 @@ import ( type ( // Value 数据源查询值 Value struct { - Value float64 `json:"value"` - Timestamp int64 `json:"timestamp"` + Value float64 `json:"value"` + Timestamp int64 `json:"timestamp"` + Ext map[string]any `json:"ext"` } // Point 数据点 diff --git a/pkg/houyi/datasource/domain.go b/pkg/houyi/datasource/domain.go index 66938b3f1..c39dd9592 100644 --- a/pkg/houyi/datasource/domain.go +++ b/pkg/houyi/datasource/domain.go @@ -17,8 +17,13 @@ import ( func DomainEval(_ context.Context, domain string, port uint32, timeout time.Duration) (map[watch.Indexer]*Point, error) { now := time.Now() points := make(map[watch.Indexer]*Point) + + dsn := domain + if port != 0 { + dsn = domain + ":" + strconv.FormatUint(uint64(port), 10) + } // 创建 TCP 连接 - conn, err := net.DialTimeout("tcp", domain+":"+strconv.FormatUint(uint64(port), 10), timeout) + conn, err := net.DialTimeout("tcp", dsn, timeout) if err != nil { // 超时或者连接失败,返回空切片和错误信息 labels := vobj.NewLabels(map[string]string{vobj.Domain: domain, vobj.DomainPort: strconv.FormatUint(uint64(port), 10)}) @@ -47,8 +52,7 @@ func DomainEval(_ context.Context, domain string, port uint32, timeout time.Dura defer tlsConn.Close() // 创建一个 TLS 的握手 - err = tlsConn.Handshake() - if err != nil { + if err = tlsConn.Handshake(); err != nil { return nil, err } @@ -56,10 +60,8 @@ func DomainEval(_ context.Context, domain string, port uint32, timeout time.Dura certs := tlsConn.ConnectionState().PeerCertificates for _, cert := range certs { labels := vobj.NewLabels(map[string]string{ - vobj.Domain: domain, - vobj.DomainPort: strconv.FormatUint(uint64(port), 10), - vobj.DomainSubject: cert.Subject.CommonName, - vobj.DomainExpiresOn: cert.NotAfter.Format("2006-01-02 15:04:05"), + vobj.Domain: domain, + vobj.DomainPort: strconv.FormatUint(uint64(port), 10), }) points[labels] = &Point{ Labels: labels.Map(), @@ -67,6 +69,10 @@ func DomainEval(_ context.Context, domain string, port uint32, timeout time.Dura { Value: float64(int(cert.NotAfter.Sub(now).Hours() / 24)), Timestamp: now.Unix(), + Ext: map[string]any{ + vobj.DomainSubject: cert.Subject.CommonName, + vobj.DomainExpiresOn: cert.NotAfter.Format("2006-01-02 15:04:05"), + }, }, }, } diff --git a/pkg/houyi/datasource/endpoint_port.go b/pkg/houyi/datasource/endpoint_port.go index 15db47f28..bd79458b4 100644 --- a/pkg/houyi/datasource/endpoint_port.go +++ b/pkg/houyi/datasource/endpoint_port.go @@ -23,7 +23,7 @@ func EndpointPortEval(_ context.Context, endpoint string, port uint32, timeout t Labels: labels.Map(), Values: []*Value{ { - Value: 1, + Value: 0, Timestamp: now.Unix(), }, }, @@ -37,7 +37,7 @@ func EndpointPortEval(_ context.Context, endpoint string, port uint32, timeout t Labels: labels.Map(), Values: []*Value{ { - Value: 0, + Value: 1, Timestamp: now.Unix(), }, }, diff --git a/pkg/vobj/condition.go b/pkg/vobj/condition.go index 3385aafb5..eca8846e9 100644 --- a/pkg/vobj/condition.go +++ b/pkg/vobj/condition.go @@ -29,20 +29,20 @@ const ( ) // Judge 判断是否符合条件 -func (c Condition) Judge(value, threshold float64) bool { +func (c Condition) Judge(threshold, value float64) bool { switch c { case ConditionEQ: return threshold == value case ConditionNE: return threshold != value case ConditionGT: - return threshold > value + return threshold < value case ConditionGTE: - return threshold >= value + return threshold <= value case ConditionLT: - return threshold < value + return threshold > value case ConditionLTE: - return threshold <= value + return threshold >= value default: return false }