From b97a49b9252ae37a6b565082906a4c04e7f034e5 Mon Sep 17 00:00:00 2001 From: junot <49136171+junotx@users.noreply.github.com> Date: Fri, 12 Aug 2022 20:30:33 +0800 Subject: [PATCH] enhance globalrulegroups (#5134) Signed-off-by: junot Signed-off-by: junot --- ...erting.kubesphere.io_globalrulegroups.yaml | 172 ++++++++++++++++++ pkg/api/alerting/v2beta1/types.go | 5 +- pkg/controller/alerting/util.go | 70 +++++-- pkg/kapis/alerting/v2beta1/register.go | 2 + pkg/models/alerting/rulegroup.go | 120 +++++++++++- .../api/alerting/v2beta1/rulegroup_types.go | 100 +++++++++- .../api/alerting/v2beta1/rulegroup_webhook.go | 24 +++ .../alerting/v2beta1/zz_generated.deepcopy.go | 83 +++++++++ 8 files changed, 555 insertions(+), 21 deletions(-) diff --git a/config/crds/alerting.kubesphere.io_globalrulegroups.yaml b/config/crds/alerting.kubesphere.io_globalrulegroups.yaml index e3fb7dc5f..4b2a20089 100644 --- a/config/crds/alerting.kubesphere.io_globalrulegroups.yaml +++ b/config/crds/alerting.kubesphere.io_globalrulegroups.yaml @@ -49,6 +49,23 @@ spec: additionalProperties: type: string type: object + clusterSelector: + description: Only one of its members may be specified. + properties: + inValues: + items: + type: string + type: array + matcher: + properties: + type: + type: string + value: + type: string + required: + - type + type: object + type: object disable: type: boolean expr: @@ -56,6 +73,144 @@ spec: - type: integer - type: string x-kubernetes-int-or-string: true + exprBuilder: + description: If ExprBuilder is not nil, the configured Expr + will be ignored + properties: + node: + properties: + comparator: + type: string + metricThreshold: + description: Only one of its members may be specified. + properties: + cpu: + description: Only one of its members may be specified. + properties: + load15m: + type: number + load1m: + type: number + load5m: + type: number + utilization: + type: number + type: object + disk: + description: Only one of its members may be specified. + properties: + inodeUtilization: + type: number + iopsRead: + description: The unit is io/s + type: number + iopsWrite: + description: The unit is io/s + type: number + spaceAvailable: + description: The unit is bytes + type: number + spaceUtilization: + type: number + throughputRead: + description: The unit is bytes/s + type: number + throughputWrite: + description: The unit is bytes/s + type: number + type: object + memory: + description: Only one of its members may be specified. + properties: + available: + description: The unit is bytes + type: number + utilization: + type: number + type: object + network: + description: Only one of its members may be specified. + properties: + receivedRate: + description: The unit is bit/s + type: number + transmittedRate: + description: The unit is bit/s + type: number + type: object + pod: + description: Only one of its members may be specified. + properties: + abnormalRatio: + type: number + utilization: + type: number + type: object + type: object + names: + items: + type: string + type: array + required: + - comparator + - metricThreshold + - names + type: object + workload: + properties: + comparator: + type: string + kind: + type: string + metricThreshold: + description: Only one of its members may be specified. + properties: + cpu: + description: Only one of its members may be specified. + properties: + usage: + description: The unit is core + type: number + type: object + memory: + description: Only one of its members may be specified. + properties: + usage: + description: The memory usage contains cache + The unit is bytes + type: number + usageWoCache: + description: The memory usage contains no cache + The unit is bytes + type: number + type: object + network: + description: Only one of its members may be specified. + properties: + receivedRate: + description: The unit is bit/s + type: number + transmittedRate: + description: The unit is bit/s + type: number + type: object + replica: + description: Only one of its members may be specified. + properties: + unavailableRatio: + type: number + type: object + type: object + names: + items: + type: string + type: array + required: + - comparator + - kind + - names + type: object + type: object for: description: 'Duration is a valid time unit Supported units: y, w, d, h, m, s, ms Examples: `30s`, `1m`, `1h20m15s`' @@ -65,6 +220,23 @@ spec: additionalProperties: type: string type: object + namespaceSelector: + description: Only one of its members may be specified. + properties: + inValues: + items: + type: string + type: array + matcher: + properties: + type: + type: string + value: + type: string + required: + - type + type: object + type: object severity: type: string required: diff --git a/pkg/api/alerting/v2beta1/types.go b/pkg/api/alerting/v2beta1/types.go index 720faa587..10adb383a 100644 --- a/pkg/api/alerting/v2beta1/types.go +++ b/pkg/api/alerting/v2beta1/types.go @@ -25,11 +25,13 @@ import ( const ( // for rulegroup/alert - FieldState = "state" + FieldState = "state" + FieldBuiltin = "builtin" // for rulegroup FieldRuleGroupEvaluationTime = "evaluationTime" FieldRuleGroupLastEvaluation = "lastEvalution" + // for alert FieldAlertLabelFilters = "label_filters" FieldAlertActiveAt = "activeAt" @@ -69,6 +71,7 @@ type RuleGroupStatus struct { } type RuleStatus struct { + Expr string `json:"expr,omitempty" description:"expression evaluated, for global rules only"` State string `json:"state,omitempty" description:"state of a rule, one of firing, pending or inactive depending on its alerts"` Health string `json:"health,omitempty" description:"health state of a rule, one of ok, err, unknown depending on the last execution result"` LastError string `json:"lastError,omitempty" description:"error of the last evaluation"` diff --git a/pkg/controller/alerting/util.go b/pkg/controller/alerting/util.go index 62d9175b7..63b6eaea3 100644 --- a/pkg/controller/alerting/util.go +++ b/pkg/controller/alerting/util.go @@ -53,10 +53,13 @@ const ( SourceGroupResourceLabelValueEnableTrue = "true" SourceGroupResourceLabelValueEnableFalse = "false" - // label keys in PrometheusRule.metadata.labels + // for PrometheusRule.metadata.labels PrometheusRuleResourceLabelKeyOwnerNamespace = "alerting.kubesphere.io/owner_namespace" PrometheusRuleResourceLabelKeyOwnerCluster = "alerting.kubesphere.io/owner_cluster" PrometheusRuleResourceLabelKeyRuleLevel = "alerting.kubesphere.io/rule_level" + PrometheusRuleResourceLabelKeyBuiltin = "alerting.kubesphere.io/builtin" + PrometheusRuleResourceLabelValueBuiltinTrue = "true" + PrometheusRuleResourceLabelValueBuiltinFalse = "false" // name prefix for PrometheusRule PrometheusRulePrefix = "alertrules-" @@ -73,23 +76,40 @@ var maxConfigMapDataSize = int(float64(corev1.MaxSecretSize) * 0.5) type enforceRuleFunc func(rule *promresourcesv1.Rule) error +type EnforceExprFunc func(expr string) (string, error) + +var emptyEnforceExprFunc = func(expr string) (string, error) { + return expr, nil +} + +func CreateEnforceExprFunc(enforceRuleMatchers []*promlabels.Matcher) EnforceExprFunc { + if len(enforceRuleMatchers) > 0 { + enforcer := injectproxy.NewEnforcer(enforceRuleMatchers...) + return func(expr string) (string, error) { + parsedExpr, err := parser.ParseExpr(expr) + if err != nil { + return expr, err + } + if err := enforcer.EnforceNode(parsedExpr); err != nil { + return expr, err + } + return parsedExpr.String(), nil + } + } + return emptyEnforceExprFunc +} + func createEnforceRuleFuncs(enforceRuleMatchers []*promlabels.Matcher, enforceRuleLabels map[string]string) []enforceRuleFunc { var enforceFuncs []enforceRuleFunc // enforce func for rule.expr if len(enforceRuleMatchers) > 0 { - enforcer := injectproxy.NewEnforcer(enforceRuleMatchers...) + enforceExprFunc := CreateEnforceExprFunc(enforceRuleMatchers) enforceFuncs = append(enforceFuncs, func(rule *promresourcesv1.Rule) error { - if enforcer != nil { - expr := rule.Expr.String() - parsedExpr, err := parser.ParseExpr(expr) - if err != nil { - return err - } - if err = enforcer.EnforceNode(parsedExpr); err != nil { - return err - } - rule.Expr = intstr.FromString(parsedExpr.String()) + expr, err := enforceExprFunc(rule.Expr.String()) + if err != nil { + return err } + rule.Expr = intstr.FromString(expr) return nil }) } @@ -109,10 +129,10 @@ func createEnforceRuleFuncs(enforceRuleMatchers []*promlabels.Matcher, enforceRu } func makePrometheusRuleGroups(log logr.Logger, groupList client.ObjectList, - enforceFuncs ...enforceRuleFunc) ([]*promresourcesv1.RuleGroup, error) { + commonEnforceFuncs ...enforceRuleFunc) ([]*promresourcesv1.RuleGroup, error) { var rulegroups []*promresourcesv1.RuleGroup - convertRule := func(rule *alertingv2beta1.Rule) (*promresourcesv1.Rule, error) { + convertRule := func(rule *alertingv2beta1.Rule, enforceFuncs ...enforceRuleFunc) (*promresourcesv1.Rule, error) { if rule.Disable { // ignoring disabled rule return nil, nil } @@ -135,6 +155,8 @@ func makePrometheusRuleGroups(log logr.Logger, groupList client.ObjectList, Annotations: rule.Annotations, } + enforceFuncs = append(enforceFuncs, commonEnforceFuncs...) + for _, f := range enforceFuncs { if f == nil { continue @@ -193,7 +215,8 @@ func makePrometheusRuleGroups(log logr.Logger, groupList client.ObjectList, for _, group := range list.Items { var prules []promresourcesv1.Rule for _, rule := range group.Spec.Rules { - prule, err := convertRule(&rule.Rule) + + prule, err := convertRule(&rule.Rule, createEnforceRuleFuncs(ParseGlobalRuleEnforceMatchers(&rule), nil)...) if err != nil { log.WithValues("globalrulegroup", group.Name).Error(err, "failed to convert") continue @@ -214,6 +237,23 @@ func makePrometheusRuleGroups(log logr.Logger, groupList client.ObjectList, return rulegroups, nil } +func ParseGlobalRuleEnforceMatchers(rule *alertingv2beta1.GlobalRule) []*promlabels.Matcher { + var enforceRuleMatchers []*promlabels.Matcher + if rule.ClusterSelector != nil { + matcher := rule.ClusterSelector.ParseToMatcher(RuleLabelKeyCluster) + if matcher != nil { + enforceRuleMatchers = append(enforceRuleMatchers, matcher) + } + } + if rule.NamespaceSelector != nil { + matcher := rule.NamespaceSelector.ParseToMatcher(RuleLabelKeyNamespace) + if matcher != nil { + enforceRuleMatchers = append(enforceRuleMatchers, matcher) + } + } + return enforceRuleMatchers +} + func makePrometheusRuleResources(rulegroups []*promresourcesv1.RuleGroup, namespace, namePrefix string, labels map[string]string, ownerReferences []metav1.OwnerReference) ([]*promresourcesv1.PrometheusRule, error) { diff --git a/pkg/kapis/alerting/v2beta1/register.go b/pkg/kapis/alerting/v2beta1/register.go index bc6699dc6..68940fbcb 100644 --- a/pkg/kapis/alerting/v2beta1/register.go +++ b/pkg/kapis/alerting/v2beta1/register.go @@ -108,6 +108,7 @@ func AddToContainer(container *restful.Container, ksclient kubesphere.Interface, Param(ws.QueryParameter(query.ParameterAscending, "sort parameters, e.g. reverse=true").Required(false).DefaultValue("ascending=false")). Param(ws.QueryParameter(query.ParameterOrderBy, "sort parameters, e.g. orderBy=createTime")). Param(ws.QueryParameter(kapialertingv2beta1.FieldState, "state of a rulegroup, one of `firing`, `pending`, `inactive` depending on its rules")). + Param(ws.QueryParameter(kapialertingv2beta1.FieldBuiltin, "filter rule groups, `true` for built-in rule groups and `false` for custom rule groups")). Returns(http.StatusOK, kapi.StatusOK, kapi.ListResult{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag})) @@ -126,6 +127,7 @@ func AddToContainer(container *restful.Container, ksclient kubesphere.Interface, Param(ws.QueryParameter(query.ParameterOrderBy, "sort parameters, one of `activeAt`. e.g. orderBy=activeAt")). Param(ws.QueryParameter(kapialertingv2beta1.FieldState, "state, one of `firing`, `pending`")). Param(ws.QueryParameter(kapialertingv2beta1.FieldAlertLabelFilters, "label filters, concatenating multiple filters with commas, equal symbol for exact query, wave symbol for fuzzy query e.g. name~a").DataFormat("key=%s,key~%s")). + Param(ws.QueryParameter(kapialertingv2beta1.FieldBuiltin, "filter alerts, `true` for alerts from built-in rule groups and `false` for alerts from custom rule groups")). Returns(http.StatusOK, kapi.StatusOK, kapi.ListResult{}). Metadata(restfulspec.KeyOpenAPITags, []string{constants.AlertingTag})) diff --git a/pkg/models/alerting/rulegroup.go b/pkg/models/alerting/rulegroup.go index af72c7bee..8453dbca4 100644 --- a/pkg/models/alerting/rulegroup.go +++ b/pkg/models/alerting/rulegroup.go @@ -22,6 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" "kubesphere.io/kubesphere/pkg/api" kapialertingv2beta1 "kubesphere.io/kubesphere/pkg/api/alerting/v2beta1" @@ -106,6 +107,14 @@ func (o *ruleGroupOperator) listRuleGroups(ctx context.Context, namespace string statusg, ok := statusRuleGroupMap[g.Name] if ok && len(statusg.Rules) == len(g.Spec.Rules) { // assure that they are the same rulegroups copyRuleGroupStatus(statusg, &g.Status) + } else { + // for rules not loaded by rule reloader (eg.thanos) yet + for range g.Spec.Rules { + g.Status.RulesStatus = append(g.Status.RulesStatus, kapialertingv2beta1.RuleStatus{ + State: stateInactiveString, + Health: string(promrules.HealthUnknown), + }) + } } groups[i] = g } @@ -272,12 +281,23 @@ func (o *ruleGroupOperator) GetRuleGroup(ctx context.Context, namespace, name st return nil, err } + var setStatus bool for _, g := range statusRuleGroups { if g.Name == resourceRuleGroup.Name && len(g.Rules) == len(resourceRuleGroup.Spec.Rules) { copyRuleGroupStatus(g, &ret.Status) + setStatus = true break } } + if !setStatus { + // for rules not loaded by rule reloader (eg.thanos) yet + for range ret.Spec.Rules { + ret.Status.RulesStatus = append(ret.Status.RulesStatus, kapialertingv2beta1.RuleStatus{ + State: stateInactiveString, + Health: string(promrules.HealthUnknown), + }) + } + } return ret, nil } @@ -324,6 +344,14 @@ func (o *ruleGroupOperator) listClusterRuleGroups(ctx context.Context, selector statusg, ok := statusRuleGroupMap[g.Name] if ok && len(statusg.Rules) == len(g.Spec.Rules) { copyRuleGroupStatus(statusg, &g.Status) + } else { + // for rules not loaded by rule reloader (eg.thanos) yet + for range g.Spec.Rules { + g.Status.RulesStatus = append(g.Status.RulesStatus, kapialertingv2beta1.RuleStatus{ + State: stateInactiveString, + Health: string(promrules.HealthUnknown), + }) + } } groups[i] = g } @@ -410,12 +438,23 @@ func (o *ruleGroupOperator) GetClusterRuleGroup(ctx context.Context, name string return nil, err } + var setStatus bool for _, g := range statusRuleGroups { if g.Name == resourceRuleGroup.Name && len(g.Rules) == len(resourceRuleGroup.Spec.Rules) { copyRuleGroupStatus(g, &ret.Status) + setStatus = true break } } + if !setStatus { + // for rules not loaded by rule reloader (eg.thanos) yet + for range ret.Spec.Rules { + ret.Status.RulesStatus = append(ret.Status.RulesStatus, kapialertingv2beta1.RuleStatus{ + State: stateInactiveString, + Health: string(promrules.HealthUnknown), + }) + } + } return ret, nil } @@ -463,6 +502,21 @@ func (o *ruleGroupOperator) listGlobalRuleGroups(ctx context.Context, selector l statusg, ok := statusRuleGroupMap[g.Name] if ok && len(statusg.Rules) == len(g.Spec.Rules) { copyRuleGroupStatus(statusg, &g.Status) + } else { + // for rules not loaded by rule reloader (eg.thanos) yet + for _, rule := range g.Spec.Rules { + ruleStatus := kapialertingv2beta1.RuleStatus{ + State: stateInactiveString, + Health: string(promrules.HealthUnknown), + } + enforceExprFunc := controller.CreateEnforceExprFunc(controller.ParseGlobalRuleEnforceMatchers(&rule)) + expr, err := enforceExprFunc(rule.Expr.String()) + if err != nil { + return nil, err + } + ruleStatus.Expr = expr + g.Status.RulesStatus = append(g.Status.RulesStatus, ruleStatus) + } } groups[i] = g } @@ -472,7 +526,22 @@ func (o *ruleGroupOperator) listGlobalRuleGroups(ctx context.Context, selector l func (o *ruleGroupOperator) ListGlobalRuleGroups(ctx context.Context, queryParam *query.Query) (*api.ListResult, error) { - groups, err := o.listGlobalRuleGroups(ctx, queryParam.Selector()) + selector := queryParam.Selector() + if val, ok := queryParam.Filters[kapialertingv2beta1.FieldBuiltin]; ok { + // add match requirement to the selector to select only builtin or custom rulegroups + var operator selection.Operator + if val == controller.PrometheusRuleResourceLabelValueBuiltinTrue { + operator = selection.Equals + } else { + operator = selection.NotEquals + } + requirement, _ := labels.NewRequirement( + controller.PrometheusRuleResourceLabelKeyBuiltin, + operator, + []string{controller.PrometheusRuleResourceLabelValueBuiltinTrue}) + selector = selector.Add(*requirement) + } + groups, err := o.listGlobalRuleGroups(ctx, selector) if err != nil { return nil, err } @@ -486,6 +555,9 @@ func (o *ruleGroupOperator) ListGlobalRuleGroups(ctx context.Context, return resources.DefaultObjectMetaCompare( left.(*kapialertingv2beta1.GlobalRuleGroup).ObjectMeta, right.(*kapialertingv2beta1.GlobalRuleGroup).ObjectMeta, field) }, func(obj runtime.Object, filter query.Filter) bool { + if filter.Field == kapialertingv2beta1.FieldBuiltin { // ignoring this filter because it is filtered at the front + return true + } hit, selected := o.filterRuleGroupStatus(&obj.(*kapialertingv2beta1.GlobalRuleGroup).Status, filter) if hit { return selected @@ -497,7 +569,22 @@ func (o *ruleGroupOperator) ListGlobalRuleGroups(ctx context.Context, func (o *ruleGroupOperator) ListGlobalAlerts(ctx context.Context, queryParam *query.Query) (*api.ListResult, error) { - groups, err := o.listGlobalRuleGroups(ctx, labels.Everything()) + selector := labels.Everything() + if val, ok := queryParam.Filters[kapialertingv2beta1.FieldBuiltin]; ok { + // add match requirement to the selector to select only builtin or custom rulegroups + var operator selection.Operator + if val == controller.PrometheusRuleResourceLabelValueBuiltinTrue { + operator = selection.Equals + } else { + operator = selection.NotEquals + } + requirement, _ := labels.NewRequirement( + controller.PrometheusRuleResourceLabelKeyBuiltin, + operator, + []string{controller.PrometheusRuleResourceLabelValueBuiltinTrue}) + selector = selector.Add(*requirement) + } + groups, err := o.listGlobalRuleGroups(ctx, selector) if err != nil { return nil, err } @@ -549,12 +636,30 @@ func (o *ruleGroupOperator) GetGlobalRuleGroup(ctx context.Context, name string) return nil, err } + var setStatus bool for _, g := range statusRuleGroups { if g.Name == resourceRuleGroup.Name && len(g.Rules) == len(resourceRuleGroup.Spec.Rules) { copyRuleGroupStatus(g, &ret.Status) + setStatus = true break } } + if !setStatus { + // for rules not loaded by rule reloader (eg.thanos) yet + for _, rule := range ret.Spec.Rules { + ruleStatus := kapialertingv2beta1.RuleStatus{ + State: stateInactiveString, + Health: string(promrules.HealthUnknown), + } + enforceExprFunc := controller.CreateEnforceExprFunc(controller.ParseGlobalRuleEnforceMatchers(&rule)) + expr, err := enforceExprFunc(rule.Expr.String()) + if err != nil { + return nil, err + } + ruleStatus.Expr = expr + ret.Status.RulesStatus = append(ret.Status.RulesStatus, ruleStatus) + } + } return ret, nil } @@ -582,14 +687,21 @@ func copyRuleGroupStatus(source *alerting.RuleGroup, target *kapialertingv2beta1 Value: alert.Value, }) } - target.RulesStatus = append(target.RulesStatus, kapialertingv2beta1.RuleStatus{ + ruleStatus := kapialertingv2beta1.RuleStatus{ State: rule.State, Health: rule.Health, LastError: rule.LastError, EvaluationTime: rule.EvaluationTime, LastEvaluation: rule.LastEvaluation, Alerts: alerts, - }) + } + if len(rule.Labels) > 0 { + if level, ok := rule.Labels[controller.RuleLabelKeyRuleLevel]; ok && + level == string(controller.RuleLevelGlobal) { // provided only for global rules + ruleStatus.Expr = rule.Query + } + } + target.RulesStatus = append(target.RulesStatus, ruleStatus) } target.State = groupState.String() } diff --git a/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_types.go b/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_types.go index 9ad78cc33..e0e8f8369 100644 --- a/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_types.go +++ b/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_types.go @@ -20,6 +20,7 @@ import ( "fmt" "strings" + "github.com/prometheus/prometheus/pkg/labels" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -33,6 +34,8 @@ type Comparator string type Severity string +type MatchType string + const ( ComparatorLT Comparator = "<" ComparatorLE Comparator = "<=" @@ -42,6 +45,11 @@ const ( SeverityWarning Severity = "warning" SeverityError Severity = "error" SeverityCritical Severity = "critical" + + MatchEqual = "=" + MatchNotEqual = "!=" + MatchRegexp = "=~" + MatchNotRegexp = "!~" ) type Rule struct { @@ -71,7 +79,91 @@ type ClusterRule struct { } type GlobalRule struct { - Rule `json:",inline"` + ClusterSelector *MetricLabelSelector `json:"clusterSelector,omitempty"` + NamespaceSelector *MetricLabelSelector `json:"namespaceSelector,omitempty"` + Rule `json:",inline"` + // If ExprBuilder is not nil, the configured Expr will be ignored + ExprBuilder *GlobalRuleExprBuilder `json:"exprBuilder,omitempty"` +} + +// Only one of its members may be specified. +type MetricLabelSelector struct { + InValues []string `json:"inValues,omitempty"` + Matcher *Matcher `json:"matcher,omitempty"` +} + +func (s *MetricLabelSelector) ParseToMatcher(labelName string) *labels.Matcher { + if s == nil { + return nil + } + if len(s.InValues) == 1 { + return &labels.Matcher{ + Type: labels.MatchEqual, + Name: labelName, + Value: s.InValues[0], + } + } + if len(s.InValues) > 1 { + return &labels.Matcher{ + Type: labels.MatchRegexp, + Name: labelName, + Value: fmt.Sprintf("(%s)", strings.Join(s.InValues, "|")), + } + } + if s.Matcher != nil { + var mtype labels.MatchType + switch s.Matcher.Type { + case MatchEqual: + mtype = labels.MatchEqual + case MatchNotEqual: + mtype = labels.MatchNotEqual + case MatchRegexp: + mtype = labels.MatchRegexp + case MatchNotRegexp: + mtype = labels.MatchNotRegexp + default: + return nil + } + return &labels.Matcher{ + Type: mtype, + Name: labelName, + Value: s.Matcher.Value, + } + } + return nil +} + +func (s *MetricLabelSelector) Validate() error { + if s.Matcher != nil { + return s.Matcher.Validate() + } + return nil +} + +type Matcher struct { + Type MatchType `json:"type"` + Value string `json:"value,omitempty"` +} + +func (m *Matcher) Validate() error { + var mtype labels.MatchType + switch m.Type { + case MatchEqual: + mtype = labels.MatchEqual + case MatchNotEqual: + mtype = labels.MatchNotEqual + case MatchRegexp: + mtype = labels.MatchRegexp + case MatchNotRegexp: + mtype = labels.MatchNotRegexp + default: + return fmt.Errorf("unsupported match type [%s]", m.Type) + } + _, err := labels.NewMatcher(mtype, "name", m.Value) + if err != nil { + return fmt.Errorf("invalid matcher: %v", err) + } + return nil } type NamespaceRuleExprBuilder struct { @@ -82,6 +174,12 @@ type ClusterRuleExprBuilder struct { Node *NodeExprBuilder `json:"node,omitempty"` } +// Only one of its members may be specified. +type GlobalRuleExprBuilder struct { + Workload *WorkloadExprBuilder `json:"workload,omitempty"` + Node *NodeExprBuilder `json:"node,omitempty"` +} + type WorkloadKind string const ( diff --git a/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_webhook.go b/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_webhook.go index 3ddd04e79..7df251e82 100644 --- a/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_webhook.go +++ b/staging/src/kubesphere.io/api/alerting/v2beta1/rulegroup_webhook.go @@ -212,6 +212,20 @@ func (r *GlobalRuleGroup) SetupWebhookWithManager(mgr ctrl.Manager) error { var _ webhook.Defaulter = &GlobalRuleGroup{} func (r *GlobalRuleGroup) Default() { + log := globalrulegrouplog.WithValues("name", r.Name) + log.Info("default") + + for i := range r.Spec.Rules { + rule := r.Spec.Rules[i] + if rule.ExprBuilder != nil { + if rule.ExprBuilder.Node != nil { + rule.Expr = intstr.FromString(rule.ExprBuilder.Node.Build()) + } else if rule.ExprBuilder.Workload != nil { + rule.Expr = intstr.FromString(rule.ExprBuilder.Workload.Build()) + } + } + r.Spec.Rules[i] = rule + } } var _ webhook.Validator = &GlobalRuleGroup{} @@ -231,6 +245,16 @@ func (r *GlobalRuleGroup) Validate() error { var rules []Rule for _, r := range r.Spec.Rules { + if r.ClusterSelector != nil { + if err := r.ClusterSelector.Validate(); err != nil { + return err + } + } + if r.NamespaceSelector != nil { + if err := r.NamespaceSelector.Validate(); err != nil { + return err + } + } rules = append(rules, r.Rule) } var err = validateRules(log, r.Name, r.Spec.Interval, rules) diff --git a/staging/src/kubesphere.io/api/alerting/v2beta1/zz_generated.deepcopy.go b/staging/src/kubesphere.io/api/alerting/v2beta1/zz_generated.deepcopy.go index c46dc8429..9f4f0579a 100644 --- a/staging/src/kubesphere.io/api/alerting/v2beta1/zz_generated.deepcopy.go +++ b/staging/src/kubesphere.io/api/alerting/v2beta1/zz_generated.deepcopy.go @@ -171,7 +171,22 @@ func (in *ClusterRuleGroupStatus) DeepCopy() *ClusterRuleGroupStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GlobalRule) DeepCopyInto(out *GlobalRule) { *out = *in + if in.ClusterSelector != nil { + in, out := &in.ClusterSelector, &out.ClusterSelector + *out = new(MetricLabelSelector) + (*in).DeepCopyInto(*out) + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(MetricLabelSelector) + (*in).DeepCopyInto(*out) + } in.Rule.DeepCopyInto(&out.Rule) + if in.ExprBuilder != nil { + in, out := &in.ExprBuilder, &out.ExprBuilder + *out = new(GlobalRuleExprBuilder) + (*in).DeepCopyInto(*out) + } return } @@ -185,6 +200,32 @@ func (in *GlobalRule) DeepCopy() *GlobalRule { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalRuleExprBuilder) DeepCopyInto(out *GlobalRuleExprBuilder) { + *out = *in + if in.Workload != nil { + in, out := &in.Workload, &out.Workload + *out = new(WorkloadExprBuilder) + (*in).DeepCopyInto(*out) + } + if in.Node != nil { + in, out := &in.Node, &out.Node + *out = new(NodeExprBuilder) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalRuleExprBuilder. +func (in *GlobalRuleExprBuilder) DeepCopy() *GlobalRuleExprBuilder { + if in == nil { + return nil + } + out := new(GlobalRuleExprBuilder) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GlobalRuleGroup) DeepCopyInto(out *GlobalRuleGroup) { *out = *in @@ -285,6 +326,48 @@ func (in *GlobalRuleGroupStatus) DeepCopy() *GlobalRuleGroupStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Matcher) DeepCopyInto(out *Matcher) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Matcher. +func (in *Matcher) DeepCopy() *Matcher { + if in == nil { + return nil + } + out := new(Matcher) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetricLabelSelector) DeepCopyInto(out *MetricLabelSelector) { + *out = *in + if in.InValues != nil { + in, out := &in.InValues, &out.InValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Matcher != nil { + in, out := &in.Matcher, &out.Matcher + *out = new(Matcher) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricLabelSelector. +func (in *MetricLabelSelector) DeepCopy() *MetricLabelSelector { + if in == nil { + return nil + } + out := new(MetricLabelSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceRule) DeepCopyInto(out *NamespaceRule) { *out = *in