karmada/pkg/webhook/federatedresourcequota/validating.go

159 lines
5.6 KiB
Go

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(&quota.Spec, field.NewPath("spec"))...)
errs = append(errs, validateFederatedResourceQuotaStatus(&quota.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(&quotaSpec.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(&quotaStatus.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
}