kubesphere.io/api@v0.0.0-20231107125330-c9a03957060c/alerting/v2beta1/rulegroup_webhook.go (about)

     1  /*
     2  Copyright 2020 KubeSphere Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v2beta1
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/go-logr/logr"
    23  	"github.com/prometheus/common/model"
    24  	"github.com/prometheus/prometheus/model/rulefmt"
    25  	yaml "gopkg.in/yaml.v3"
    26  	runtime "k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/util/intstr"
    28  	"k8s.io/apimachinery/pkg/util/uuid"
    29  	ctrl "sigs.k8s.io/controller-runtime"
    30  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    31  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    32  )
    33  
    34  var rulegrouplog = logf.Log.WithName("rulegroup")
    35  
    36  const (
    37  	RuleLabelKeyRuleId   = "rule_id"
    38  	MaxRuleCountPerGroup = 40
    39  )
    40  
    41  func (r *RuleGroup) SetupWebhookWithManager(mgr ctrl.Manager) error {
    42  	return ctrl.NewWebhookManagedBy(mgr).
    43  		For(r).
    44  		Complete()
    45  }
    46  
    47  var _ webhook.Defaulter = &RuleGroup{}
    48  
    49  func (r *RuleGroup) Default() {
    50  	log := rulegrouplog.WithValues("name", r.Namespace+"/"+r.Name)
    51  	log.Info("default")
    52  	for i := range r.Spec.Rules {
    53  		rule := r.Spec.Rules[i]
    54  		if rule.ExprBuilder != nil {
    55  			if rule.ExprBuilder.Workload != nil {
    56  				rule.Expr = intstr.FromString(rule.ExprBuilder.Workload.Build())
    57  			}
    58  		}
    59  		setRuleId(&rule.Rule)
    60  		r.Spec.Rules[i] = rule
    61  	}
    62  }
    63  
    64  func setRuleId(rule *Rule) {
    65  	var setRuleId = true
    66  	if len(rule.Labels) > 0 {
    67  		if _, ok := rule.Labels[RuleLabelKeyRuleId]; ok {
    68  			setRuleId = false
    69  		}
    70  	}
    71  	if setRuleId {
    72  		if rule.Labels == nil {
    73  			rule.Labels = make(map[string]string)
    74  		}
    75  		rule.Labels[RuleLabelKeyRuleId] = string(uuid.NewUUID())
    76  	}
    77  }
    78  
    79  var _ webhook.Validator = &RuleGroup{}
    80  
    81  func (r *RuleGroup) ValidateCreate() error {
    82  	return r.Validate()
    83  }
    84  func (r *RuleGroup) ValidateUpdate(old runtime.Object) error {
    85  	return r.Validate()
    86  }
    87  func (r *RuleGroup) ValidateDelete() error {
    88  	return nil
    89  }
    90  func (r *RuleGroup) Validate() error {
    91  	log := rulegrouplog.WithValues("name", r.Namespace+"/"+r.Name)
    92  	log.Info("validate")
    93  
    94  	if len(r.Spec.Rules) > MaxRuleCountPerGroup {
    95  		return fmt.Errorf("the rule group has %d rules, exceeding the max count (%d)", len(r.Spec.Rules), MaxRuleCountPerGroup)
    96  	}
    97  
    98  	var rules []Rule
    99  	for _, r := range r.Spec.Rules {
   100  		rules = append(rules, r.Rule)
   101  	}
   102  	var err = validateRules(log, r.Name, r.Spec.Interval, rules)
   103  	if err == errorEmptyExpr {
   104  		return fmt.Errorf("one of 'expr' and 'exprBuilder.workload' must be set for a RuleGroup")
   105  	}
   106  	return err
   107  }
   108  
   109  type ruleGroup struct {
   110  	Name     string         `yaml:"name"`
   111  	Interval model.Duration `yaml:"interval,omitempty"`
   112  	Rules    []rulefmt.Rule `yaml:"rules"`
   113  }
   114  
   115  type ruleGroups struct {
   116  	Groups []ruleGroup `yaml:"groups"`
   117  }
   118  
   119  var errorEmptyExpr = fmt.Errorf("'expr' is empty")
   120  
   121  func validateRules(log logr.Logger, groupName, groupInterval string, rules []Rule) error {
   122  	durationStr := groupInterval
   123  	if durationStr == "" {
   124  		durationStr = "1m"
   125  	}
   126  	interval, err := model.ParseDuration(durationStr)
   127  	if err != nil {
   128  		return fmt.Errorf("invalid 'interval': %s", durationStr)
   129  	}
   130  
   131  	var g = ruleGroup{
   132  		Name:     groupName,
   133  		Interval: interval,
   134  	}
   135  
   136  	for i := range rules {
   137  		rule := rules[i]
   138  		if rule.Alert == "" {
   139  			return fmt.Errorf("'alert' cannot be empty")
   140  		}
   141  		if rule.Expr.String() == "" {
   142  			return errorEmptyExpr
   143  		}
   144  		durationStr := string(rule.For)
   145  		if durationStr == "" {
   146  			durationStr = "0"
   147  		}
   148  		forDuration, err := model.ParseDuration(durationStr)
   149  		if err != nil {
   150  			return fmt.Errorf("invalid 'for': %s", durationStr)
   151  		}
   152  		g.Rules = append(g.Rules, rulefmt.Rule{
   153  			Alert:       rule.Alert,
   154  			Expr:        rule.Expr.String(),
   155  			For:         forDuration,
   156  			Labels:      rule.Labels,
   157  			Annotations: rule.Annotations,
   158  		})
   159  	}
   160  
   161  	var gs = ruleGroups{}
   162  	gs.Groups = append(gs.Groups, g)
   163  
   164  	content, err := yaml.Marshal(gs)
   165  	if err != nil {
   166  		return fmt.Errorf("failed to marshal content: %v", err)
   167  	}
   168  	_, errs := rulefmt.Parse(content)
   169  
   170  	if len(errs) > 0 {
   171  		for _, err := range errs {
   172  			log.Info(fmt.Sprintf("invalid rule: %v", err))
   173  		}
   174  		return fmt.Errorf("invalid rules: %v", errs)
   175  	}
   176  	return nil
   177  }
   178  
   179  var clusterrulegrouplog = logf.Log.WithName("clusterrulegroup")
   180  
   181  func (r *ClusterRuleGroup) SetupWebhookWithManager(mgr ctrl.Manager) error {
   182  	return ctrl.NewWebhookManagedBy(mgr).
   183  		For(r).
   184  		Complete()
   185  }
   186  
   187  var _ webhook.Defaulter = &ClusterRuleGroup{}
   188  
   189  func (r *ClusterRuleGroup) Default() {
   190  	log := clusterrulegrouplog.WithValues("name", r.Name)
   191  	log.Info("default")
   192  
   193  	for i := range r.Spec.Rules {
   194  		rule := r.Spec.Rules[i]
   195  		if rule.ExprBuilder != nil {
   196  			if rule.ExprBuilder.Node != nil {
   197  				rule.Expr = intstr.FromString(rule.ExprBuilder.Node.Build())
   198  			}
   199  		}
   200  		setRuleId(&rule.Rule)
   201  		r.Spec.Rules[i] = rule
   202  	}
   203  }
   204  
   205  var _ webhook.Validator = &ClusterRuleGroup{}
   206  
   207  func (r *ClusterRuleGroup) ValidateCreate() error {
   208  	return r.Validate()
   209  }
   210  func (r *ClusterRuleGroup) ValidateUpdate(old runtime.Object) error {
   211  	return r.Validate()
   212  }
   213  func (r *ClusterRuleGroup) ValidateDelete() error {
   214  	return nil
   215  }
   216  func (r *ClusterRuleGroup) Validate() error {
   217  	log := clusterrulegrouplog.WithValues("name", r.Name)
   218  	log.Info("validate")
   219  
   220  	if len(r.Spec.Rules) > MaxRuleCountPerGroup {
   221  		return fmt.Errorf("the rule group has %d rules, exceeding the max count (%d)", len(r.Spec.Rules), MaxRuleCountPerGroup)
   222  	}
   223  
   224  	var rules []Rule
   225  	for _, r := range r.Spec.Rules {
   226  		rules = append(rules, r.Rule)
   227  	}
   228  	var err = validateRules(log, r.Name, r.Spec.Interval, rules)
   229  	if err == errorEmptyExpr {
   230  		return fmt.Errorf("one of 'expr' and 'exprBuilder.node' must be set for a ClusterRuleGroup")
   231  	}
   232  	return err
   233  }
   234  
   235  var globalrulegrouplog = logf.Log.WithName("globalrulegroup")
   236  
   237  func (r *GlobalRuleGroup) SetupWebhookWithManager(mgr ctrl.Manager) error {
   238  	return ctrl.NewWebhookManagedBy(mgr).
   239  		For(r).
   240  		Complete()
   241  }
   242  
   243  var _ webhook.Defaulter = &GlobalRuleGroup{}
   244  
   245  func (r *GlobalRuleGroup) Default() {
   246  	log := globalrulegrouplog.WithValues("name", r.Name)
   247  	log.Info("default")
   248  
   249  	for i := range r.Spec.Rules {
   250  		rule := r.Spec.Rules[i]
   251  		if rule.ExprBuilder != nil {
   252  			if rule.ExprBuilder.Node != nil {
   253  				rule.Expr = intstr.FromString(rule.ExprBuilder.Node.Build())
   254  				// limiting the clusters will take the union result for clusters from scoped nodes.
   255  				// eg. if specify nodeA of cluster1 and nodeB of cluster2 in rule.ExprBuilder.Node.NodeNames,
   256  				// the nodeA and nodeB in cluster1 and cluster2 are selected.
   257  				var clusters = make(map[string]struct{})
   258  				for _, sname := range rule.ExprBuilder.Node.NodeNames {
   259  					if sname.Cluster != "" {
   260  						clusters[sname.Cluster] = struct{}{}
   261  					}
   262  				}
   263  				if len(clusters) > 0 {
   264  					clusterSelector := &MetricLabelSelector{}
   265  					for cluster := range clusters {
   266  						clusterSelector.InValues = append(clusterSelector.InValues, cluster)
   267  					}
   268  					rule.ClusterSelector = clusterSelector
   269  				}
   270  			} else if rule.ExprBuilder.Workload != nil {
   271  				rule.Expr = intstr.FromString(rule.ExprBuilder.Workload.Build())
   272  				// limiting the clusters and namespaces will take the union result for clusters from scoped workloads.
   273  				// eg. if specify worloadA of cluster1-namespace1 and worloadB of cluster2-namespace2 in rule.ExprBuilder.Workload.WorkloadNames,
   274  				// the worloadA and worloadB in cluster1-namespace1, cluster1-namespace2 and cluster2-namespace1, cluster2-namespace2 are selected.
   275  				var clusters = make(map[string]struct{})
   276  				var namespaces = make(map[string]struct{})
   277  				for _, sname := range rule.ExprBuilder.Workload.WorkloadNames {
   278  					if sname.Cluster != "" {
   279  						clusters[sname.Cluster] = struct{}{}
   280  					}
   281  					if sname.Namespace != "" {
   282  						namespaces[sname.Namespace] = struct{}{}
   283  					}
   284  				}
   285  				if len(clusters) > 0 {
   286  					clusterSelector := &MetricLabelSelector{}
   287  					for cluster := range clusters {
   288  						clusterSelector.InValues = append(clusterSelector.InValues, cluster)
   289  					}
   290  					rule.ClusterSelector = clusterSelector
   291  				}
   292  				if len(namespaces) > 0 {
   293  					nsSelector := &MetricLabelSelector{}
   294  					for ns := range namespaces {
   295  						nsSelector.InValues = append(nsSelector.InValues, ns)
   296  					}
   297  					rule.NamespaceSelector = nsSelector
   298  				}
   299  			}
   300  		}
   301  		setRuleId(&rule.Rule)
   302  		r.Spec.Rules[i] = rule
   303  	}
   304  }
   305  
   306  var _ webhook.Validator = &GlobalRuleGroup{}
   307  
   308  func (r *GlobalRuleGroup) ValidateCreate() error {
   309  	return r.Validate()
   310  }
   311  func (r *GlobalRuleGroup) ValidateUpdate(old runtime.Object) error {
   312  	return r.Validate()
   313  }
   314  func (r *GlobalRuleGroup) ValidateDelete() error {
   315  	return nil
   316  }
   317  func (r *GlobalRuleGroup) Validate() error {
   318  	log := globalrulegrouplog.WithValues("name", r.Name)
   319  	log.Info("validate")
   320  
   321  	if len(r.Spec.Rules) > MaxRuleCountPerGroup {
   322  		return fmt.Errorf("the rule group has %d rules, exceeding the max count (%d)", len(r.Spec.Rules), MaxRuleCountPerGroup)
   323  	}
   324  
   325  	var rules []Rule
   326  	for _, r := range r.Spec.Rules {
   327  		if r.ClusterSelector != nil {
   328  			if err := r.ClusterSelector.Validate(); err != nil {
   329  				return err
   330  			}
   331  		}
   332  		if r.NamespaceSelector != nil {
   333  			if err := r.NamespaceSelector.Validate(); err != nil {
   334  				return err
   335  			}
   336  		}
   337  		rules = append(rules, r.Rule)
   338  	}
   339  	var err = validateRules(log, r.Name, r.Spec.Interval, rules)
   340  	if err == errorEmptyExpr {
   341  		return fmt.Errorf("'expr' must be set for a GlobalRuleGroup")
   342  	}
   343  	return err
   344  }