Merge pull request #126124 from cici37/feature/validating-admission-policy/metrics-improvement
Feature/validating admission policy/metrics improvement
This commit is contained in:
		| @@ -163,11 +163,12 @@ type variableDeclEnvs map[OptionalVariableDeclarations]*environment.EnvSet | |||||||
| // CompileCELExpression returns a compiled CEL expression. | // CompileCELExpression returns a compiled CEL expression. | ||||||
| // perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input. | // perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input. | ||||||
| func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, envType environment.Type) CompilationResult { | func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, envType environment.Type) CompilationResult { | ||||||
| 	resultError := func(errorString string, errType apiservercel.ErrorType) CompilationResult { | 	resultError := func(errorString string, errType apiservercel.ErrorType, cause error) CompilationResult { | ||||||
| 		return CompilationResult{ | 		return CompilationResult{ | ||||||
| 			Error: &apiservercel.Error{ | 			Error: &apiservercel.Error{ | ||||||
| 				Type:   errType, | 				Type:   errType, | ||||||
| 				Detail: errorString, | 				Detail: errorString, | ||||||
|  | 				Cause:  cause, | ||||||
| 			}, | 			}, | ||||||
| 			ExpressionAccessor: expressionAccessor, | 			ExpressionAccessor: expressionAccessor, | ||||||
| 		} | 		} | ||||||
| @@ -175,12 +176,12 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op | |||||||
|  |  | ||||||
| 	env, err := c.varEnvs[options].Env(envType) | 	env, err := c.varEnvs[options].Env(envType) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal) | 		return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal, nil) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ast, issues := env.Compile(expressionAccessor.GetExpression()) | 	ast, issues := env.Compile(expressionAccessor.GetExpression()) | ||||||
| 	if issues != nil { | 	if issues != nil { | ||||||
| 		return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid) | 		return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid, apiservercel.NewCompilationError(issues)) | ||||||
| 	} | 	} | ||||||
| 	found := false | 	found := false | ||||||
| 	returnTypes := expressionAccessor.ReturnTypes() | 	returnTypes := expressionAccessor.ReturnTypes() | ||||||
| @@ -198,19 +199,19 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op | |||||||
| 			reason = fmt.Sprintf("must evaluate to one of %v", returnTypes) | 			reason = fmt.Sprintf("must evaluate to one of %v", returnTypes) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return resultError(reason, apiservercel.ErrorTypeInvalid) | 		return resultError(reason, apiservercel.ErrorTypeInvalid, nil) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, err = cel.AstToCheckedExpr(ast) | 	_, err = cel.AstToCheckedExpr(ast) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		// should be impossible since env.Compile returned no issues | 		// should be impossible since env.Compile returned no issues | ||||||
| 		return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal) | 		return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal, nil) | ||||||
| 	} | 	} | ||||||
| 	prog, err := env.Program(ast, | 	prog, err := env.Program(ast, | ||||||
| 		cel.InterruptCheckFrequency(celconfig.CheckFrequency), | 		cel.InterruptCheckFrequency(celconfig.CheckFrequency), | ||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal) | 		return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal, nil) | ||||||
| 	} | 	} | ||||||
| 	return CompilationResult{ | 	return CompilationResult{ | ||||||
| 		Program:            prog, | 		Program:            prog, | ||||||
|   | |||||||
| @@ -192,6 +192,7 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione | |||||||
| 			evaluation.Error = &cel.Error{ | 			evaluation.Error = &cel.Error{ | ||||||
| 				Type:   cel.ErrorTypeInvalid, | 				Type:   cel.ErrorTypeInvalid, | ||||||
| 				Detail: fmt.Sprintf("compilation error: %v", compilationResult.Error), | 				Detail: fmt.Sprintf("compilation error: %v", compilationResult.Error), | ||||||
|  | 				Cause:  compilationResult.Error, | ||||||
| 			} | 			} | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| @@ -211,6 +212,7 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione | |||||||
| 				return nil, -1, &cel.Error{ | 				return nil, -1, &cel.Error{ | ||||||
| 					Type:   cel.ErrorTypeInvalid, | 					Type:   cel.ErrorTypeInvalid, | ||||||
| 					Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"), | 					Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"), | ||||||
|  | 					Cause:  cel.ErrOutOfBudget, | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			remainingBudget -= compositionCost | 			remainingBudget -= compositionCost | ||||||
| @@ -228,12 +230,14 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione | |||||||
| 				return nil, -1, &cel.Error{ | 				return nil, -1, &cel.Error{ | ||||||
| 					Type:   cel.ErrorTypeInvalid, | 					Type:   cel.ErrorTypeInvalid, | ||||||
| 					Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()), | 					Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()), | ||||||
|  | 					Cause:  cel.ErrOutOfBudget, | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget { | 				if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget { | ||||||
| 					return nil, -1, &cel.Error{ | 					return nil, -1, &cel.Error{ | ||||||
| 						Type:   cel.ErrorTypeInvalid, | 						Type:   cel.ErrorTypeInvalid, | ||||||
| 						Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"), | 						Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"), | ||||||
|  | 						Cause:  cel.ErrOutOfBudget, | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				remainingBudget -= int64(*rtCost) | 				remainingBudget -= int64(*rtCost) | ||||||
|   | |||||||
| @@ -223,7 +223,7 @@ func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o adm | |||||||
| 					switch decision.Action { | 					switch decision.Action { | ||||||
| 					case ActionAdmit: | 					case ActionAdmit: | ||||||
| 						if decision.Evaluation == EvalError { | 						if decision.Evaluation == EvalError { | ||||||
| 							celmetrics.Metrics.ObserveAdmissionWithError(ctx, decision.Elapsed, definition.Name, binding.Name, "active") | 							celmetrics.Metrics.ObserveAdmission(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision)) | ||||||
| 						} | 						} | ||||||
| 					case ActionDeny: | 					case ActionDeny: | ||||||
| 						for _, action := range binding.Spec.ValidationActions { | 						for _, action := range binding.Spec.ValidationActions { | ||||||
| @@ -234,13 +234,13 @@ func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o adm | |||||||
| 									Binding:        binding, | 									Binding:        binding, | ||||||
| 									PolicyDecision: decision, | 									PolicyDecision: decision, | ||||||
| 								}) | 								}) | ||||||
| 								celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, "active") | 								celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision)) | ||||||
| 							case admissionregistrationv1.Audit: | 							case admissionregistrationv1.Audit: | ||||||
| 								publishValidationFailureAnnotation(binding, i, decision, versionedAttr) | 								publishValidationFailureAnnotation(binding, i, decision, versionedAttr) | ||||||
| 								celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, "active") | 								celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision)) | ||||||
| 							case admissionregistrationv1.Warn: | 							case admissionregistrationv1.Warn: | ||||||
| 								warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message)) | 								warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message)) | ||||||
| 								celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, "active") | 								celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision)) | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					default: | 					default: | ||||||
| @@ -259,7 +259,7 @@ func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o adm | |||||||
| 						auditAnnotationCollector.add(auditAnnotation.Key, value) | 						auditAnnotationCollector.add(auditAnnotation.Key, value) | ||||||
| 					case AuditAnnotationActionError: | 					case AuditAnnotationActionError: | ||||||
| 						// When failurePolicy=fail, audit annotation errors result in deny | 						// When failurePolicy=fail, audit annotation errors result in deny | ||||||
| 						deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{ | 						d := policyDecisionWithMetadata{ | ||||||
| 							Definition: definition, | 							Definition: definition, | ||||||
| 							Binding:    binding, | 							Binding:    binding, | ||||||
| 							PolicyDecision: PolicyDecision{ | 							PolicyDecision: PolicyDecision{ | ||||||
| @@ -268,8 +268,9 @@ func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o adm | |||||||
| 								Message:    auditAnnotation.Error, | 								Message:    auditAnnotation.Error, | ||||||
| 								Elapsed:    auditAnnotation.Elapsed, | 								Elapsed:    auditAnnotation.Elapsed, | ||||||
| 							}, | 							}, | ||||||
| 						}) | 						} | ||||||
| 						celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, "active") | 						deniedDecisions = append(deniedDecisions, d) | ||||||
|  | 						celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, ErrorType(&d.PolicyDecision)) | ||||||
| 					case AuditAnnotationActionExclude: // skip it | 					case AuditAnnotationActionExclude: // skip it | ||||||
| 					default: | 					default: | ||||||
| 						return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action) | 						return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action) | ||||||
|   | |||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2024 The Kubernetes 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 validating | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	celmetrics "k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrorType decodes the error to determine the error type | ||||||
|  | // that the metrics understand. | ||||||
|  | func ErrorType(decision *PolicyDecision) celmetrics.ValidationErrorType { | ||||||
|  | 	if decision.Evaluation == EvalAdmit { | ||||||
|  | 		return celmetrics.ValidationNoError | ||||||
|  | 	} | ||||||
|  | 	if strings.HasPrefix(decision.Message, "compilation") { | ||||||
|  | 		return celmetrics.ValidationCompileError | ||||||
|  | 	} | ||||||
|  | 	if strings.HasPrefix(decision.Message, "validation failed due to running out of cost budget") { | ||||||
|  | 		return celmetrics.ValidatingOutOfBudget | ||||||
|  | 	} | ||||||
|  | 	return celmetrics.ValidatingInvalidError | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2024 The Kubernetes 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 cel | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  |  | ||||||
|  | 	apiservercel "k8s.io/apiserver/pkg/cel" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrorType decodes the error to determine the error type | ||||||
|  | // that the metrics understand. | ||||||
|  | func ErrorType(err error) ValidationErrorType { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return ValidationNoError | ||||||
|  | 	} | ||||||
|  | 	if errors.Is(err, apiservercel.ErrCompilation) { | ||||||
|  | 		return ValidationCompileError | ||||||
|  | 	} | ||||||
|  | 	if errors.Is(err, apiservercel.ErrOutOfBudget) { | ||||||
|  | 		return ValidatingOutOfBudget | ||||||
|  | 	} | ||||||
|  | 	return ValidatingInvalidError | ||||||
|  | } | ||||||
| @@ -29,6 +29,22 @@ const ( | |||||||
| 	metricsSubsystem = "validating_admission_policy" | 	metricsSubsystem = "validating_admission_policy" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // ValidationErrorType defines different error types that happen to a validation expression | ||||||
|  | type ValidationErrorType string | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// ValidationCompileError indicates that the expression fails to compile. | ||||||
|  | 	ValidationCompileError ValidationErrorType = "compile_error" | ||||||
|  | 	// ValidatingInvalidError indicates that the expression fails due to internal | ||||||
|  | 	// errors that are out of the control of the user. | ||||||
|  | 	ValidatingInvalidError ValidationErrorType = "invalid_error" | ||||||
|  | 	// ValidatingOutOfBudget indicates that the expression fails due to running | ||||||
|  | 	// out of cost budget, or the budget cannot be obtained. | ||||||
|  | 	ValidatingOutOfBudget ValidationErrorType = "out_of_budget" | ||||||
|  | 	// ValidationNoError indicates that the expression returns without an error. | ||||||
|  | 	ValidationNoError ValidationErrorType = "no_error" | ||||||
|  | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// Metrics provides access to validation admission metrics. | 	// Metrics provides access to validation admission metrics. | ||||||
| 	Metrics = newValidationAdmissionMetrics() | 	Metrics = newValidationAdmissionMetrics() | ||||||
| @@ -36,9 +52,8 @@ var ( | |||||||
|  |  | ||||||
| // ValidatingAdmissionPolicyMetrics aggregates Prometheus metrics related to validation admission control. | // ValidatingAdmissionPolicyMetrics aggregates Prometheus metrics related to validation admission control. | ||||||
| type ValidatingAdmissionPolicyMetrics struct { | type ValidatingAdmissionPolicyMetrics struct { | ||||||
| 	policyCheck      *metrics.CounterVec | 	policyCheck   *metrics.CounterVec | ||||||
| 	policyDefinition *metrics.CounterVec | 	policyLatency *metrics.HistogramVec | ||||||
| 	policyLatency    *metrics.HistogramVec |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func newValidationAdmissionMetrics() *ValidatingAdmissionPolicyMetrics { | func newValidationAdmissionMetrics() *ValidatingAdmissionPolicyMetrics { | ||||||
| @@ -47,25 +62,16 @@ func newValidationAdmissionMetrics() *ValidatingAdmissionPolicyMetrics { | |||||||
| 			Namespace:      metricsNamespace, | 			Namespace:      metricsNamespace, | ||||||
| 			Subsystem:      metricsSubsystem, | 			Subsystem:      metricsSubsystem, | ||||||
| 			Name:           "check_total", | 			Name:           "check_total", | ||||||
| 			Help:           "Validation admission policy check total, labeled by policy and further identified by binding, enforcement action taken, and state.", | 			Help:           "Validation admission policy check total, labeled by policy and further identified by binding and enforcement action taken.", | ||||||
| 			StabilityLevel: metrics.ALPHA, | 			StabilityLevel: metrics.ALPHA, | ||||||
| 		}, | 		}, | ||||||
| 		[]string{"policy", "policy_binding", "enforcement_action", "state"}, | 		[]string{"policy", "policy_binding", "error_type", "enforcement_action"}, | ||||||
| 	) |  | ||||||
| 	definition := metrics.NewCounterVec(&metrics.CounterOpts{ |  | ||||||
| 		Namespace:      metricsNamespace, |  | ||||||
| 		Subsystem:      metricsSubsystem, |  | ||||||
| 		Name:           "definition_total", |  | ||||||
| 		Help:           "Validation admission policy count total, labeled by state and enforcement action.", |  | ||||||
| 		StabilityLevel: metrics.ALPHA, |  | ||||||
| 	}, |  | ||||||
| 		[]string{"state", "enforcement_action"}, |  | ||||||
| 	) | 	) | ||||||
| 	latency := metrics.NewHistogramVec(&metrics.HistogramOpts{ | 	latency := metrics.NewHistogramVec(&metrics.HistogramOpts{ | ||||||
| 		Namespace: metricsNamespace, | 		Namespace: metricsNamespace, | ||||||
| 		Subsystem: metricsSubsystem, | 		Subsystem: metricsSubsystem, | ||||||
| 		Name:      "check_duration_seconds", | 		Name:      "check_duration_seconds", | ||||||
| 		Help:      "Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding, state and enforcement action taken.", | 		Help:      "Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding and enforcement action taken.", | ||||||
| 		// the bucket distribution here is based oo the benchmark suite at | 		// the bucket distribution here is based oo the benchmark suite at | ||||||
| 		// github.com/DangerOnTheRanger/cel-benchmark performed on 16-core Intel Xeon | 		// github.com/DangerOnTheRanger/cel-benchmark performed on 16-core Intel Xeon | ||||||
| 		// the lowest bucket was based around the 180ns/op figure for BenchmarkAccess, | 		// the lowest bucket was based around the 180ns/op figure for BenchmarkAccess, | ||||||
| @@ -77,47 +83,40 @@ func newValidationAdmissionMetrics() *ValidatingAdmissionPolicyMetrics { | |||||||
| 		Buckets:        []float64{0.0000005, 0.001, 0.01, 0.1, 1.0}, | 		Buckets:        []float64{0.0000005, 0.001, 0.01, 0.1, 1.0}, | ||||||
| 		StabilityLevel: metrics.ALPHA, | 		StabilityLevel: metrics.ALPHA, | ||||||
| 	}, | 	}, | ||||||
| 		[]string{"policy", "policy_binding", "enforcement_action", "state"}, | 		[]string{"policy", "policy_binding", "error_type", "enforcement_action"}, | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	legacyregistry.MustRegister(check) | 	legacyregistry.MustRegister(check) | ||||||
| 	legacyregistry.MustRegister(definition) |  | ||||||
| 	legacyregistry.MustRegister(latency) | 	legacyregistry.MustRegister(latency) | ||||||
| 	return &ValidatingAdmissionPolicyMetrics{policyCheck: check, policyDefinition: definition, policyLatency: latency} | 	return &ValidatingAdmissionPolicyMetrics{policyCheck: check, policyLatency: latency} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Reset resets all validation admission-related Prometheus metrics. | // Reset resets all validation admission-related Prometheus metrics. | ||||||
| func (m *ValidatingAdmissionPolicyMetrics) Reset() { | func (m *ValidatingAdmissionPolicyMetrics) Reset() { | ||||||
| 	m.policyCheck.Reset() | 	m.policyCheck.Reset() | ||||||
| 	m.policyDefinition.Reset() |  | ||||||
| 	m.policyLatency.Reset() | 	m.policyLatency.Reset() | ||||||
| } | } | ||||||
|  |  | ||||||
| // ObserveDefinition observes a policy definition. | // ObserveAdmission observes a policy validation, with an optional error to indicate the error that may occur but ignored. | ||||||
| func (m *ValidatingAdmissionPolicyMetrics) ObserveDefinition(ctx context.Context, state, enforcementAction string) { | func (m *ValidatingAdmissionPolicyMetrics) ObserveAdmission(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) { | ||||||
| 	m.policyDefinition.WithContext(ctx).WithLabelValues(state, enforcementAction).Inc() | 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "allow").Inc() | ||||||
| } | 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "allow").Observe(elapsed.Seconds()) | ||||||
|  |  | ||||||
| // ObserveAdmissionWithError observes a policy validation error that was ignored due to failure policy. |  | ||||||
| func (m *ValidatingAdmissionPolicyMetrics) ObserveAdmissionWithError(ctx context.Context, elapsed time.Duration, policy, binding, state string) { |  | ||||||
| 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, "allow", state).Inc() |  | ||||||
| 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, "allow", state).Observe(elapsed.Seconds()) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // ObserveRejection observes a policy validation error that was at least one of the reasons for a deny. | // ObserveRejection observes a policy validation error that was at least one of the reasons for a deny. | ||||||
| func (m *ValidatingAdmissionPolicyMetrics) ObserveRejection(ctx context.Context, elapsed time.Duration, policy, binding, state string) { | func (m *ValidatingAdmissionPolicyMetrics) ObserveRejection(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) { | ||||||
| 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, "deny", state).Inc() | 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "deny").Inc() | ||||||
| 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, "deny", state).Observe(elapsed.Seconds()) | 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "deny").Observe(elapsed.Seconds()) | ||||||
| } | } | ||||||
|  |  | ||||||
| // ObserveAudit observes a policy validation audit annotation was published for a validation failure. | // ObserveAudit observes a policy validation audit annotation was published for a validation failure. | ||||||
| func (m *ValidatingAdmissionPolicyMetrics) ObserveAudit(ctx context.Context, elapsed time.Duration, policy, binding, state string) { | func (m *ValidatingAdmissionPolicyMetrics) ObserveAudit(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) { | ||||||
| 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, "audit", state).Inc() | 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "audit").Inc() | ||||||
| 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, "audit", state).Observe(elapsed.Seconds()) | 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "audit").Observe(elapsed.Seconds()) | ||||||
| } | } | ||||||
|  |  | ||||||
| // ObserveWarn observes a policy validation warning was published for a validation failure. | // ObserveWarn observes a policy validation warning was published for a validation failure. | ||||||
| func (m *ValidatingAdmissionPolicyMetrics) ObserveWarn(ctx context.Context, elapsed time.Duration, policy, binding, state string) { | func (m *ValidatingAdmissionPolicyMetrics) ObserveWarn(ctx context.Context, elapsed time.Duration, policy, binding string, errorType ValidationErrorType) { | ||||||
| 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, "warn", state).Inc() | 	m.policyCheck.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "warn").Inc() | ||||||
| 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, "warn", state).Observe(elapsed.Seconds()) | 	m.policyLatency.WithContext(ctx).WithLabelValues(policy, binding, string(errorType), "warn").Observe(elapsed.Seconds()) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -31,7 +31,6 @@ type metricsObserver func() | |||||||
| func TestNoUtils(t *testing.T) { | func TestNoUtils(t *testing.T) { | ||||||
|  |  | ||||||
| 	metrics := []string{ | 	metrics := []string{ | ||||||
| 		"apiserver_validating_admission_policy_definition_total", |  | ||||||
| 		"apiserver_validating_admission_policy_check_total", | 		"apiserver_validating_admission_policy_check_total", | ||||||
| 		"apiserver_validating_admission_policy_check_duration_seconds", | 		"apiserver_validating_admission_policy_check_duration_seconds", | ||||||
| 	} | 	} | ||||||
| @@ -44,53 +43,43 @@ func TestNoUtils(t *testing.T) { | |||||||
| 		{ | 		{ | ||||||
| 			desc: "observe policy admission", | 			desc: "observe policy admission", | ||||||
| 			want: ` | 			want: ` | ||||||
| 			# HELP apiserver_validating_admission_policy_check_duration_seconds [ALPHA] Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding, state and enforcement action taken. | 			# HELP apiserver_validating_admission_policy_check_duration_seconds [ALPHA] Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding and enforcement action taken. | ||||||
|             # TYPE apiserver_validating_admission_policy_check_duration_seconds histogram |             # TYPE apiserver_validating_admission_policy_check_duration_seconds histogram | ||||||
| 			apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.0000005"} 0 | 			apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.0000005"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.001"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.001"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.01"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.01"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.1"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.1"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="1"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="1"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="+Inf"} 1 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="+Inf"} 1 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_sum{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active"} 10 |             apiserver_validating_admission_policy_check_duration_seconds_sum{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com"} 10 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_count{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active"} 1 |             apiserver_validating_admission_policy_check_duration_seconds_count{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com"} 1 | ||||||
|             # HELP apiserver_validating_admission_policy_check_total [ALPHA] Validation admission policy check total, labeled by policy and further identified by binding, enforcement action taken, and state. |             # HELP apiserver_validating_admission_policy_check_total [ALPHA] Validation admission policy check total, labeled by policy and further identified by binding and enforcement action taken. | ||||||
|             # TYPE apiserver_validating_admission_policy_check_total counter |             # TYPE apiserver_validating_admission_policy_check_total counter | ||||||
|             apiserver_validating_admission_policy_check_total{enforcement_action="allow",policy="policy.example.com",policy_binding="binding.example.com",state="active"} 1 |             apiserver_validating_admission_policy_check_total{enforcement_action="allow",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com"} 1 | ||||||
| 			`, | 			`, | ||||||
| 			observer: func() { | 			observer: func() { | ||||||
| 				Metrics.ObserveAdmissionWithError(context.TODO(), time.Duration(10)*time.Second, "policy.example.com", "binding.example.com", "active") | 				Metrics.ObserveAdmission(context.TODO(), time.Duration(10)*time.Second, "policy.example.com", "binding.example.com", ValidatingInvalidError) | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			desc: "observe policy rejection", | 			desc: "observe policy rejection", | ||||||
| 			want: ` | 			want: ` | ||||||
| 			# HELP apiserver_validating_admission_policy_check_duration_seconds [ALPHA] Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding, state and enforcement action taken. | 			# HELP apiserver_validating_admission_policy_check_duration_seconds [ALPHA] Validation admission latency for individual validation expressions in seconds, labeled by policy and further including binding and enforcement action taken. | ||||||
|             # TYPE apiserver_validating_admission_policy_check_duration_seconds histogram |             # TYPE apiserver_validating_admission_policy_check_duration_seconds histogram | ||||||
| 			apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.0000005"} 0 | 			apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.0000005"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.001"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.001"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.01"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.01"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="0.1"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="0.1"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="1"} 0 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="1"} 0 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active",le="+Inf"} 1 |             apiserver_validating_admission_policy_check_duration_seconds_bucket{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com",le="+Inf"} 1 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_sum{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active"} 10 |             apiserver_validating_admission_policy_check_duration_seconds_sum{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com"} 10 | ||||||
|             apiserver_validating_admission_policy_check_duration_seconds_count{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active"} 1 |             apiserver_validating_admission_policy_check_duration_seconds_count{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com"} 1 | ||||||
|             # HELP apiserver_validating_admission_policy_check_total [ALPHA] Validation admission policy check total, labeled by policy and further identified by binding, enforcement action taken, and state. |             # HELP apiserver_validating_admission_policy_check_total [ALPHA] Validation admission policy check total, labeled by policy and further identified by binding and enforcement action taken. | ||||||
|             # TYPE apiserver_validating_admission_policy_check_total counter |             # TYPE apiserver_validating_admission_policy_check_total counter | ||||||
|             apiserver_validating_admission_policy_check_total{enforcement_action="deny",policy="policy.example.com",policy_binding="binding.example.com",state="active"} 1 |             apiserver_validating_admission_policy_check_total{enforcement_action="deny",error_type="invalid_error",policy="policy.example.com",policy_binding="binding.example.com"} 1 | ||||||
| 			`, | 			`, | ||||||
| 			observer: func() { | 			observer: func() { | ||||||
| 				Metrics.ObserveRejection(context.TODO(), time.Duration(10)*time.Second, "policy.example.com", "binding.example.com", "active") | 				Metrics.ObserveRejection(context.TODO(), time.Duration(10)*time.Second, "policy.example.com", "binding.example.com", ValidatingInvalidError) | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			desc: "observe policy definition", |  | ||||||
| 			want: `# HELP apiserver_validating_admission_policy_definition_total [ALPHA] Validation admission policy count total, labeled by state and enforcement action. |  | ||||||
|             # TYPE apiserver_validating_admission_policy_definition_total counter |  | ||||||
|             apiserver_validating_admission_policy_definition_total{enforcement_action="deny",state="active"} 1 |  | ||||||
| 			`, |  | ||||||
| 			observer: func() { |  | ||||||
| 				Metrics.ObserveDefinition(context.TODO(), "active", "deny") |  | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ package validating | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @@ -132,19 +133,14 @@ func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVe | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var messageResult *cel.EvaluationResult | 		var messageResult *cel.EvaluationResult | ||||||
| 		var messageError *apiservercel.Error |  | ||||||
| 		if len(messageResults) > i { | 		if len(messageResults) > i { | ||||||
| 			messageResult = &messageResults[i] | 			messageResult = &messageResults[i] | ||||||
| 		} | 		} | ||||||
| 		messageError, _ = err.(*apiservercel.Error) |  | ||||||
| 		if evalResult.Error != nil { | 		if evalResult.Error != nil { | ||||||
| 			decision.Action = policyDecisionActionForError(f) | 			decision.Action = policyDecisionActionForError(f) | ||||||
| 			decision.Evaluation = EvalError | 			decision.Evaluation = EvalError | ||||||
| 			decision.Message = evalResult.Error.Error() | 			decision.Message = evalResult.Error.Error() | ||||||
| 		} else if messageError != nil && | 		} else if errors.Is(err, apiservercel.ErrInternal) || errors.Is(err, apiservercel.ErrOutOfBudget) { | ||||||
| 			(messageError.Type == apiservercel.ErrorTypeInternal || |  | ||||||
| 				(messageError.Type == apiservercel.ErrorTypeInvalid && |  | ||||||
| 					strings.HasPrefix(messageError.Detail, "validation failed due to running out of cost budget"))) { |  | ||||||
| 			decision.Action = policyDecisionActionForError(f) | 			decision.Action = policyDecisionActionForError(f) | ||||||
| 			decision.Evaluation = EvalError | 			decision.Evaluation = EvalError | ||||||
| 			decision.Message = fmt.Sprintf("failed messageExpression: %s", err) | 			decision.Message = fmt.Sprintf("failed messageExpression: %s", err) | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ func (f *fakeCelFilter) ForInput(ctx context.Context, versionedAttr *admission.V | |||||||
| 		return nil, -1, &apiservercel.Error{ | 		return nil, -1, &apiservercel.Error{ | ||||||
| 			Type:   apiservercel.ErrorTypeInvalid, | 			Type:   apiservercel.ErrorTypeInvalid, | ||||||
| 			Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"), | 			Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"), | ||||||
|  | 			Cause:  apiservercel.ErrOutOfBudget, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if f.throwError { | 	if f.throwError { | ||||||
|   | |||||||
| @@ -16,11 +16,46 @@ limitations under the License. | |||||||
|  |  | ||||||
| package cel | package cel | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/google/cel-go/cel" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrInternal the basic error that occurs when the expression fails to evaluate | ||||||
|  | // due to internal reasons. Any Error that has the Type of | ||||||
|  | // ErrorInternal is considered equal to ErrInternal | ||||||
|  | var ErrInternal = fmt.Errorf("internal") | ||||||
|  |  | ||||||
|  | // ErrInvalid is the basic error that occurs when the expression fails to | ||||||
|  | // evaluate but not due to internal reasons. Any Error that has the Type of | ||||||
|  | // ErrorInvalid is considered equal to ErrInvalid. | ||||||
|  | var ErrInvalid = fmt.Errorf("invalid") | ||||||
|  |  | ||||||
|  | // ErrRequired is the basic error that occurs when the expression is required | ||||||
|  | // but absent. | ||||||
|  | // Any Error that has the Type of ErrorRequired is considered equal | ||||||
|  | // to ErrRequired. | ||||||
|  | var ErrRequired = fmt.Errorf("required") | ||||||
|  |  | ||||||
|  | // ErrCompilation is the basic error that occurs when the expression fails to | ||||||
|  | // compile. Any CompilationError wraps ErrCompilation. | ||||||
|  | // ErrCompilation wraps ErrInvalid | ||||||
|  | var ErrCompilation = fmt.Errorf("%w: compilation error", ErrInvalid) | ||||||
|  |  | ||||||
|  | // ErrOutOfBudget is the basic error that occurs when the expression fails due to | ||||||
|  | // exceeding budget. | ||||||
|  | var ErrOutOfBudget = fmt.Errorf("out of budget") | ||||||
|  |  | ||||||
| // Error is an implementation of the 'error' interface, which represents a | // Error is an implementation of the 'error' interface, which represents a | ||||||
| // XValidation error. | // XValidation error. | ||||||
| type Error struct { | type Error struct { | ||||||
| 	Type   ErrorType | 	Type   ErrorType | ||||||
| 	Detail string | 	Detail string | ||||||
|  |  | ||||||
|  | 	// Cause is an optional wrapped errors that can be useful to | ||||||
|  | 	// programmatically retrieve detailed errors. | ||||||
|  | 	Cause error | ||||||
| } | } | ||||||
|  |  | ||||||
| var _ error = &Error{} | var _ error = &Error{} | ||||||
| @@ -30,7 +65,24 @@ func (v *Error) Error() string { | |||||||
| 	return v.Detail | 	return v.Detail | ||||||
| } | } | ||||||
|  |  | ||||||
| // ErrorType is a machine readable value providing more detail about why | func (v *Error) Is(err error) bool { | ||||||
|  | 	switch v.Type { | ||||||
|  | 	case ErrorTypeRequired: | ||||||
|  | 		return err == ErrRequired | ||||||
|  | 	case ErrorTypeInvalid: | ||||||
|  | 		return err == ErrInvalid | ||||||
|  | 	case ErrorTypeInternal: | ||||||
|  | 		return err == ErrInternal | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Unwrap returns the wrapped Cause. | ||||||
|  | func (v *Error) Unwrap() error { | ||||||
|  | 	return v.Cause | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ErrorType is a machine-readable value providing more detail about why | ||||||
| // a XValidation is invalid. | // a XValidation is invalid. | ||||||
| type ErrorType string | type ErrorType string | ||||||
|  |  | ||||||
| @@ -45,3 +97,28 @@ const ( | |||||||
| 	// to user input.  See InternalError(). | 	// to user input.  See InternalError(). | ||||||
| 	ErrorTypeInternal ErrorType = "InternalError" | 	ErrorTypeInternal ErrorType = "InternalError" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // CompilationError indicates an error during expression compilation. | ||||||
|  | // It wraps ErrCompilation. | ||||||
|  | type CompilationError struct { | ||||||
|  | 	err    *Error | ||||||
|  | 	Issues *cel.Issues | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewCompilationError wraps a cel.Issues to indicate a compilation failure. | ||||||
|  | func NewCompilationError(issues *cel.Issues) *CompilationError { | ||||||
|  | 	return &CompilationError{ | ||||||
|  | 		Issues: issues, | ||||||
|  | 		err: &Error{ | ||||||
|  | 			Type:   ErrorTypeInvalid, | ||||||
|  | 			Detail: fmt.Sprintf("compilation error: %s", issues), | ||||||
|  | 		}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *CompilationError) Error() string { | ||||||
|  | 	return e.err.Error() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e *CompilationError) Unwrap() []error { | ||||||
|  | 	return []error{e.err, ErrCompilation} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								staging/src/k8s.io/apiserver/pkg/cel/errors_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								staging/src/k8s.io/apiserver/pkg/cel/errors_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2024 The Kubernetes 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 cel | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/google/cel-go/cel" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestOutOfBudgetError(t *testing.T) { | ||||||
|  | 	err := &Error{ | ||||||
|  | 		Type:   ErrorTypeInvalid, | ||||||
|  | 		Detail: "expression out of budget", | ||||||
|  | 		Cause:  ErrOutOfBudget, | ||||||
|  | 	} | ||||||
|  | 	if !errors.Is(err, ErrOutOfBudget) { | ||||||
|  | 		t.Errorf("unexpected %v is not %v", err, ErrOutOfBudget) | ||||||
|  | 	} | ||||||
|  | 	if !errors.Is(err, ErrInvalid) { | ||||||
|  | 		t.Errorf("unexpected %v is not %v", err, ErrInvalid) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestCompilationError(t *testing.T) { | ||||||
|  | 	if !errors.Is(ErrCompilation, ErrInvalid) { | ||||||
|  | 		t.Errorf("unexpected %v is not %v", ErrCompilation, ErrInvalid) | ||||||
|  | 	} | ||||||
|  | 	issues := &cel.Issues{} | ||||||
|  | 	err := &Error{ | ||||||
|  | 		Type:   ErrorTypeInvalid, | ||||||
|  | 		Detail: "fake compilation failed", | ||||||
|  | 		Cause:  NewCompilationError(issues), | ||||||
|  | 	} | ||||||
|  | 	if !errors.Is(err, ErrCompilation) { | ||||||
|  | 		t.Errorf("unexpected %v is not %v", err, ErrCompilation) | ||||||
|  | 	} | ||||||
|  | 	if !errors.Is(err, ErrInvalid) { | ||||||
|  | 		t.Errorf("unexpected %v is not %v", err, ErrInvalid) | ||||||
|  | 	} | ||||||
|  | 	var compilationErr *CompilationError | ||||||
|  | 	if errors.As(err, &compilationErr); compilationErr == nil { | ||||||
|  | 		t.Errorf("unexpected %v cannot be fitted into CompilationError", err) | ||||||
|  | 	} | ||||||
|  | 	if compilationErr.Issues != issues { | ||||||
|  | 		t.Errorf("retrieved issues is not the original") | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot