159 lines
5.6 KiB
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("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
|
|
}
|