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 }