package federatedresourcequota import ( "context" "net/http" "strings" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" clustervalidation "github.com/karmada-io/karmada/pkg/apis/cluster/validation" policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1" "github.com/karmada-io/karmada/pkg/util/lifted" ) // ValidatingAdmission validates FederatedResourceQuota object when creating/updating. type ValidatingAdmission struct { decoder *admission.Decoder } // Check if our ValidatingAdmission implements necessary interface var _ admission.Handler = &ValidatingAdmission{} var _ admission.DecoderInjector = &ValidatingAdmission{} // Handle implements admission.Handler interface. // It yields a response to an AdmissionRequest. func (v *ValidatingAdmission) Handle(ctx context.Context, req admission.Request) admission.Response { quota := &policyv1alpha1.FederatedResourceQuota{} err := v.decoder.Decode(req, quota) if err != nil { return admission.Errored(http.StatusBadRequest, err) } klog.V(2).Infof("Validating FederatedResourceQuote(%s) for request: %s", klog.KObj(quota).String(), req.Operation) if errs := validateFederatedResourceQuota(quota); len(errs) != 0 { klog.Errorf("%v", errs) return admission.Denied(errs.ToAggregate().Error()) } return admission.Allowed("") } // InjectDecoder implements admission.DecoderInjector interface. // A decoder will be automatically injected. func (v *ValidatingAdmission) InjectDecoder(d *admission.Decoder) error { v.decoder = d return nil } func validateFederatedResourceQuota(quota *policyv1alpha1.FederatedResourceQuota) field.ErrorList { errs := field.ErrorList{} errs = append(errs, validateFederatedResourceQuotaSpec("a.Spec, field.NewPath("spec"))...) errs = append(errs, validateFederatedResourceQuotaStatus("a.Status, field.NewPath("status"))...) return errs } func validateFederatedResourceQuotaSpec(quotaSpec *policyv1alpha1.FederatedResourceQuotaSpec, fld *field.Path) field.ErrorList { errs := field.ErrorList{} errs = append(errs, validateResourceList(quotaSpec.Overall, fld.Child("overall"))...) fldPath := fld.Child("staticAssignments") for index := range quotaSpec.StaticAssignments { errs = append(errs, validateStaticAssignment("aSpec.StaticAssignments[index], fldPath.Index(index))...) } errs = append(errs, validateOverallAndAssignments(quotaSpec, fld)...) return errs } func validateStaticAssignment(staticAssignment *policyv1alpha1.StaticClusterAssignment, fld *field.Path) field.ErrorList { errs := field.ErrorList{} if errMegs := clustervalidation.ValidateClusterName(staticAssignment.ClusterName); len(errMegs) > 0 { errs = append(errs, field.Invalid(fld.Child("clusterName"), staticAssignment.ClusterName, strings.Join(errMegs, ","))) } errs = append(errs, validateResourceList(staticAssignment.Hard, fld.Child("hard"))...) return errs } func validateOverallAndAssignments(quotaSpec *policyv1alpha1.FederatedResourceQuotaSpec, fld *field.Path) field.ErrorList { errs := field.ErrorList{} overallPath := fld.Child("overall") for k, v := range quotaSpec.Overall { assignment := calculateAssignmentForResourceKey(k, quotaSpec.StaticAssignments) if v.Cmp(assignment) < 0 { errs = append(errs, field.Invalid(overallPath.Key(string(k)), v.String(), "overall is less than assignments")) } } staticAssignmentsPath := fld.Child("staticAssignments") for index, assignment := range quotaSpec.StaticAssignments { restPath := staticAssignmentsPath.Index(index).Child("hard") for k := range assignment.Hard { if _, exist := quotaSpec.Overall[k]; !exist { errs = append(errs, field.Invalid(restPath.Key(string(k)), k, "assignment resourceName is not exist in overall")) } } } return errs } func calculateAssignmentForResourceKey(resourceKey corev1.ResourceName, staticAssignments []policyv1alpha1.StaticClusterAssignment) resource.Quantity { quantity := resource.Quantity{} for index := range staticAssignments { q, exist := staticAssignments[index].Hard[resourceKey] if exist { quantity.Add(q) } } return quantity } func validateFederatedResourceQuotaStatus(quotaStatus *policyv1alpha1.FederatedResourceQuotaStatus, fld *field.Path) field.ErrorList { errs := field.ErrorList{} errs = append(errs, validateResourceList(quotaStatus.Overall, fld.Child("overall"))...) errs = append(errs, validateResourceList(quotaStatus.OverallUsed, fld.Child("overallUsed"))...) fldPath := fld.Child("aggregatedStatus") for index := range quotaStatus.AggregatedStatus { errs = append(errs, validateClusterQuotaStatus("aStatus.AggregatedStatus[index], fldPath.Index(index))...) } return errs } func validateClusterQuotaStatus(aggregatedStatus *policyv1alpha1.ClusterQuotaStatus, fld *field.Path) field.ErrorList { errs := field.ErrorList{} if errMegs := clustervalidation.ValidateClusterName(aggregatedStatus.ClusterName); len(errMegs) > 0 { errs = append(errs, field.Invalid(fld.Child("clusterName"), aggregatedStatus.ClusterName, strings.Join(errMegs, ","))) } errs = append(errs, validateResourceList(aggregatedStatus.Hard, fld.Child("hard"))...) errs = append(errs, validateResourceList(aggregatedStatus.Used, fld.Child("used"))...) return errs } func validateResourceList(resourceList corev1.ResourceList, fld *field.Path) field.ErrorList { errs := field.ErrorList{} for k, v := range resourceList { resPath := fld.Key(string(k)) errs = append(errs, lifted.ValidateResourceQuotaResourceName(string(k), resPath)...) errs = append(errs, lifted.ValidateResourceQuantityValue(string(k), v, resPath)...) } return errs }